@forwardimpact/pathway 0.12.0 → 0.14.0
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 +32 -12
- package/package.json +3 -3
- package/src/commands/build.js +98 -2
- package/src/commands/index.js +1 -0
- package/src/commands/interview.js +52 -14
- package/src/commands/job.js +1 -0
- package/src/commands/questions.js +13 -10
- package/src/commands/stage.js +8 -8
- package/src/commands/update.js +133 -0
- package/src/components/command-prompt.js +85 -0
- package/src/components/nav.js +2 -2
- package/src/components/top-bar.js +97 -0
- package/src/css/bundles/app.css +2 -0
- package/src/css/components/badges.css +41 -11
- package/src/css/components/command-prompt.css +98 -0
- package/src/css/components/layout.css +0 -3
- package/src/css/components/nav.css +121 -81
- package/src/css/components/surfaces.css +1 -1
- package/src/css/components/top-bar.css +180 -0
- package/src/css/pages/agent-builder.css +0 -9
- package/src/css/pages/landing.css +4 -0
- package/src/css/pages/lifecycle.css +5 -2
- package/src/css/reset.css +1 -1
- package/src/css/tokens.css +25 -11
- package/src/css/views/slide-base.css +2 -1
- package/src/formatters/agent/dom.js +0 -26
- package/src/formatters/agent/profile.js +13 -7
- package/src/formatters/agent/skill.js +4 -4
- package/src/formatters/interview/markdown.js +62 -3
- package/src/formatters/interview/shared.js +89 -52
- package/src/formatters/questions/markdown.js +15 -0
- package/src/formatters/questions/shared.js +70 -58
- package/src/formatters/stage/dom.js +13 -10
- package/src/formatters/stage/microdata.js +14 -8
- package/src/formatters/stage/shared.js +4 -4
- package/src/index.html +69 -44
- package/src/lib/cli-command.js +145 -0
- package/src/lib/state.js +2 -0
- package/src/lib/yaml-loader.js +39 -21
- package/src/main.js +47 -26
- package/src/pages/agent-builder.js +0 -28
- package/src/pages/behaviour.js +3 -1
- package/src/pages/discipline.js +3 -1
- package/src/pages/driver.js +6 -1
- package/src/pages/grade.js +6 -1
- package/src/pages/interview.js +61 -5
- package/src/pages/job.js +1 -0
- package/src/pages/landing.js +7 -0
- package/src/pages/skill.js +9 -2
- package/src/pages/track.js +6 -1
- package/src/slides/job.js +1 -0
- package/templates/agent.template.md +17 -10
- package/templates/install.template.sh +33 -0
- package/templates/skill.template.md +15 -7
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
getDisciplineSkillIds,
|
|
11
11
|
} from "@forwardimpact/model/derivation";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
deriveMissionFitInterview,
|
|
14
|
+
deriveDecompositionInterview,
|
|
15
|
+
deriveStakeholderInterview,
|
|
16
16
|
} from "@forwardimpact/model/interview";
|
|
17
17
|
import { getOrCreateJob } from "@forwardimpact/model/job-cache";
|
|
18
18
|
|
|
@@ -20,26 +20,35 @@ import { getOrCreateJob } from "@forwardimpact/model/job-cache";
|
|
|
20
20
|
* Interview type configurations
|
|
21
21
|
*/
|
|
22
22
|
export const INTERVIEW_TYPES = {
|
|
23
|
-
|
|
24
|
-
id: "
|
|
25
|
-
name: "
|
|
26
|
-
description:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
behaviour: {
|
|
31
|
-
id: "behaviour",
|
|
32
|
-
name: "Behavioural",
|
|
33
|
-
description: "Focus on behaviours and mindsets",
|
|
34
|
-
icon: "🧠",
|
|
23
|
+
mission: {
|
|
24
|
+
id: "mission",
|
|
25
|
+
name: "Mission Fit",
|
|
26
|
+
description:
|
|
27
|
+
"Assess technical skills and alignment with role expectations. Focus on depth of knowledge, problem-solving approach, and ability to articulate technical decisions.",
|
|
28
|
+
icon: "🎯",
|
|
35
29
|
expectedDurationMinutes: 45,
|
|
30
|
+
panel: "Recruiting Manager + 1 Senior Engineer",
|
|
31
|
+
questionTypes: ["skill"],
|
|
32
|
+
},
|
|
33
|
+
decomposition: {
|
|
34
|
+
id: "decomposition",
|
|
35
|
+
name: "Decomposition",
|
|
36
|
+
description:
|
|
37
|
+
"Evaluate how candidates break down ambiguous problems into actionable components. Inspired by Palantir's technique—focus on structured thinking, trade-off analysis, and communication under uncertainty.",
|
|
38
|
+
icon: "🧩",
|
|
39
|
+
expectedDurationMinutes: 60,
|
|
40
|
+
panel: "2 Senior Engineers",
|
|
41
|
+
questionTypes: ["capability"],
|
|
36
42
|
},
|
|
37
|
-
|
|
38
|
-
id: "
|
|
39
|
-
name: "
|
|
40
|
-
description:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
stakeholder: {
|
|
44
|
+
id: "stakeholder",
|
|
45
|
+
name: "Stakeholder Simulation",
|
|
46
|
+
description:
|
|
47
|
+
"Simulate real-world stakeholder interactions through behaviour-focused assessment. Focus on communication style, influence, and ability to navigate competing priorities.",
|
|
48
|
+
icon: "👥",
|
|
49
|
+
expectedDurationMinutes: 60,
|
|
50
|
+
panel: "3-4 Stakeholders",
|
|
51
|
+
questionTypes: ["behaviour"],
|
|
43
52
|
},
|
|
44
53
|
};
|
|
45
54
|
|
|
@@ -67,14 +76,27 @@ function groupQuestionsIntoSections(questions) {
|
|
|
67
76
|
};
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
79
|
+
const questionEntry = {
|
|
71
80
|
skillOrBehaviourId: id,
|
|
72
81
|
skillOrBehaviourName: name,
|
|
73
82
|
type,
|
|
74
83
|
level,
|
|
75
84
|
question: q.question.text,
|
|
76
85
|
followUps: q.question.followUps || [],
|
|
77
|
-
|
|
86
|
+
lookingFor: q.question.lookingFor || [],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (q.question.decompositionPrompts) {
|
|
90
|
+
questionEntry.decompositionPrompts = q.question.decompositionPrompts;
|
|
91
|
+
}
|
|
92
|
+
if (q.question.simulationPrompts) {
|
|
93
|
+
questionEntry.simulationPrompts = q.question.simulationPrompts;
|
|
94
|
+
}
|
|
95
|
+
if (q.question.context) {
|
|
96
|
+
questionEntry.context = q.question.context;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
sections[id].questions.push(questionEntry);
|
|
78
100
|
}
|
|
79
101
|
|
|
80
102
|
return Object.values(sections);
|
|
@@ -83,7 +105,7 @@ function groupQuestionsIntoSections(questions) {
|
|
|
83
105
|
/**
|
|
84
106
|
* @typedef {Object} InterviewDetailView
|
|
85
107
|
* @property {string} title
|
|
86
|
-
* @property {string} interviewType - '
|
|
108
|
+
* @property {string} interviewType - 'mission', 'decomposition', or 'stakeholder'
|
|
87
109
|
* @property {string} disciplineId
|
|
88
110
|
* @property {string} disciplineName
|
|
89
111
|
* @property {string} gradeId
|
|
@@ -104,7 +126,7 @@ function groupQuestionsIntoSections(questions) {
|
|
|
104
126
|
* @param {Array} params.skills
|
|
105
127
|
* @param {Array} params.behaviours
|
|
106
128
|
* @param {Array} params.questions
|
|
107
|
-
* @param {string} [params.interviewType='
|
|
129
|
+
* @param {string} [params.interviewType='mission']
|
|
108
130
|
* @returns {InterviewDetailView|null}
|
|
109
131
|
*/
|
|
110
132
|
export function prepareInterviewDetail({
|
|
@@ -114,7 +136,7 @@ export function prepareInterviewDetail({
|
|
|
114
136
|
skills,
|
|
115
137
|
behaviours,
|
|
116
138
|
questions,
|
|
117
|
-
interviewType = "
|
|
139
|
+
interviewType = "mission",
|
|
118
140
|
}) {
|
|
119
141
|
if (!discipline || !grade) return null;
|
|
120
142
|
|
|
@@ -130,18 +152,26 @@ export function prepareInterviewDetail({
|
|
|
130
152
|
|
|
131
153
|
let interviewGuide;
|
|
132
154
|
switch (interviewType) {
|
|
133
|
-
case "
|
|
134
|
-
interviewGuide =
|
|
155
|
+
case "mission":
|
|
156
|
+
interviewGuide = deriveMissionFitInterview({
|
|
157
|
+
job,
|
|
158
|
+
questionBank: questions,
|
|
159
|
+
});
|
|
135
160
|
break;
|
|
136
|
-
case "
|
|
137
|
-
interviewGuide =
|
|
161
|
+
case "decomposition":
|
|
162
|
+
interviewGuide = deriveDecompositionInterview({
|
|
163
|
+
job,
|
|
164
|
+
questionBank: questions,
|
|
165
|
+
});
|
|
166
|
+
break;
|
|
167
|
+
case "stakeholder":
|
|
168
|
+
interviewGuide = deriveStakeholderInterview({
|
|
138
169
|
job,
|
|
139
170
|
questionBank: questions,
|
|
140
171
|
});
|
|
141
172
|
break;
|
|
142
|
-
case "full":
|
|
143
173
|
default:
|
|
144
|
-
interviewGuide =
|
|
174
|
+
interviewGuide = deriveMissionFitInterview({
|
|
145
175
|
job,
|
|
146
176
|
questionBank: questions,
|
|
147
177
|
});
|
|
@@ -151,19 +181,27 @@ export function prepareInterviewDetail({
|
|
|
151
181
|
// Extract the questions array from the interview guide
|
|
152
182
|
const rawQuestions = interviewGuide.questions || [];
|
|
153
183
|
|
|
154
|
-
// Separate
|
|
184
|
+
// Separate questions by type
|
|
155
185
|
const skillQuestions = rawQuestions.filter((q) => q.targetType === "skill");
|
|
156
186
|
const behaviourQuestions = rawQuestions.filter(
|
|
157
187
|
(q) => q.targetType === "behaviour",
|
|
158
188
|
);
|
|
189
|
+
const capabilityQuestions = rawQuestions.filter(
|
|
190
|
+
(q) => q.targetType === "capability",
|
|
191
|
+
);
|
|
159
192
|
|
|
160
193
|
const skillSections = groupQuestionsIntoSections(skillQuestions);
|
|
161
194
|
const behaviourSections = groupQuestionsIntoSections(behaviourQuestions);
|
|
195
|
+
const capabilitySections = groupQuestionsIntoSections(capabilityQuestions);
|
|
162
196
|
|
|
163
|
-
const allSections = [
|
|
197
|
+
const allSections = [
|
|
198
|
+
...skillSections,
|
|
199
|
+
...behaviourSections,
|
|
200
|
+
...capabilitySections,
|
|
201
|
+
];
|
|
164
202
|
const totalQuestions = rawQuestions.length;
|
|
165
203
|
|
|
166
|
-
const typeConfig = INTERVIEW_TYPES[interviewType] || INTERVIEW_TYPES.
|
|
204
|
+
const typeConfig = INTERVIEW_TYPES[interviewType] || INTERVIEW_TYPES.mission;
|
|
167
205
|
|
|
168
206
|
return {
|
|
169
207
|
title: job.title,
|
|
@@ -293,24 +331,19 @@ export function prepareAllInterviews({
|
|
|
293
331
|
if (!job) return null;
|
|
294
332
|
|
|
295
333
|
// Generate all interview types
|
|
296
|
-
const
|
|
334
|
+
const missionInterview = deriveMissionFitInterview({
|
|
297
335
|
job,
|
|
298
336
|
questionBank: questions,
|
|
299
|
-
targetMinutes: 20,
|
|
300
337
|
});
|
|
301
338
|
|
|
302
|
-
const
|
|
339
|
+
const decompositionInterview = deriveDecompositionInterview({
|
|
303
340
|
job,
|
|
304
341
|
questionBank: questions,
|
|
305
342
|
});
|
|
306
343
|
|
|
307
|
-
const
|
|
344
|
+
const stakeholderInterview = deriveStakeholderInterview({
|
|
308
345
|
job,
|
|
309
346
|
questionBank: questions,
|
|
310
|
-
options: {
|
|
311
|
-
targetMinutes: 60,
|
|
312
|
-
skillBehaviourRatio: 0.6,
|
|
313
|
-
},
|
|
314
347
|
});
|
|
315
348
|
|
|
316
349
|
return {
|
|
@@ -321,17 +354,21 @@ export function prepareAllInterviews({
|
|
|
321
354
|
trackId: track?.id || null,
|
|
322
355
|
trackName: track?.name || null,
|
|
323
356
|
interviews: {
|
|
324
|
-
|
|
325
|
-
...
|
|
326
|
-
type: "
|
|
327
|
-
typeInfo: INTERVIEW_TYPES.
|
|
357
|
+
mission: {
|
|
358
|
+
...missionInterview,
|
|
359
|
+
type: "mission",
|
|
360
|
+
typeInfo: INTERVIEW_TYPES.mission,
|
|
361
|
+
},
|
|
362
|
+
decomposition: {
|
|
363
|
+
...decompositionInterview,
|
|
364
|
+
type: "decomposition",
|
|
365
|
+
typeInfo: INTERVIEW_TYPES.decomposition,
|
|
328
366
|
},
|
|
329
|
-
|
|
330
|
-
...
|
|
331
|
-
type: "
|
|
332
|
-
typeInfo: INTERVIEW_TYPES.
|
|
367
|
+
stakeholder: {
|
|
368
|
+
...stakeholderInterview,
|
|
369
|
+
type: "stakeholder",
|
|
370
|
+
typeInfo: INTERVIEW_TYPES.stakeholder,
|
|
333
371
|
},
|
|
334
|
-
full: { ...fullInterview, type: "full", typeInfo: INTERVIEW_TYPES.full },
|
|
335
372
|
},
|
|
336
373
|
};
|
|
337
374
|
}
|
|
@@ -259,6 +259,21 @@ function formatSingleSource(view) {
|
|
|
259
259
|
for (const q of byLevel[level]) {
|
|
260
260
|
lines.push(` • [${q.id}] ${q.text}`);
|
|
261
261
|
lines.push(` Duration: ${q.expectedDurationMinutes} min`);
|
|
262
|
+
if (q.context) {
|
|
263
|
+
lines.push(` Context: ${q.context}`);
|
|
264
|
+
}
|
|
265
|
+
if (q.simulationPrompts && q.simulationPrompts.length > 0) {
|
|
266
|
+
lines.push(" Steer the simulation:");
|
|
267
|
+
for (const prompt of q.simulationPrompts) {
|
|
268
|
+
lines.push(` - ${prompt}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (q.decompositionPrompts && q.decompositionPrompts.length > 0) {
|
|
272
|
+
lines.push(" Guide candidate thinking:");
|
|
273
|
+
for (const prompt of q.decompositionPrompts) {
|
|
274
|
+
lines.push(` - ${prompt}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
262
277
|
if (q.lookingFor.length > 0) {
|
|
263
278
|
lines.push(" Looking for:");
|
|
264
279
|
for (const item of q.lookingFor) {
|
|
@@ -46,6 +46,9 @@ export const BEHAVIOUR_MATURITIES = [
|
|
|
46
46
|
* @property {string[]} lookingFor - Expected answer indicators
|
|
47
47
|
* @property {number} expectedDurationMinutes - Time estimate
|
|
48
48
|
* @property {string[]} [followUps] - Follow-up questions
|
|
49
|
+
* @property {string} [context] - Scenario context
|
|
50
|
+
* @property {string[]} [simulationPrompts] - Simulation steering prompts
|
|
51
|
+
* @property {string[]} [decompositionPrompts] - Decomposition guidance prompts
|
|
49
52
|
*/
|
|
50
53
|
|
|
51
54
|
/**
|
|
@@ -103,6 +106,11 @@ function getSkillCapability(skillId, skills) {
|
|
|
103
106
|
return skill ? skill.capability : null;
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Role type keys in question YAML files
|
|
111
|
+
*/
|
|
112
|
+
const ROLE_TYPES = ["professionalQuestions", "managementQuestions"];
|
|
113
|
+
|
|
106
114
|
/**
|
|
107
115
|
* Flatten all questions from question bank
|
|
108
116
|
* @param {Object} questionBank
|
|
@@ -115,78 +123,76 @@ export function flattenQuestions(questionBank, skills, behaviours, filter) {
|
|
|
115
123
|
const questions = [];
|
|
116
124
|
|
|
117
125
|
// Process skill questions
|
|
118
|
-
for (const [skillId,
|
|
126
|
+
for (const [skillId, roleTypes] of Object.entries(
|
|
119
127
|
questionBank.skillLevels || {},
|
|
120
128
|
)) {
|
|
121
129
|
const skillName = getSkillName(skillId, skills);
|
|
122
130
|
const capability = getSkillCapability(skillId, skills);
|
|
123
131
|
|
|
124
|
-
// Filter by skill IDs
|
|
125
132
|
if (filter.skills && !filter.skills.includes(skillId)) continue;
|
|
126
|
-
|
|
127
|
-
// Skip skills if filtering by specific behaviours
|
|
128
133
|
if (filter.behaviours) continue;
|
|
129
|
-
|
|
130
|
-
// Filter by capability
|
|
131
134
|
if (filter.capability && capability !== filter.capability) continue;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
|
|
156
163
|
// Process behaviour questions
|
|
157
|
-
for (const [behaviourId,
|
|
164
|
+
for (const [behaviourId, roleTypes] of Object.entries(
|
|
158
165
|
questionBank.behaviourMaturities || {},
|
|
159
166
|
)) {
|
|
160
167
|
const behaviourName = getBehaviourName(behaviourId, behaviours);
|
|
161
168
|
|
|
162
|
-
// Filter by behaviour IDs
|
|
163
169
|
if (filter.behaviours && !filter.behaviours.includes(behaviourId)) continue;
|
|
164
|
-
|
|
165
|
-
// Skip behaviours if filtering by capability (skill-only filter)
|
|
166
170
|
if (filter.capability) continue;
|
|
167
|
-
|
|
168
|
-
// Skip behaviours if filtering by specific skills
|
|
169
171
|
if (filter.skills) continue;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
172
|
+
if (filter.level) continue;
|
|
173
|
+
|
|
174
|
+
for (const roleType of ROLE_TYPES) {
|
|
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
|
+
}
|
|
190
196
|
}
|
|
191
197
|
}
|
|
192
198
|
}
|
|
@@ -211,12 +217,16 @@ export function calculateStats(questions, questionBank) {
|
|
|
211
217
|
|
|
212
218
|
// Calculate full stats for skills and behaviours
|
|
213
219
|
const skillStats = {};
|
|
214
|
-
for (const [skillId,
|
|
220
|
+
for (const [skillId, roleTypes] of Object.entries(
|
|
215
221
|
questionBank.skillLevels || {},
|
|
216
222
|
)) {
|
|
217
223
|
skillStats[skillId] = {};
|
|
218
224
|
for (const level of SKILL_LEVELS) {
|
|
219
|
-
|
|
225
|
+
let count = 0;
|
|
226
|
+
for (const roleType of ROLE_TYPES) {
|
|
227
|
+
count += (roleTypes[roleType]?.[level] || []).length;
|
|
228
|
+
}
|
|
229
|
+
skillStats[skillId][level] = count;
|
|
220
230
|
}
|
|
221
231
|
skillStats[skillId].total = Object.values(skillStats[skillId]).reduce(
|
|
222
232
|
(a, b) => a + b,
|
|
@@ -225,14 +235,16 @@ export function calculateStats(questions, questionBank) {
|
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
const behaviourStats = {};
|
|
228
|
-
for (const [behaviourId,
|
|
238
|
+
for (const [behaviourId, roleTypes] of Object.entries(
|
|
229
239
|
questionBank.behaviourMaturities || {},
|
|
230
240
|
)) {
|
|
231
241
|
behaviourStats[behaviourId] = {};
|
|
232
242
|
for (const maturity of BEHAVIOUR_MATURITIES) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
243
|
+
let count = 0;
|
|
244
|
+
for (const roleType of ROLE_TYPES) {
|
|
245
|
+
count += (roleTypes[roleType]?.[maturity] || []).length;
|
|
246
|
+
}
|
|
247
|
+
behaviourStats[behaviourId][maturity] = count;
|
|
236
248
|
}
|
|
237
249
|
behaviourStats[behaviourId].total = Object.values(
|
|
238
250
|
behaviourStats[behaviourId],
|
|
@@ -34,33 +34,36 @@ export function stageToDOM(stage, { stages = [], showBackLink = true } = {}) {
|
|
|
34
34
|
p({ className: "page-description" }, view.description),
|
|
35
35
|
),
|
|
36
36
|
|
|
37
|
-
//
|
|
38
|
-
view.
|
|
37
|
+
// Read/Confirm Checklists
|
|
38
|
+
view.readChecklist.length > 0 || view.confirmChecklist.length > 0
|
|
39
39
|
? div(
|
|
40
40
|
{ className: "section section-detail" },
|
|
41
41
|
div(
|
|
42
42
|
{ className: "content-columns" },
|
|
43
|
-
//
|
|
44
|
-
view.
|
|
43
|
+
// Read checklist column
|
|
44
|
+
view.readChecklist.length > 0
|
|
45
45
|
? div(
|
|
46
46
|
{ className: "column" },
|
|
47
|
-
h2({ className: "section-title" }, "
|
|
47
|
+
h2({ className: "section-title" }, "Read-Then-Do Checklist"),
|
|
48
48
|
ul(
|
|
49
49
|
{ className: "criteria-list" },
|
|
50
|
-
...view.
|
|
50
|
+
...view.readChecklist.map((item) =>
|
|
51
51
|
li({ className: "criteria-item" }, item),
|
|
52
52
|
),
|
|
53
53
|
),
|
|
54
54
|
)
|
|
55
55
|
: null,
|
|
56
|
-
//
|
|
57
|
-
view.
|
|
56
|
+
// Confirm checklist column
|
|
57
|
+
view.confirmChecklist.length > 0
|
|
58
58
|
? div(
|
|
59
59
|
{ className: "column" },
|
|
60
|
-
h2(
|
|
60
|
+
h2(
|
|
61
|
+
{ className: "section-title" },
|
|
62
|
+
"Do-Then-Confirm Checklist",
|
|
63
|
+
),
|
|
61
64
|
ul(
|
|
62
65
|
{ className: "criteria-list" },
|
|
63
|
-
...view.
|
|
66
|
+
...view.confirmChecklist.map((item) =>
|
|
64
67
|
li({ className: "criteria-item" }, item),
|
|
65
68
|
),
|
|
66
69
|
),
|
|
@@ -60,11 +60,11 @@ export function stageToMicrodata(stage) {
|
|
|
60
60
|
|
|
61
61
|
const sections = [];
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
if (view.
|
|
65
|
-
const
|
|
63
|
+
// Read checklist
|
|
64
|
+
if (view.readChecklist.length > 0) {
|
|
65
|
+
const readItems = view.readChecklist.map((c) => escapeHtml(c));
|
|
66
66
|
sections.push(
|
|
67
|
-
section("
|
|
67
|
+
section("Read-Then-Do Checklist", ul(readItems, "readChecklist"), 2),
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -76,10 +76,16 @@ export function stageToMicrodata(stage) {
|
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
//
|
|
80
|
-
if (view.
|
|
81
|
-
const
|
|
82
|
-
sections.push(
|
|
79
|
+
// Confirm checklist
|
|
80
|
+
if (view.confirmChecklist.length > 0) {
|
|
81
|
+
const confirmItems = view.confirmChecklist.map((c) => escapeHtml(c));
|
|
82
|
+
sections.push(
|
|
83
|
+
section(
|
|
84
|
+
"Do-Then-Confirm Checklist",
|
|
85
|
+
ul(confirmItems, "confirmChecklist"),
|
|
86
|
+
2,
|
|
87
|
+
),
|
|
88
|
+
);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
// Handoffs - using Handoff itemtype
|
|
@@ -46,8 +46,8 @@ export function prepareStagesList(stages, descriptionLimit = 150) {
|
|
|
46
46
|
* @property {string} name
|
|
47
47
|
* @property {string} description
|
|
48
48
|
* @property {string[]} constraints
|
|
49
|
-
* @property {string[]}
|
|
50
|
-
* @property {string[]}
|
|
49
|
+
* @property {string[]} readChecklist
|
|
50
|
+
* @property {string[]} confirmChecklist
|
|
51
51
|
* @property {Array<{target: string, label: string, prompt: string}>} handoffs
|
|
52
52
|
*/
|
|
53
53
|
|
|
@@ -62,8 +62,8 @@ export function prepareStageDetail(stage) {
|
|
|
62
62
|
name: stage.name,
|
|
63
63
|
description: stage.description,
|
|
64
64
|
constraints: stage.constraints || [],
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
readChecklist: stage.readChecklist || [],
|
|
66
|
+
confirmChecklist: stage.confirmChecklist || [],
|
|
67
67
|
handoffs: (stage.handoffs || []).map((h) => ({
|
|
68
68
|
target: h.targetStage,
|
|
69
69
|
label: h.label,
|