@forwardimpact/pathway 0.25.15 → 0.25.21
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 +1 -3
- 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/index.js +0 -19
- 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
- package/src/formatters/behaviour/microdata.js +0 -106
- package/src/formatters/discipline/microdata.js +0 -117
- package/src/formatters/driver/microdata.js +0 -91
- package/src/formatters/level/microdata.js +0 -141
- package/src/formatters/microdata-shared.js +0 -184
- package/src/formatters/skill/microdata.js +0 -151
- package/src/formatters/stage/microdata.js +0 -116
- package/src/formatters/track/microdata.js +0 -111
|
@@ -35,14 +35,98 @@ import { createJsonLdScript, levelToJsonLd } from "../json-ld.js";
|
|
|
35
35
|
* @param {boolean} [options.showBackLink=true] - Whether to show back navigation link
|
|
36
36
|
* @returns {HTMLElement}
|
|
37
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* Create a proficiency row for the base skill table
|
|
40
|
+
* @param {string} label
|
|
41
|
+
* @param {string} badgeClass
|
|
42
|
+
* @param {string|undefined} proficiency
|
|
43
|
+
* @returns {HTMLElement}
|
|
44
|
+
*/
|
|
45
|
+
function createProficiencyRow(label, badgeClass, proficiency) {
|
|
46
|
+
return tr(
|
|
47
|
+
{},
|
|
48
|
+
td({}, span({ className: `badge ${badgeClass}` }, label)),
|
|
49
|
+
td(
|
|
50
|
+
{},
|
|
51
|
+
proficiency
|
|
52
|
+
? createLevelDots(
|
|
53
|
+
SKILL_PROFICIENCY_ORDER.indexOf(proficiency),
|
|
54
|
+
SKILL_PROFICIENCY_ORDER.length,
|
|
55
|
+
)
|
|
56
|
+
: span({ className: "text-muted" }, "—"),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create the base skill proficiencies and behaviour maturity section
|
|
63
|
+
* @param {Object} view
|
|
64
|
+
* @returns {HTMLElement}
|
|
65
|
+
*/
|
|
66
|
+
function createBaseProfileSection(view) {
|
|
67
|
+
const profs = view.baseSkillProficiencies || {};
|
|
68
|
+
return div(
|
|
69
|
+
{ className: "detail-section" },
|
|
70
|
+
div(
|
|
71
|
+
{ className: "content-columns" },
|
|
72
|
+
div(
|
|
73
|
+
{ className: "column" },
|
|
74
|
+
heading2({ className: "section-title" }, "Base Skill Proficiencies"),
|
|
75
|
+
table(
|
|
76
|
+
{ className: "level-table" },
|
|
77
|
+
thead({}, tr({}, th({}, "Type"), th({}, "Level"))),
|
|
78
|
+
tbody(
|
|
79
|
+
{},
|
|
80
|
+
createProficiencyRow("Primary", "badge-primary", profs.primary),
|
|
81
|
+
createProficiencyRow(
|
|
82
|
+
"Secondary",
|
|
83
|
+
"badge-secondary",
|
|
84
|
+
profs.secondary,
|
|
85
|
+
),
|
|
86
|
+
createProficiencyRow("Broad", "badge-broad", profs.broad),
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
div(
|
|
91
|
+
{ className: "column" },
|
|
92
|
+
heading2({ className: "section-title" }, "Base Behaviour Maturity"),
|
|
93
|
+
view.baseBehaviourMaturity
|
|
94
|
+
? table(
|
|
95
|
+
{ className: "level-table" },
|
|
96
|
+
thead({}, tr({}, th({}, "Maturity"), th({}, "Level"))),
|
|
97
|
+
tbody(
|
|
98
|
+
{},
|
|
99
|
+
tr(
|
|
100
|
+
{},
|
|
101
|
+
td(
|
|
102
|
+
{},
|
|
103
|
+
view.baseBehaviourMaturity.charAt(0).toUpperCase() +
|
|
104
|
+
view.baseBehaviourMaturity.slice(1),
|
|
105
|
+
),
|
|
106
|
+
td(
|
|
107
|
+
{},
|
|
108
|
+
createLevelDots(
|
|
109
|
+
BEHAVIOUR_MATURITY_ORDER.indexOf(
|
|
110
|
+
view.baseBehaviourMaturity,
|
|
111
|
+
),
|
|
112
|
+
BEHAVIOUR_MATURITY_ORDER.length,
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
: p({ className: "text-muted" }, "—"),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
38
124
|
export function levelToDOM(level, { framework, showBackLink = true } = {}) {
|
|
39
125
|
const view = prepareLevelDetail(level);
|
|
40
126
|
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
41
127
|
return div(
|
|
42
128
|
{ className: "detail-page level-detail" },
|
|
43
|
-
// JSON-LD structured data
|
|
44
129
|
createJsonLdScript(levelToJsonLd(level)),
|
|
45
|
-
// Header
|
|
46
130
|
div(
|
|
47
131
|
{ className: "page-header" },
|
|
48
132
|
showBackLink ? createBackLink("/level", "← Back to Levels") : null,
|
|
@@ -68,7 +152,6 @@ export function levelToDOM(level, { framework, showBackLink = true } = {}) {
|
|
|
68
152
|
: null,
|
|
69
153
|
),
|
|
70
154
|
|
|
71
|
-
// Titles section
|
|
72
155
|
view.professionalTitle || view.managementTitle
|
|
73
156
|
? div(
|
|
74
157
|
{ className: "detail-section" },
|
|
@@ -93,7 +176,6 @@ export function levelToDOM(level, { framework, showBackLink = true } = {}) {
|
|
|
93
176
|
)
|
|
94
177
|
: null,
|
|
95
178
|
|
|
96
|
-
// Expectations
|
|
97
179
|
view.expectations && Object.keys(view.expectations).length > 0
|
|
98
180
|
? div(
|
|
99
181
|
{ className: "detail-section" },
|
|
@@ -111,103 +193,6 @@ export function levelToDOM(level, { framework, showBackLink = true } = {}) {
|
|
|
111
193
|
)
|
|
112
194
|
: null,
|
|
113
195
|
|
|
114
|
-
|
|
115
|
-
div(
|
|
116
|
-
{ className: "detail-section" },
|
|
117
|
-
div(
|
|
118
|
-
{ className: "content-columns" },
|
|
119
|
-
// Base Skill Proficiencies column
|
|
120
|
-
div(
|
|
121
|
-
{ className: "column" },
|
|
122
|
-
heading2({ className: "section-title" }, "Base Skill Proficiencies"),
|
|
123
|
-
table(
|
|
124
|
-
{ className: "level-table" },
|
|
125
|
-
thead({}, tr({}, th({}, "Type"), th({}, "Level"))),
|
|
126
|
-
tbody(
|
|
127
|
-
{},
|
|
128
|
-
tr(
|
|
129
|
-
{},
|
|
130
|
-
td({}, span({ className: "badge badge-primary" }, "Primary")),
|
|
131
|
-
td(
|
|
132
|
-
{},
|
|
133
|
-
view.baseSkillProficiencies?.primary
|
|
134
|
-
? createLevelDots(
|
|
135
|
-
SKILL_PROFICIENCY_ORDER.indexOf(
|
|
136
|
-
view.baseSkillProficiencies.primary,
|
|
137
|
-
),
|
|
138
|
-
SKILL_PROFICIENCY_ORDER.length,
|
|
139
|
-
)
|
|
140
|
-
: span({ className: "text-muted" }, "—"),
|
|
141
|
-
),
|
|
142
|
-
),
|
|
143
|
-
tr(
|
|
144
|
-
{},
|
|
145
|
-
td(
|
|
146
|
-
{},
|
|
147
|
-
span({ className: "badge badge-secondary" }, "Secondary"),
|
|
148
|
-
),
|
|
149
|
-
td(
|
|
150
|
-
{},
|
|
151
|
-
view.baseSkillProficiencies?.secondary
|
|
152
|
-
? createLevelDots(
|
|
153
|
-
SKILL_PROFICIENCY_ORDER.indexOf(
|
|
154
|
-
view.baseSkillProficiencies.secondary,
|
|
155
|
-
),
|
|
156
|
-
SKILL_PROFICIENCY_ORDER.length,
|
|
157
|
-
)
|
|
158
|
-
: span({ className: "text-muted" }, "—"),
|
|
159
|
-
),
|
|
160
|
-
),
|
|
161
|
-
tr(
|
|
162
|
-
{},
|
|
163
|
-
td({}, span({ className: "badge badge-broad" }, "Broad")),
|
|
164
|
-
td(
|
|
165
|
-
{},
|
|
166
|
-
view.baseSkillProficiencies?.broad
|
|
167
|
-
? createLevelDots(
|
|
168
|
-
SKILL_PROFICIENCY_ORDER.indexOf(
|
|
169
|
-
view.baseSkillProficiencies.broad,
|
|
170
|
-
),
|
|
171
|
-
SKILL_PROFICIENCY_ORDER.length,
|
|
172
|
-
)
|
|
173
|
-
: span({ className: "text-muted" }, "—"),
|
|
174
|
-
),
|
|
175
|
-
),
|
|
176
|
-
),
|
|
177
|
-
),
|
|
178
|
-
),
|
|
179
|
-
// Base Behaviour Maturity column
|
|
180
|
-
div(
|
|
181
|
-
{ className: "column" },
|
|
182
|
-
heading2({ className: "section-title" }, "Base Behaviour Maturity"),
|
|
183
|
-
view.baseBehaviourMaturity
|
|
184
|
-
? table(
|
|
185
|
-
{ className: "level-table" },
|
|
186
|
-
thead({}, tr({}, th({}, "Maturity"), th({}, "Level"))),
|
|
187
|
-
tbody(
|
|
188
|
-
{},
|
|
189
|
-
tr(
|
|
190
|
-
{},
|
|
191
|
-
td(
|
|
192
|
-
{},
|
|
193
|
-
view.baseBehaviourMaturity.charAt(0).toUpperCase() +
|
|
194
|
-
view.baseBehaviourMaturity.slice(1),
|
|
195
|
-
),
|
|
196
|
-
td(
|
|
197
|
-
{},
|
|
198
|
-
createLevelDots(
|
|
199
|
-
BEHAVIOUR_MATURITY_ORDER.indexOf(
|
|
200
|
-
view.baseBehaviourMaturity,
|
|
201
|
-
),
|
|
202
|
-
BEHAVIOUR_MATURITY_ORDER.length,
|
|
203
|
-
),
|
|
204
|
-
),
|
|
205
|
-
),
|
|
206
|
-
),
|
|
207
|
-
)
|
|
208
|
-
: p({ className: "text-muted" }, "—"),
|
|
209
|
-
),
|
|
210
|
-
),
|
|
211
|
-
),
|
|
196
|
+
createBaseProfileSection(view),
|
|
212
197
|
);
|
|
213
198
|
}
|
|
@@ -223,10 +223,43 @@ function formatTable(view) {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
* Format single
|
|
227
|
-
* @param {
|
|
228
|
-
* @
|
|
226
|
+
* Format a single question's detail lines
|
|
227
|
+
* @param {string[]} lines
|
|
228
|
+
* @param {Object} q - Flattened question
|
|
229
229
|
*/
|
|
230
|
+
function formatQuestionDetail(lines, q) {
|
|
231
|
+
lines.push(` • [${q.id}] ${q.text}`);
|
|
232
|
+
lines.push(` Duration: ${q.expectedDurationMinutes} min`);
|
|
233
|
+
if (q.context) {
|
|
234
|
+
lines.push(` Context: ${q.context}`);
|
|
235
|
+
}
|
|
236
|
+
if (q.simulationPrompts && q.simulationPrompts.length > 0) {
|
|
237
|
+
lines.push(" Steer the simulation:");
|
|
238
|
+
for (const prompt of q.simulationPrompts) {
|
|
239
|
+
lines.push(` - ${prompt}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (q.decompositionPrompts && q.decompositionPrompts.length > 0) {
|
|
243
|
+
lines.push(" Guide candidate thinking:");
|
|
244
|
+
for (const prompt of q.decompositionPrompts) {
|
|
245
|
+
lines.push(` - ${prompt}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (q.lookingFor.length > 0) {
|
|
249
|
+
lines.push(" Looking for:");
|
|
250
|
+
for (const item of q.lookingFor) {
|
|
251
|
+
lines.push(` - ${item}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (q.followUps.length > 0) {
|
|
255
|
+
lines.push(" Follow-ups:");
|
|
256
|
+
for (const fu of q.followUps) {
|
|
257
|
+
lines.push(` → ${fu}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
lines.push("");
|
|
261
|
+
}
|
|
262
|
+
|
|
230
263
|
function formatSingleSource(view) {
|
|
231
264
|
const lines = [];
|
|
232
265
|
const { questions, stats } = view;
|
|
@@ -259,36 +292,7 @@ function formatSingleSource(view) {
|
|
|
259
292
|
|
|
260
293
|
lines.push(level.toUpperCase());
|
|
261
294
|
for (const q of byLevel[level]) {
|
|
262
|
-
lines
|
|
263
|
-
lines.push(` Duration: ${q.expectedDurationMinutes} min`);
|
|
264
|
-
if (q.context) {
|
|
265
|
-
lines.push(` Context: ${q.context}`);
|
|
266
|
-
}
|
|
267
|
-
if (q.simulationPrompts && q.simulationPrompts.length > 0) {
|
|
268
|
-
lines.push(" Steer the simulation:");
|
|
269
|
-
for (const prompt of q.simulationPrompts) {
|
|
270
|
-
lines.push(` - ${prompt}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (q.decompositionPrompts && q.decompositionPrompts.length > 0) {
|
|
274
|
-
lines.push(" Guide candidate thinking:");
|
|
275
|
-
for (const prompt of q.decompositionPrompts) {
|
|
276
|
-
lines.push(` - ${prompt}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (q.lookingFor.length > 0) {
|
|
280
|
-
lines.push(" Looking for:");
|
|
281
|
-
for (const item of q.lookingFor) {
|
|
282
|
-
lines.push(` - ${item}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (q.followUps.length > 0) {
|
|
286
|
-
lines.push(" Follow-ups:");
|
|
287
|
-
for (const fu of q.followUps) {
|
|
288
|
-
lines.push(` → ${fu}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
lines.push("");
|
|
295
|
+
formatQuestionDetail(lines, q);
|
|
292
296
|
}
|
|
293
297
|
}
|
|
294
298
|
|
|
@@ -111,6 +111,119 @@ function getSkillCapability(skillId, skills) {
|
|
|
111
111
|
*/
|
|
112
112
|
const ROLE_TYPES = ["professionalQuestions", "managementQuestions"];
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Check whether skill questions should be included given the current filter
|
|
116
|
+
* @param {string} skillId
|
|
117
|
+
* @param {Array} skills
|
|
118
|
+
* @param {QuestionsFilter} filter
|
|
119
|
+
* @returns {boolean}
|
|
120
|
+
*/
|
|
121
|
+
function shouldIncludeSkill(skillId, skills, filter) {
|
|
122
|
+
if (filter.skills && !filter.skills.includes(skillId)) return false;
|
|
123
|
+
if (filter.behaviours) return false;
|
|
124
|
+
if (filter.maturity) return false;
|
|
125
|
+
if (filter.capability) {
|
|
126
|
+
const capability = getSkillCapability(skillId, skills);
|
|
127
|
+
if (capability !== filter.capability) return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check whether behaviour questions should be included given the current filter
|
|
134
|
+
* @param {string} behaviourId
|
|
135
|
+
* @param {QuestionsFilter} filter
|
|
136
|
+
* @returns {boolean}
|
|
137
|
+
*/
|
|
138
|
+
function shouldIncludeBehaviour(behaviourId, filter) {
|
|
139
|
+
if (filter.behaviours && !filter.behaviours.includes(behaviourId))
|
|
140
|
+
return false;
|
|
141
|
+
if (filter.capability) return false;
|
|
142
|
+
if (filter.skills) return false;
|
|
143
|
+
if (filter.level) return false;
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Flatten skill questions from a single skill entry
|
|
149
|
+
* @param {string} skillId
|
|
150
|
+
* @param {Object} roleTypes
|
|
151
|
+
* @param {Array} skills
|
|
152
|
+
* @param {QuestionsFilter} filter
|
|
153
|
+
* @param {FlattenedQuestion[]} out
|
|
154
|
+
*/
|
|
155
|
+
function flattenSkillQuestions(skillId, roleTypes, skills, filter, out) {
|
|
156
|
+
const skillName = getSkillName(skillId, skills);
|
|
157
|
+
|
|
158
|
+
for (const roleType of ROLE_TYPES) {
|
|
159
|
+
const levels = roleTypes[roleType];
|
|
160
|
+
if (!levels) continue;
|
|
161
|
+
|
|
162
|
+
for (const [level, levelQuestions] of Object.entries(levels)) {
|
|
163
|
+
if (filter.level && level !== filter.level) continue;
|
|
164
|
+
|
|
165
|
+
for (const q of levelQuestions) {
|
|
166
|
+
out.push({
|
|
167
|
+
source: skillId,
|
|
168
|
+
sourceName: skillName,
|
|
169
|
+
sourceType: "skill",
|
|
170
|
+
level,
|
|
171
|
+
id: q.id,
|
|
172
|
+
text: q.text,
|
|
173
|
+
lookingFor: q.lookingFor || [],
|
|
174
|
+
expectedDurationMinutes: q.expectedDurationMinutes || 5,
|
|
175
|
+
followUps: q.followUps || [],
|
|
176
|
+
context: q.context || null,
|
|
177
|
+
decompositionPrompts: q.decompositionPrompts || [],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Flatten behaviour questions from a single behaviour entry
|
|
186
|
+
* @param {string} behaviourId
|
|
187
|
+
* @param {Object} roleTypes
|
|
188
|
+
* @param {Array} behaviours
|
|
189
|
+
* @param {QuestionsFilter} filter
|
|
190
|
+
* @param {FlattenedQuestion[]} out
|
|
191
|
+
*/
|
|
192
|
+
function flattenBehaviourQuestions(
|
|
193
|
+
behaviourId,
|
|
194
|
+
roleTypes,
|
|
195
|
+
behaviours,
|
|
196
|
+
filter,
|
|
197
|
+
out,
|
|
198
|
+
) {
|
|
199
|
+
const behaviourName = getBehaviourName(behaviourId, behaviours);
|
|
200
|
+
|
|
201
|
+
for (const roleType of ROLE_TYPES) {
|
|
202
|
+
const maturities = roleTypes[roleType];
|
|
203
|
+
if (!maturities) continue;
|
|
204
|
+
|
|
205
|
+
for (const [maturity, maturityQuestions] of Object.entries(maturities)) {
|
|
206
|
+
if (filter.maturity && maturity !== filter.maturity) continue;
|
|
207
|
+
|
|
208
|
+
for (const q of maturityQuestions) {
|
|
209
|
+
out.push({
|
|
210
|
+
source: behaviourId,
|
|
211
|
+
sourceName: behaviourName,
|
|
212
|
+
sourceType: "behaviour",
|
|
213
|
+
level: maturity,
|
|
214
|
+
id: q.id,
|
|
215
|
+
text: q.text,
|
|
216
|
+
lookingFor: q.lookingFor || [],
|
|
217
|
+
expectedDurationMinutes: q.expectedDurationMinutes || 5,
|
|
218
|
+
followUps: q.followUps || [],
|
|
219
|
+
context: q.context || null,
|
|
220
|
+
simulationPrompts: q.simulationPrompts || [],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
114
227
|
/**
|
|
115
228
|
* Flatten all questions from question bank
|
|
116
229
|
* @param {Object} questionBank
|
|
@@ -122,84 +235,43 @@ const ROLE_TYPES = ["professionalQuestions", "managementQuestions"];
|
|
|
122
235
|
export function flattenQuestions(questionBank, skills, behaviours, filter) {
|
|
123
236
|
const questions = [];
|
|
124
237
|
|
|
125
|
-
// Process skill questions
|
|
126
238
|
for (const [skillId, roleTypes] of Object.entries(
|
|
127
239
|
questionBank.skillProficiencies || {},
|
|
128
240
|
)) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (filter.skills && !filter.skills.includes(skillId)) continue;
|
|
133
|
-
if (filter.behaviours) continue;
|
|
134
|
-
if (filter.capability && capability !== filter.capability) continue;
|
|
135
|
-
if (filter.maturity) continue;
|
|
136
|
-
|
|
137
|
-
for (const roleType of ROLE_TYPES) {
|
|
138
|
-
const levels = roleTypes[roleType];
|
|
139
|
-
if (!levels) continue;
|
|
140
|
-
|
|
141
|
-
for (const [level, levelQuestions] of Object.entries(levels)) {
|
|
142
|
-
if (filter.level && level !== filter.level) continue;
|
|
143
|
-
|
|
144
|
-
for (const q of levelQuestions) {
|
|
145
|
-
questions.push({
|
|
146
|
-
source: skillId,
|
|
147
|
-
sourceName: skillName,
|
|
148
|
-
sourceType: "skill",
|
|
149
|
-
level,
|
|
150
|
-
id: q.id,
|
|
151
|
-
text: q.text,
|
|
152
|
-
lookingFor: q.lookingFor || [],
|
|
153
|
-
expectedDurationMinutes: q.expectedDurationMinutes || 5,
|
|
154
|
-
followUps: q.followUps || [],
|
|
155
|
-
context: q.context || null,
|
|
156
|
-
decompositionPrompts: q.decompositionPrompts || [],
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
241
|
+
if (!shouldIncludeSkill(skillId, skills, filter)) continue;
|
|
242
|
+
flattenSkillQuestions(skillId, roleTypes, skills, filter, questions);
|
|
161
243
|
}
|
|
162
244
|
|
|
163
|
-
// Process behaviour questions
|
|
164
245
|
for (const [behaviourId, roleTypes] of Object.entries(
|
|
165
246
|
questionBank.behaviourMaturities || {},
|
|
166
247
|
)) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const maturities = roleTypes[roleType];
|
|
176
|
-
if (!maturities) continue;
|
|
177
|
-
|
|
178
|
-
for (const [maturity, maturityQuestions] of Object.entries(maturities)) {
|
|
179
|
-
if (filter.maturity && maturity !== filter.maturity) continue;
|
|
180
|
-
|
|
181
|
-
for (const q of maturityQuestions) {
|
|
182
|
-
questions.push({
|
|
183
|
-
source: behaviourId,
|
|
184
|
-
sourceName: behaviourName,
|
|
185
|
-
sourceType: "behaviour",
|
|
186
|
-
level: maturity,
|
|
187
|
-
id: q.id,
|
|
188
|
-
text: q.text,
|
|
189
|
-
lookingFor: q.lookingFor || [],
|
|
190
|
-
expectedDurationMinutes: q.expectedDurationMinutes || 5,
|
|
191
|
-
followUps: q.followUps || [],
|
|
192
|
-
context: q.context || null,
|
|
193
|
-
simulationPrompts: q.simulationPrompts || [],
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
248
|
+
if (!shouldIncludeBehaviour(behaviourId, filter)) continue;
|
|
249
|
+
flattenBehaviourQuestions(
|
|
250
|
+
behaviourId,
|
|
251
|
+
roleTypes,
|
|
252
|
+
behaviours,
|
|
253
|
+
filter,
|
|
254
|
+
questions,
|
|
255
|
+
);
|
|
198
256
|
}
|
|
199
257
|
|
|
200
258
|
return questions;
|
|
201
259
|
}
|
|
202
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Count questions across role types for a given level/maturity
|
|
263
|
+
* @param {Object} roleTypes - Role type map
|
|
264
|
+
* @param {string} levelKey - Level or maturity key
|
|
265
|
+
* @returns {number}
|
|
266
|
+
*/
|
|
267
|
+
function countQuestionsForLevel(roleTypes, levelKey) {
|
|
268
|
+
let count = 0;
|
|
269
|
+
for (const roleType of ROLE_TYPES) {
|
|
270
|
+
count += (roleTypes[roleType]?.[levelKey] || []).length;
|
|
271
|
+
}
|
|
272
|
+
return count;
|
|
273
|
+
}
|
|
274
|
+
|
|
203
275
|
/**
|
|
204
276
|
* Calculate question statistics
|
|
205
277
|
* @param {FlattenedQuestion[]} questions
|
|
@@ -222,11 +294,7 @@ export function calculateStats(questions, questionBank) {
|
|
|
222
294
|
)) {
|
|
223
295
|
skillStats[skillId] = {};
|
|
224
296
|
for (const level of SKILL_PROFICIENCIES) {
|
|
225
|
-
|
|
226
|
-
for (const roleType of ROLE_TYPES) {
|
|
227
|
-
count += (roleTypes[roleType]?.[level] || []).length;
|
|
228
|
-
}
|
|
229
|
-
skillStats[skillId][level] = count;
|
|
297
|
+
skillStats[skillId][level] = countQuestionsForLevel(roleTypes, level);
|
|
230
298
|
}
|
|
231
299
|
skillStats[skillId].total = Object.values(skillStats[skillId]).reduce(
|
|
232
300
|
(a, b) => a + b,
|
|
@@ -240,11 +308,10 @@ export function calculateStats(questions, questionBank) {
|
|
|
240
308
|
)) {
|
|
241
309
|
behaviourStats[behaviourId] = {};
|
|
242
310
|
for (const maturity of BEHAVIOUR_MATURITIES) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
behaviourStats[behaviourId][maturity] = count;
|
|
311
|
+
behaviourStats[behaviourId][maturity] = countQuestionsForLevel(
|
|
312
|
+
roleTypes,
|
|
313
|
+
maturity,
|
|
314
|
+
);
|
|
248
315
|
}
|
|
249
316
|
behaviourStats[behaviourId].total = Object.values(
|
|
250
317
|
behaviourStats[behaviourId],
|