@forwardimpact/pathway 0.25.12 → 0.25.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/fit-pathway.js +62 -54
- package/package.json +3 -4
- package/src/commands/agent-io.js +120 -0
- package/src/commands/agent.js +266 -349
- package/src/commands/init.js +2 -2
- package/src/commands/job.js +237 -183
- package/src/components/comparison-radar.js +118 -103
- package/src/components/progression-table.js +244 -208
- package/src/formatters/interview/markdown.js +100 -88
- package/src/formatters/job/description.js +76 -75
- package/src/formatters/job/dom.js +113 -97
- package/src/formatters/level/dom.js +87 -102
- package/src/formatters/questions/markdown.js +37 -33
- package/src/formatters/questions/shared.js +142 -75
- package/src/formatters/skill/dom.js +102 -93
- package/src/lib/comparison-radar-chart.js +256 -0
- package/src/lib/radar-utils.js +199 -0
- package/src/lib/radar.js +25 -662
- package/src/pages/agent-builder-download.js +170 -0
- package/src/pages/agent-builder-preview.js +344 -0
- package/src/pages/agent-builder.js +6 -550
- package/src/pages/progress-comparison.js +110 -0
- package/src/pages/progress.js +11 -111
- package/src/pages/self-assessment-steps.js +494 -0
- package/src/pages/self-assessment.js +54 -504
|
@@ -21,6 +21,213 @@ import { formatLevel } from "../lib/render.js";
|
|
|
21
21
|
import { createLevelCell, createEmptyLevelCell } from "./detail.js";
|
|
22
22
|
import { createBadge } from "./card.js";
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Create a row for a gained (new) item
|
|
26
|
+
*/
|
|
27
|
+
function createGainedRow(item, isSkill, maxLevels) {
|
|
28
|
+
const nameCell = td(
|
|
29
|
+
{},
|
|
30
|
+
a({ href: `#/${isSkill ? "skill" : "behaviour"}/${item.id}` }, item.name),
|
|
31
|
+
span({ className: "gained-badge" }, "NEW"),
|
|
32
|
+
);
|
|
33
|
+
const changeCell = td(
|
|
34
|
+
{ className: "change-cell change-gained" },
|
|
35
|
+
span({ className: "change-indicator" }, "+"),
|
|
36
|
+
);
|
|
37
|
+
if (isSkill) {
|
|
38
|
+
return tr(
|
|
39
|
+
{ className: "change-gained" },
|
|
40
|
+
nameCell,
|
|
41
|
+
td({}, createBadge(item.capability, item.capability)),
|
|
42
|
+
td({}, createBadge(formatLevel(item.type), item.type)),
|
|
43
|
+
createEmptyLevelCell(),
|
|
44
|
+
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
45
|
+
changeCell,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return tr(
|
|
49
|
+
{ className: "change-gained" },
|
|
50
|
+
nameCell,
|
|
51
|
+
createEmptyLevelCell(),
|
|
52
|
+
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
53
|
+
changeCell,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a row for a lost (removed) item
|
|
59
|
+
*/
|
|
60
|
+
function createLostRow(item, isSkill, maxLevels) {
|
|
61
|
+
const nameCell = td(
|
|
62
|
+
{},
|
|
63
|
+
a({ href: `#/${isSkill ? "skill" : "behaviour"}/${item.id}` }, item.name),
|
|
64
|
+
span({ className: "lost-badge" }, "REMOVED"),
|
|
65
|
+
);
|
|
66
|
+
const changeCell = td(
|
|
67
|
+
{ className: "change-cell change-lost" },
|
|
68
|
+
span({ className: "change-indicator" }, "−"),
|
|
69
|
+
);
|
|
70
|
+
if (isSkill) {
|
|
71
|
+
return tr(
|
|
72
|
+
{ className: "change-lost" },
|
|
73
|
+
nameCell,
|
|
74
|
+
td({}, createBadge(item.capability, item.capability)),
|
|
75
|
+
td({}, createBadge(formatLevel(item.type), item.type)),
|
|
76
|
+
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
77
|
+
createEmptyLevelCell(),
|
|
78
|
+
changeCell,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return tr(
|
|
82
|
+
{ className: "change-lost" },
|
|
83
|
+
nameCell,
|
|
84
|
+
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
85
|
+
createEmptyLevelCell(),
|
|
86
|
+
changeCell,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a row for a normal change item
|
|
92
|
+
*/
|
|
93
|
+
function createChangeRow(item, isSkill, maxLevels) {
|
|
94
|
+
const changeClass =
|
|
95
|
+
item.change > 0
|
|
96
|
+
? "change-up"
|
|
97
|
+
: item.change < 0
|
|
98
|
+
? "change-down"
|
|
99
|
+
: "change-same";
|
|
100
|
+
const changeIcon = item.change > 0 ? "↑" : item.change < 0 ? "↓" : "—";
|
|
101
|
+
const changeText =
|
|
102
|
+
item.change !== 0 ? `${changeIcon} ${Math.abs(item.change)}` : "—";
|
|
103
|
+
|
|
104
|
+
if (isSkill) {
|
|
105
|
+
return tr(
|
|
106
|
+
{ className: changeClass },
|
|
107
|
+
td({}, a({ href: `#/skill/${item.id}` }, item.name)),
|
|
108
|
+
td({}, createBadge(item.capability, item.capability)),
|
|
109
|
+
td({}, createBadge(formatLevel(item.type), item.type)),
|
|
110
|
+
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
111
|
+
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
112
|
+
td(
|
|
113
|
+
{ className: `change-cell ${changeClass}` },
|
|
114
|
+
span({ className: "change-indicator" }, changeText),
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return tr(
|
|
119
|
+
{ className: changeClass },
|
|
120
|
+
td({}, a({ href: `#/behaviour/${item.id}` }, item.name)),
|
|
121
|
+
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
122
|
+
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
123
|
+
td(
|
|
124
|
+
{ className: `change-cell ${changeClass}` },
|
|
125
|
+
span({ className: "change-indicator" }, changeText),
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Pluralize a type name
|
|
132
|
+
* @param {string} type
|
|
133
|
+
* @param {number} count
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function pluralize(type, count) {
|
|
137
|
+
return count > 1 ? `${type}s` : type;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Create a progression group with header and table
|
|
142
|
+
* @param {Object} params
|
|
143
|
+
* @param {Array} params.items
|
|
144
|
+
* @param {string} params.icon
|
|
145
|
+
* @param {string} params.label
|
|
146
|
+
* @param {string} [params.headerClass]
|
|
147
|
+
* @param {string} params.tableClass
|
|
148
|
+
* @param {HTMLElement} params.headers
|
|
149
|
+
* @param {Function} params.createRow
|
|
150
|
+
* @returns {HTMLElement}
|
|
151
|
+
*/
|
|
152
|
+
function createProgressionGroup({
|
|
153
|
+
items,
|
|
154
|
+
icon,
|
|
155
|
+
label,
|
|
156
|
+
headerClass,
|
|
157
|
+
tableClass,
|
|
158
|
+
headers,
|
|
159
|
+
createRow,
|
|
160
|
+
}) {
|
|
161
|
+
return div(
|
|
162
|
+
{ className: "progression-group" },
|
|
163
|
+
div(
|
|
164
|
+
{ className: `progression-group-header ${headerClass || ""}`.trim() },
|
|
165
|
+
span({ className: "group-icon" }, icon),
|
|
166
|
+
span({}, label),
|
|
167
|
+
),
|
|
168
|
+
div(
|
|
169
|
+
{ className: "table-container" },
|
|
170
|
+
table(
|
|
171
|
+
{ className: tableClass },
|
|
172
|
+
thead({}, headers),
|
|
173
|
+
tbody({}, ...items.map(createRow)),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a collapsible group for unchanged items
|
|
181
|
+
* @param {Object} params
|
|
182
|
+
* @param {Array} params.items
|
|
183
|
+
* @param {string} params.type
|
|
184
|
+
* @param {boolean} params.isSkill
|
|
185
|
+
* @param {string} params.tableClass
|
|
186
|
+
* @param {Function} params.createRow
|
|
187
|
+
* @returns {HTMLElement}
|
|
188
|
+
*/
|
|
189
|
+
function createCollapsibleGroup({
|
|
190
|
+
items,
|
|
191
|
+
type,
|
|
192
|
+
isSkill,
|
|
193
|
+
tableClass,
|
|
194
|
+
createRow,
|
|
195
|
+
}) {
|
|
196
|
+
const noChangeHeaders = tr(
|
|
197
|
+
{},
|
|
198
|
+
th({}, isSkill ? "Skill" : "Behaviour"),
|
|
199
|
+
...(isSkill ? [th({}, "Capability"), th({}, "Type")] : []),
|
|
200
|
+
th({}, "Current"),
|
|
201
|
+
th({}, "Target"),
|
|
202
|
+
th({}, "Change"),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const detailsId = `no-changes-${type}-${Date.now()}`;
|
|
206
|
+
return div(
|
|
207
|
+
{ className: "progression-group no-change-group" },
|
|
208
|
+
createCollapsibleHeader(
|
|
209
|
+
detailsId,
|
|
210
|
+
`${items.length} ${pluralize(type, items.length)} unchanged`,
|
|
211
|
+
"✓",
|
|
212
|
+
),
|
|
213
|
+
div(
|
|
214
|
+
{
|
|
215
|
+
className: "collapsible-content",
|
|
216
|
+
id: detailsId,
|
|
217
|
+
style: "display: none",
|
|
218
|
+
},
|
|
219
|
+
div(
|
|
220
|
+
{ className: "table-container" },
|
|
221
|
+
table(
|
|
222
|
+
{ className: tableClass },
|
|
223
|
+
thead({}, noChangeHeaders),
|
|
224
|
+
tbody({}, ...items.map(createRow)),
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
24
231
|
/**
|
|
25
232
|
* Create a progression table showing changes
|
|
26
233
|
* @param {SkillChangeItem[]|BehaviourChangeItem[]} changes - Array of change objects
|
|
@@ -44,116 +251,13 @@ export function createProgressionTable(changes, type = "skill") {
|
|
|
44
251
|
const noChanges = changes.filter((c) => c.change === 0);
|
|
45
252
|
|
|
46
253
|
const createRow = (item) => {
|
|
47
|
-
// Handle gained skills (new in target role)
|
|
48
254
|
if (item.isGained) {
|
|
49
|
-
|
|
50
|
-
return tr(
|
|
51
|
-
{ className: "change-gained" },
|
|
52
|
-
td(
|
|
53
|
-
{},
|
|
54
|
-
a({ href: `#/skill/${item.id}` }, item.name),
|
|
55
|
-
span({ className: "gained-badge" }, "NEW"),
|
|
56
|
-
),
|
|
57
|
-
td({}, createBadge(item.capability, item.capability)),
|
|
58
|
-
td({}, createBadge(formatLevel(item.type), item.type)),
|
|
59
|
-
createEmptyLevelCell(),
|
|
60
|
-
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
61
|
-
td(
|
|
62
|
-
{ className: "change-cell change-gained" },
|
|
63
|
-
span({ className: "change-indicator" }, "+"),
|
|
64
|
-
),
|
|
65
|
-
);
|
|
66
|
-
} else {
|
|
67
|
-
return tr(
|
|
68
|
-
{ className: "change-gained" },
|
|
69
|
-
td(
|
|
70
|
-
{},
|
|
71
|
-
a({ href: `#/behaviour/${item.id}` }, item.name),
|
|
72
|
-
span({ className: "gained-badge" }, "NEW"),
|
|
73
|
-
),
|
|
74
|
-
createEmptyLevelCell(),
|
|
75
|
-
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
76
|
-
td(
|
|
77
|
-
{ className: "change-cell change-gained" },
|
|
78
|
-
span({ className: "change-indicator" }, "+"),
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
}
|
|
255
|
+
return createGainedRow(item, isSkill, maxLevels);
|
|
82
256
|
}
|
|
83
|
-
|
|
84
|
-
// Handle lost skills (removed in target role)
|
|
85
257
|
if (item.isLost) {
|
|
86
|
-
|
|
87
|
-
return tr(
|
|
88
|
-
{ className: "change-lost" },
|
|
89
|
-
td(
|
|
90
|
-
{},
|
|
91
|
-
a({ href: `#/skill/${item.id}` }, item.name),
|
|
92
|
-
span({ className: "lost-badge" }, "REMOVED"),
|
|
93
|
-
),
|
|
94
|
-
td({}, createBadge(item.capability, item.capability)),
|
|
95
|
-
td({}, createBadge(formatLevel(item.type), item.type)),
|
|
96
|
-
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
97
|
-
createEmptyLevelCell(),
|
|
98
|
-
td(
|
|
99
|
-
{ className: "change-cell change-lost" },
|
|
100
|
-
span({ className: "change-indicator" }, "−"),
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
} else {
|
|
104
|
-
return tr(
|
|
105
|
-
{ className: "change-lost" },
|
|
106
|
-
td(
|
|
107
|
-
{},
|
|
108
|
-
a({ href: `#/behaviour/${item.id}` }, item.name),
|
|
109
|
-
span({ className: "lost-badge" }, "REMOVED"),
|
|
110
|
-
),
|
|
111
|
-
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
112
|
-
createEmptyLevelCell(),
|
|
113
|
-
td(
|
|
114
|
-
{ className: "change-cell change-lost" },
|
|
115
|
-
span({ className: "change-indicator" }, "−"),
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Normal change (both current and target exist)
|
|
122
|
-
const changeClass =
|
|
123
|
-
item.change > 0
|
|
124
|
-
? "change-up"
|
|
125
|
-
: item.change < 0
|
|
126
|
-
? "change-down"
|
|
127
|
-
: "change-same";
|
|
128
|
-
const changeIcon = item.change > 0 ? "↑" : item.change < 0 ? "↓" : "—";
|
|
129
|
-
const changeText =
|
|
130
|
-
item.change !== 0 ? `${changeIcon} ${Math.abs(item.change)}` : "—";
|
|
131
|
-
|
|
132
|
-
if (isSkill) {
|
|
133
|
-
return tr(
|
|
134
|
-
{ className: changeClass },
|
|
135
|
-
td({}, a({ href: `#/skill/${item.id}` }, item.name)),
|
|
136
|
-
td({}, createBadge(item.capability, item.capability)),
|
|
137
|
-
td({}, createBadge(formatLevel(item.type), item.type)),
|
|
138
|
-
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
139
|
-
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
140
|
-
td(
|
|
141
|
-
{ className: `change-cell ${changeClass}` },
|
|
142
|
-
span({ className: "change-indicator" }, changeText),
|
|
143
|
-
),
|
|
144
|
-
);
|
|
145
|
-
} else {
|
|
146
|
-
return tr(
|
|
147
|
-
{ className: changeClass },
|
|
148
|
-
td({}, a({ href: `#/behaviour/${item.id}` }, item.name)),
|
|
149
|
-
createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
|
|
150
|
-
createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
|
|
151
|
-
td(
|
|
152
|
-
{ className: `change-cell ${changeClass}` },
|
|
153
|
-
span({ className: "change-indicator" }, changeText),
|
|
154
|
-
),
|
|
155
|
-
);
|
|
258
|
+
return createLostRow(item, isSkill, maxLevels);
|
|
156
259
|
}
|
|
260
|
+
return createChangeRow(item, isSkill, maxLevels);
|
|
157
261
|
};
|
|
158
262
|
|
|
159
263
|
const skillHeaders = tr(
|
|
@@ -165,127 +269,59 @@ export function createProgressionTable(changes, type = "skill") {
|
|
|
165
269
|
th({}, "Change"),
|
|
166
270
|
);
|
|
167
271
|
|
|
272
|
+
const tableClass = `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`;
|
|
168
273
|
const content = [];
|
|
169
274
|
|
|
170
|
-
// Show new skills first (in target role but not current)
|
|
171
275
|
if (gained.length > 0) {
|
|
172
276
|
content.push(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
),
|
|
183
|
-
div(
|
|
184
|
-
{ className: "table-container" },
|
|
185
|
-
table(
|
|
186
|
-
{
|
|
187
|
-
className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
|
|
188
|
-
},
|
|
189
|
-
thead({}, skillHeaders),
|
|
190
|
-
tbody({}, ...gained.map(createRow)),
|
|
191
|
-
),
|
|
192
|
-
),
|
|
193
|
-
),
|
|
277
|
+
createProgressionGroup({
|
|
278
|
+
items: gained,
|
|
279
|
+
icon: "➕",
|
|
280
|
+
label: `${gained.length} new ${pluralize(type, gained.length)}`,
|
|
281
|
+
headerClass: "gained-header",
|
|
282
|
+
tableClass,
|
|
283
|
+
headers: skillHeaders,
|
|
284
|
+
createRow,
|
|
285
|
+
}),
|
|
194
286
|
);
|
|
195
287
|
}
|
|
196
288
|
|
|
197
|
-
// Show level changes
|
|
198
289
|
if (changesRequired.length > 0) {
|
|
199
290
|
content.push(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
),
|
|
209
|
-
),
|
|
210
|
-
div(
|
|
211
|
-
{ className: "table-container" },
|
|
212
|
-
table(
|
|
213
|
-
{
|
|
214
|
-
className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
|
|
215
|
-
},
|
|
216
|
-
thead({}, skillHeaders),
|
|
217
|
-
tbody({}, ...changesRequired.map(createRow)),
|
|
218
|
-
),
|
|
219
|
-
),
|
|
220
|
-
),
|
|
291
|
+
createProgressionGroup({
|
|
292
|
+
items: changesRequired,
|
|
293
|
+
icon: "📈",
|
|
294
|
+
label: `${changesRequired.length} ${pluralize(type, changesRequired.length)} need growth`,
|
|
295
|
+
tableClass,
|
|
296
|
+
headers: skillHeaders,
|
|
297
|
+
createRow,
|
|
298
|
+
}),
|
|
221
299
|
);
|
|
222
300
|
}
|
|
223
301
|
|
|
224
|
-
// Show removed skills (in current role but not target)
|
|
225
302
|
if (lost.length > 0) {
|
|
226
303
|
content.push(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
),
|
|
237
|
-
div(
|
|
238
|
-
{ className: "table-container" },
|
|
239
|
-
table(
|
|
240
|
-
{
|
|
241
|
-
className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
|
|
242
|
-
},
|
|
243
|
-
thead({}, skillHeaders),
|
|
244
|
-
tbody({}, ...lost.map(createRow)),
|
|
245
|
-
),
|
|
246
|
-
),
|
|
247
|
-
),
|
|
304
|
+
createProgressionGroup({
|
|
305
|
+
items: lost,
|
|
306
|
+
icon: "➖",
|
|
307
|
+
label: `${lost.length} ${pluralize(type, lost.length)} removed`,
|
|
308
|
+
headerClass: "lost-header",
|
|
309
|
+
tableClass,
|
|
310
|
+
headers: skillHeaders,
|
|
311
|
+
createRow,
|
|
312
|
+
}),
|
|
248
313
|
);
|
|
249
314
|
}
|
|
250
315
|
|
|
251
|
-
// Show no changes (collapsed by default)
|
|
252
316
|
if (noChanges.length > 0) {
|
|
253
|
-
const noChangeHeaders = tr(
|
|
254
|
-
{},
|
|
255
|
-
th({}, isSkill ? "Skill" : "Behaviour"),
|
|
256
|
-
...(isSkill ? [th({}, "Capability"), th({}, "Type")] : []),
|
|
257
|
-
th({}, "Current"),
|
|
258
|
-
th({}, "Target"),
|
|
259
|
-
th({}, "Change"),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const detailsId = `no-changes-${type}-${Date.now()}`;
|
|
263
317
|
content.push(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
div(
|
|
272
|
-
{
|
|
273
|
-
className: "collapsible-content",
|
|
274
|
-
id: detailsId,
|
|
275
|
-
style: "display: none",
|
|
276
|
-
},
|
|
277
|
-
div(
|
|
278
|
-
{ className: "table-container" },
|
|
279
|
-
table(
|
|
280
|
-
{
|
|
281
|
-
className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
|
|
282
|
-
},
|
|
283
|
-
thead({}, noChangeHeaders),
|
|
284
|
-
tbody({}, ...noChanges.map(createRow)),
|
|
285
|
-
),
|
|
286
|
-
),
|
|
287
|
-
),
|
|
288
|
-
),
|
|
318
|
+
createCollapsibleGroup({
|
|
319
|
+
items: noChanges,
|
|
320
|
+
type,
|
|
321
|
+
isSkill,
|
|
322
|
+
tableClass,
|
|
323
|
+
createRow,
|
|
324
|
+
}),
|
|
289
325
|
);
|
|
290
326
|
}
|
|
291
327
|
|
|
@@ -5,6 +5,91 @@
|
|
|
5
5
|
import { formatLevel } from "../../lib/render.js";
|
|
6
6
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Append follow-ups to lines
|
|
10
|
+
* @param {string[]} lines
|
|
11
|
+
* @param {Object} q - Question object
|
|
12
|
+
*/
|
|
13
|
+
function appendFollowUps(lines, q) {
|
|
14
|
+
if (q.followUps.length > 0) {
|
|
15
|
+
lines.push("", "**Follow-ups:**");
|
|
16
|
+
for (const followUp of q.followUps) {
|
|
17
|
+
lines.push(` → ${followUp}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Append looking-for items to lines
|
|
24
|
+
* @param {string[]} lines
|
|
25
|
+
* @param {Object} q - Question object
|
|
26
|
+
*/
|
|
27
|
+
function appendLookingFor(lines, q) {
|
|
28
|
+
if (q.lookingFor && q.lookingFor.length > 0) {
|
|
29
|
+
lines.push("", "**What to look for:**");
|
|
30
|
+
for (const item of q.lookingFor) {
|
|
31
|
+
lines.push(`- ${item}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format skill question sections
|
|
38
|
+
* @param {string[]} lines
|
|
39
|
+
* @param {Array} sections
|
|
40
|
+
* @param {string} emoji
|
|
41
|
+
*/
|
|
42
|
+
function formatSkillSections(lines, sections, emoji) {
|
|
43
|
+
if (sections.length === 0) return;
|
|
44
|
+
lines.push(`## ${emoji} Skill Questions`, "");
|
|
45
|
+
for (const section of sections) {
|
|
46
|
+
lines.push(`### ${section.name} (${formatLevel(section.level)})`, "");
|
|
47
|
+
for (const q of section.questions) {
|
|
48
|
+
lines.push(`**Q**: ${q.question}`);
|
|
49
|
+
appendFollowUps(lines, q);
|
|
50
|
+
appendLookingFor(lines, q);
|
|
51
|
+
lines.push("");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format scenario-based question sections (capability or behaviour)
|
|
58
|
+
* @param {string[]} lines
|
|
59
|
+
* @param {Array} sections
|
|
60
|
+
* @param {string} heading
|
|
61
|
+
* @param {string} promptsKey - Key for guided prompts
|
|
62
|
+
* @param {string} promptsLabel - Display label for prompts
|
|
63
|
+
*/
|
|
64
|
+
function formatScenarioSections(
|
|
65
|
+
lines,
|
|
66
|
+
sections,
|
|
67
|
+
heading,
|
|
68
|
+
promptsKey,
|
|
69
|
+
promptsLabel,
|
|
70
|
+
) {
|
|
71
|
+
if (sections.length === 0) return;
|
|
72
|
+
lines.push(`## ${heading}`, "");
|
|
73
|
+
for (const section of sections) {
|
|
74
|
+
lines.push(`### ${section.name} (${formatLevel(section.level)})`, "");
|
|
75
|
+
for (const q of section.questions) {
|
|
76
|
+
lines.push(`**Scenario**: ${q.question}`);
|
|
77
|
+
if (q.context) {
|
|
78
|
+
lines.push(`> ${q.context}`);
|
|
79
|
+
}
|
|
80
|
+
if (q[promptsKey] && q[promptsKey].length > 0) {
|
|
81
|
+
lines.push("", `**${promptsLabel}:**`);
|
|
82
|
+
for (const prompt of q[promptsKey]) {
|
|
83
|
+
lines.push(`- ${prompt}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
appendFollowUps(lines, q);
|
|
87
|
+
appendLookingFor(lines, q);
|
|
88
|
+
lines.push("");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
8
93
|
/**
|
|
9
94
|
* Format interview detail as markdown
|
|
10
95
|
* @param {Object} view - Interview detail view from presenter
|
|
@@ -24,102 +109,29 @@ export function interviewToMarkdown(view, { framework } = {}) {
|
|
|
24
109
|
"",
|
|
25
110
|
];
|
|
26
111
|
|
|
27
|
-
// Group sections by type
|
|
28
112
|
const skillSections = view.sections.filter((s) => s.type === "skill");
|
|
29
113
|
const behaviourSections = view.sections.filter((s) => s.type === "behaviour");
|
|
30
114
|
const capabilitySections = view.sections.filter(
|
|
31
115
|
(s) => s.type === "capability",
|
|
32
116
|
);
|
|
33
117
|
|
|
34
|
-
|
|
35
|
-
if (skillSections.length > 0) {
|
|
36
|
-
lines.push(`## ${skillEmoji} Skill Questions`, "");
|
|
37
|
-
for (const section of skillSections) {
|
|
38
|
-
lines.push(`### ${section.name} (${formatLevel(section.level)})`, "");
|
|
39
|
-
for (const q of section.questions) {
|
|
40
|
-
lines.push(`**Q**: ${q.question}`);
|
|
41
|
-
if (q.followUps.length > 0) {
|
|
42
|
-
lines.push("", "**Follow-ups:**");
|
|
43
|
-
for (const followUp of q.followUps) {
|
|
44
|
-
lines.push(` → ${followUp}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (q.lookingFor && q.lookingFor.length > 0) {
|
|
48
|
-
lines.push("", "**What to look for:**");
|
|
49
|
-
for (const item of q.lookingFor) {
|
|
50
|
-
lines.push(`- ${item}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
lines.push("");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
118
|
+
formatSkillSections(lines, skillSections, skillEmoji);
|
|
57
119
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (q.context) {
|
|
66
|
-
lines.push(`> ${q.context}`);
|
|
67
|
-
}
|
|
68
|
-
if (q.decompositionPrompts && q.decompositionPrompts.length > 0) {
|
|
69
|
-
lines.push("", "**Guide the candidate through:**");
|
|
70
|
-
for (const prompt of q.decompositionPrompts) {
|
|
71
|
-
lines.push(`- ${prompt}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (q.followUps.length > 0) {
|
|
75
|
-
lines.push("", "**Follow-ups:**");
|
|
76
|
-
for (const followUp of q.followUps) {
|
|
77
|
-
lines.push(` → ${followUp}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (q.lookingFor && q.lookingFor.length > 0) {
|
|
81
|
-
lines.push("", "**What to look for:**");
|
|
82
|
-
for (const item of q.lookingFor) {
|
|
83
|
-
lines.push(`- ${item}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
lines.push("");
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
120
|
+
formatScenarioSections(
|
|
121
|
+
lines,
|
|
122
|
+
capabilitySections,
|
|
123
|
+
"🧩 Decomposition Questions",
|
|
124
|
+
"decompositionPrompts",
|
|
125
|
+
"Guide the candidate through",
|
|
126
|
+
);
|
|
90
127
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (q.context) {
|
|
99
|
-
lines.push(`> ${q.context}`);
|
|
100
|
-
}
|
|
101
|
-
if (q.simulationPrompts && q.simulationPrompts.length > 0) {
|
|
102
|
-
lines.push("", "**Steer the simulation:**");
|
|
103
|
-
for (const prompt of q.simulationPrompts) {
|
|
104
|
-
lines.push(`- ${prompt}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (q.followUps.length > 0) {
|
|
108
|
-
lines.push("", "**Follow-ups:**");
|
|
109
|
-
for (const followUp of q.followUps) {
|
|
110
|
-
lines.push(` → ${followUp}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (q.lookingFor && q.lookingFor.length > 0) {
|
|
114
|
-
lines.push("", "**What to look for:**");
|
|
115
|
-
for (const item of q.lookingFor) {
|
|
116
|
-
lines.push(`- ${item}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
lines.push("");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
128
|
+
formatScenarioSections(
|
|
129
|
+
lines,
|
|
130
|
+
behaviourSections,
|
|
131
|
+
`${behaviourEmoji} Stakeholder Simulation`,
|
|
132
|
+
"simulationPrompts",
|
|
133
|
+
"Steer the simulation",
|
|
134
|
+
);
|
|
123
135
|
|
|
124
136
|
return lines.join("\n");
|
|
125
137
|
}
|