@forwardimpact/pathway 0.12.0 → 0.13.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 +6 -5
- package/package.json +3 -3
- package/src/commands/interview.js +52 -14
- package/src/commands/questions.js +13 -10
- package/src/css/components/badges.css +41 -11
- package/src/css/tokens.css +21 -9
- 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/lib/yaml-loader.js +39 -21
- package/src/pages/interview.js +61 -5
package/bin/fit-pathway.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* stage [<id>] Show stages
|
|
19
19
|
* tool [<name>] Show tools
|
|
20
20
|
* job [<discipline> <grade>] [--track=TRACK] Generate job definition
|
|
21
|
-
* interview <discipline> <grade> [--track=TRACK] [--type=
|
|
21
|
+
* interview <discipline> <grade> [--track=TRACK] [--type=mission|decomposition|stakeholder] Generate interview
|
|
22
22
|
* progress <discipline> <grade> [--track=TRACK] [--compare=GRADE] Career progression
|
|
23
23
|
* questions [options] Browse interview questions
|
|
24
24
|
* agent [<discipline> <track>] [--output=PATH] Generate AI agent
|
|
@@ -167,13 +167,14 @@ INTERVIEW COMMAND
|
|
|
167
167
|
Generate interview question sets based on job requirements.
|
|
168
168
|
|
|
169
169
|
Usage:
|
|
170
|
-
npx fit-pathway interview <discipline> <grade>
|
|
171
|
-
npx fit-pathway interview <d> <g> --track=<track>
|
|
172
|
-
npx fit-pathway interview <d> <g> --type=<type>
|
|
170
|
+
npx fit-pathway interview <discipline> <grade> All types
|
|
171
|
+
npx fit-pathway interview <d> <g> --track=<track> With track
|
|
172
|
+
npx fit-pathway interview <d> <g> --track=<t> --type=<type> Single type
|
|
173
173
|
|
|
174
174
|
Options:
|
|
175
175
|
--track=TRACK Track specialization
|
|
176
|
-
--type=TYPE Interview type:
|
|
176
|
+
--type=TYPE Interview type: mission, decomposition, stakeholder
|
|
177
|
+
(omit for all types)
|
|
177
178
|
|
|
178
179
|
────────────────────────────────────────────────────────────────────────────────
|
|
179
180
|
PROGRESS COMMAND
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/pathway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Career progression web app and CLI for exploring roles and generating agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"./commands": "./src/commands/index.js"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@forwardimpact/schema": "^0.
|
|
44
|
-
"@forwardimpact/model": "^0.
|
|
43
|
+
"@forwardimpact/schema": "^0.6.0",
|
|
44
|
+
"@forwardimpact/model": "^0.7.0",
|
|
45
45
|
"mustache": "^4.2.0",
|
|
46
46
|
"simple-icons": "^16.7.0",
|
|
47
47
|
"yaml": "^2.3.4"
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Generates and displays interview questions in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx pathway interview <discipline> <grade>
|
|
8
|
-
* npx pathway interview <discipline> <grade> --track=<track>
|
|
9
|
-
* npx pathway interview <discipline> <grade> --track=<track> --type=
|
|
7
|
+
* npx fit-pathway interview <discipline> <grade> # All interview types
|
|
8
|
+
* npx fit-pathway interview <discipline> <grade> --track=<track> # With track
|
|
9
|
+
* npx fit-pathway interview <discipline> <grade> --track=<track> --type=mission # Single type
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
} from "../formatters/interview/shared.js";
|
|
17
17
|
import { interviewToMarkdown } from "../formatters/interview/markdown.js";
|
|
18
18
|
|
|
19
|
+
const VALID_TYPES = Object.keys(INTERVIEW_TYPES);
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
|
-
* Format interview
|
|
22
|
+
* Format a single interview type as markdown
|
|
21
23
|
* @param {Object} view - Presenter view
|
|
22
24
|
* @param {Object} options - Options including framework
|
|
23
25
|
*/
|
|
@@ -25,15 +27,31 @@ function formatInterview(view, options) {
|
|
|
25
27
|
console.log(interviewToMarkdown(view, { framework: options.framework }));
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Format all interview types as markdown with separators
|
|
32
|
+
* @param {Array<Object>} views - Array of presenter views
|
|
33
|
+
* @param {Object} options - Options including framework
|
|
34
|
+
*/
|
|
35
|
+
function formatAllInterviews(views, options) {
|
|
36
|
+
for (let i = 0; i < views.length; i++) {
|
|
37
|
+
if (i > 0) {
|
|
38
|
+
console.log("\n" + "─".repeat(80) + "\n");
|
|
39
|
+
}
|
|
40
|
+
console.log(
|
|
41
|
+
interviewToMarkdown(views[i], { framework: options.framework }),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
export const runInterviewCommand = createCompositeCommand({
|
|
29
47
|
commandName: "interview",
|
|
30
48
|
requiredArgs: ["discipline_id", "grade_id"],
|
|
31
49
|
findEntities: (data, args, options) => {
|
|
32
|
-
const interviewType = options.type
|
|
50
|
+
const interviewType = options.type === "full" ? null : options.type;
|
|
33
51
|
|
|
34
|
-
if (!INTERVIEW_TYPES[interviewType]) {
|
|
52
|
+
if (interviewType && !INTERVIEW_TYPES[interviewType]) {
|
|
35
53
|
console.error(`Unknown interview type: ${interviewType}`);
|
|
36
|
-
console.error(
|
|
54
|
+
console.error(`Available types: ${VALID_TYPES.join(", ")}`);
|
|
37
55
|
process.exit(1);
|
|
38
56
|
}
|
|
39
57
|
|
|
@@ -58,18 +76,38 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
58
76
|
}
|
|
59
77
|
return null;
|
|
60
78
|
},
|
|
61
|
-
presenter: (entities, data, _options) =>
|
|
62
|
-
|
|
79
|
+
presenter: (entities, data, _options) => {
|
|
80
|
+
const params = {
|
|
63
81
|
discipline: entities.discipline,
|
|
64
82
|
grade: entities.grade,
|
|
65
83
|
track: entities.track,
|
|
66
84
|
skills: data.skills,
|
|
67
85
|
behaviours: data.behaviours,
|
|
68
86
|
questions: data.questions,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Single type: return one view
|
|
90
|
+
if (entities.interviewType) {
|
|
91
|
+
return prepareInterviewDetail({
|
|
92
|
+
...params,
|
|
93
|
+
interviewType: entities.interviewType,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// All types: return array of views
|
|
98
|
+
return VALID_TYPES.map((type) =>
|
|
99
|
+
prepareInterviewDetail({ ...params, interviewType: type }),
|
|
100
|
+
).filter(Boolean);
|
|
101
|
+
},
|
|
102
|
+
formatter: (view, options, data) => {
|
|
103
|
+
const opts = { ...options, framework: data.framework };
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(view)) {
|
|
106
|
+
formatAllInterviews(view, opts);
|
|
107
|
+
} else {
|
|
108
|
+
formatInterview(view, opts);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
73
111
|
usageExample:
|
|
74
|
-
"npx pathway interview software_engineering
|
|
112
|
+
"npx fit-pathway interview software_engineering J090 --track=platform --type=mission",
|
|
75
113
|
});
|
|
@@ -56,12 +56,15 @@ function showQuestionsSummary(data) {
|
|
|
56
56
|
"practitioner",
|
|
57
57
|
"expert",
|
|
58
58
|
];
|
|
59
|
+
const roleTypes = ["professionalQuestions", "managementQuestions"];
|
|
59
60
|
const skillRows = skillLevels.map((level) => {
|
|
60
61
|
let count = 0;
|
|
61
62
|
for (const skill of skills) {
|
|
62
|
-
const sq = questions.
|
|
63
|
-
if (sq
|
|
64
|
-
|
|
63
|
+
const sq = questions.skillLevels?.[skill.id];
|
|
64
|
+
if (sq) {
|
|
65
|
+
for (const roleType of roleTypes) {
|
|
66
|
+
count += (sq[roleType]?.[level] || []).length;
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
return [level, count];
|
|
@@ -81,9 +84,11 @@ function showQuestionsSummary(data) {
|
|
|
81
84
|
const behaviourRows = maturities.map((maturity) => {
|
|
82
85
|
let count = 0;
|
|
83
86
|
for (const behaviour of behaviours) {
|
|
84
|
-
const bq = questions.
|
|
85
|
-
if (bq
|
|
86
|
-
|
|
87
|
+
const bq = questions.behaviourMaturities?.[behaviour.id];
|
|
88
|
+
if (bq) {
|
|
89
|
+
for (const roleType of roleTypes) {
|
|
90
|
+
count += (bq[roleType]?.[maturity] || []).length;
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
return [maturity.replace(/_/g, " "), count];
|
|
@@ -138,10 +143,8 @@ export async function runQuestionsCommand({
|
|
|
138
143
|
behaviours: data.behaviours,
|
|
139
144
|
filter,
|
|
140
145
|
});
|
|
141
|
-
for (const
|
|
142
|
-
|
|
143
|
-
console.log(q.id);
|
|
144
|
-
}
|
|
146
|
+
for (const q of view.questions) {
|
|
147
|
+
console.log(q.id);
|
|
145
148
|
}
|
|
146
149
|
return;
|
|
147
150
|
}
|
|
@@ -94,24 +94,34 @@
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/* Capability badges - each capability ID maps to its own distinct color */
|
|
97
|
+
.badge-ai {
|
|
98
|
+
background: var(--color-cap-ai-light);
|
|
99
|
+
color: var(--color-cap-ai);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.badge-business {
|
|
103
|
+
background: var(--color-cap-business-light);
|
|
104
|
+
color: var(--color-cap-business);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.badge-data {
|
|
108
|
+
background: var(--color-cap-data-light);
|
|
109
|
+
color: var(--color-cap-data);
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
.badge-delivery {
|
|
98
113
|
background: var(--color-cap-delivery-light);
|
|
99
114
|
color: var(--color-cap-delivery);
|
|
100
115
|
}
|
|
101
116
|
|
|
102
|
-
.badge-
|
|
103
|
-
background: var(--color-cap-
|
|
104
|
-
color: var(--color-cap-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.badge-reliability {
|
|
108
|
-
background: var(--color-cap-reliability-light);
|
|
109
|
-
color: var(--color-cap-reliability);
|
|
117
|
+
.badge-documentation {
|
|
118
|
+
background: var(--color-cap-documentation-light);
|
|
119
|
+
color: var(--color-cap-documentation);
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
.badge-
|
|
113
|
-
background: var(--color-cap-
|
|
114
|
-
color: var(--color-cap-
|
|
122
|
+
.badge-ml {
|
|
123
|
+
background: var(--color-cap-ml-light);
|
|
124
|
+
color: var(--color-cap-ml);
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
.badge-people {
|
|
@@ -119,6 +129,26 @@
|
|
|
119
129
|
color: var(--color-cap-people);
|
|
120
130
|
}
|
|
121
131
|
|
|
132
|
+
.badge-process {
|
|
133
|
+
background: var(--color-cap-process-light);
|
|
134
|
+
color: var(--color-cap-process);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.badge-product {
|
|
138
|
+
background: var(--color-cap-product-light);
|
|
139
|
+
color: var(--color-cap-product);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.badge-reliability {
|
|
143
|
+
background: var(--color-cap-reliability-light);
|
|
144
|
+
color: var(--color-cap-reliability);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.badge-scale {
|
|
148
|
+
background: var(--color-cap-scale-light);
|
|
149
|
+
color: var(--color-cap-scale);
|
|
150
|
+
}
|
|
151
|
+
|
|
122
152
|
/* Tool badge */
|
|
123
153
|
.badge-tool {
|
|
124
154
|
background: var(--color-bg);
|
package/src/css/tokens.css
CHANGED
|
@@ -43,22 +43,34 @@
|
|
|
43
43
|
--color-maturity-5: #c4b5fd;
|
|
44
44
|
|
|
45
45
|
/* --------------------------------------------------------------------------
|
|
46
|
-
Colors - Capabilities (text)
|
|
46
|
+
Colors - Capabilities (text) - matches data/capabilities/*.yaml IDs
|
|
47
47
|
-------------------------------------------------------------------------- */
|
|
48
|
-
--color-cap-
|
|
49
|
-
--color-cap-
|
|
50
|
-
--color-cap-
|
|
51
|
-
--color-cap-
|
|
52
|
-
--color-cap-
|
|
48
|
+
--color-cap-ai: #7c3aed; /* violet */
|
|
49
|
+
--color-cap-business: #b45309; /* amber */
|
|
50
|
+
--color-cap-data: #0f766e; /* teal */
|
|
51
|
+
--color-cap-delivery: #dc2626; /* red */
|
|
52
|
+
--color-cap-documentation: #15803d; /* green */
|
|
53
|
+
--color-cap-ml: #9333ea; /* purple */
|
|
54
|
+
--color-cap-people: #be185d; /* pink */
|
|
55
|
+
--color-cap-process: #c2410c; /* orange */
|
|
56
|
+
--color-cap-product: #4f46e5; /* indigo */
|
|
57
|
+
--color-cap-reliability: #0891b2; /* cyan */
|
|
58
|
+
--color-cap-scale: #1d4ed8; /* blue */
|
|
53
59
|
|
|
54
60
|
/* --------------------------------------------------------------------------
|
|
55
61
|
Colors - Capabilities (backgrounds)
|
|
56
62
|
-------------------------------------------------------------------------- */
|
|
57
|
-
--color-cap-
|
|
58
|
-
--color-cap-scale-light: #dbeafe; /* blue */
|
|
59
|
-
--color-cap-reliability-light: #cffafe; /* cyan */
|
|
63
|
+
--color-cap-ai-light: #ede9fe; /* violet */
|
|
60
64
|
--color-cap-business-light: #fef3c7; /* amber */
|
|
65
|
+
--color-cap-data-light: #ccfbf1; /* teal */
|
|
66
|
+
--color-cap-delivery-light: #fecaca; /* red */
|
|
67
|
+
--color-cap-documentation-light: #dcfce7; /* green */
|
|
68
|
+
--color-cap-ml-light: #f3e8ff; /* purple */
|
|
61
69
|
--color-cap-people-light: #fce7f3; /* pink */
|
|
70
|
+
--color-cap-process-light: #ffedd5; /* orange */
|
|
71
|
+
--color-cap-product-light: #e0e7ff; /* indigo */
|
|
72
|
+
--color-cap-reliability-light: #cffafe; /* cyan */
|
|
73
|
+
--color-cap-scale-light: #dbeafe; /* blue */
|
|
62
74
|
|
|
63
75
|
/* --------------------------------------------------------------------------
|
|
64
76
|
Colors - Neutrals
|
|
@@ -27,6 +27,9 @@ export function interviewToMarkdown(view, { framework } = {}) {
|
|
|
27
27
|
// Group sections by type
|
|
28
28
|
const skillSections = view.sections.filter((s) => s.type === "skill");
|
|
29
29
|
const behaviourSections = view.sections.filter((s) => s.type === "behaviour");
|
|
30
|
+
const capabilitySections = view.sections.filter(
|
|
31
|
+
(s) => s.type === "capability",
|
|
32
|
+
);
|
|
30
33
|
|
|
31
34
|
// Skill questions
|
|
32
35
|
if (skillSections.length > 0) {
|
|
@@ -36,27 +39,83 @@ export function interviewToMarkdown(view, { framework } = {}) {
|
|
|
36
39
|
for (const q of section.questions) {
|
|
37
40
|
lines.push(`**Q**: ${q.question}`);
|
|
38
41
|
if (q.followUps.length > 0) {
|
|
42
|
+
lines.push("", "**Follow-ups:**");
|
|
39
43
|
for (const followUp of q.followUps) {
|
|
40
44
|
lines.push(` → ${followUp}`);
|
|
41
45
|
}
|
|
42
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
|
+
}
|
|
43
53
|
lines.push("");
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
//
|
|
58
|
+
// Capability decomposition questions
|
|
59
|
+
if (capabilitySections.length > 0) {
|
|
60
|
+
lines.push(`## 🧩 Decomposition Questions`, "");
|
|
61
|
+
for (const section of capabilitySections) {
|
|
62
|
+
lines.push(`### ${section.name} (${formatLevel(section.level)})`, "");
|
|
63
|
+
for (const q of section.questions) {
|
|
64
|
+
lines.push(`**Scenario**: ${q.question}`);
|
|
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
|
+
}
|
|
90
|
+
|
|
91
|
+
// Behaviour stakeholder simulation questions
|
|
49
92
|
if (behaviourSections.length > 0) {
|
|
50
|
-
lines.push(`## ${behaviourEmoji}
|
|
93
|
+
lines.push(`## ${behaviourEmoji} Stakeholder Simulation`, "");
|
|
51
94
|
for (const section of behaviourSections) {
|
|
52
95
|
lines.push(`### ${section.name} (${formatLevel(section.level)})`, "");
|
|
53
96
|
for (const q of section.questions) {
|
|
54
|
-
lines.push(`**
|
|
97
|
+
lines.push(`**Scenario**: ${q.question}`);
|
|
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
|
+
}
|
|
55
107
|
if (q.followUps.length > 0) {
|
|
108
|
+
lines.push("", "**Follow-ups:**");
|
|
56
109
|
for (const followUp of q.followUps) {
|
|
57
110
|
lines.push(` → ${followUp}`);
|
|
58
111
|
}
|
|
59
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
|
+
}
|
|
60
119
|
lines.push("");
|
|
61
120
|
}
|
|
62
121
|
}
|
|
@@ -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],
|
package/src/lib/yaml-loader.js
CHANGED
|
@@ -237,35 +237,52 @@ async function loadCapabilitiesFromDir(capabilitiesDir) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/**
|
|
240
|
-
* Load questions from folder structure using skill/behaviour IDs
|
|
240
|
+
* Load questions from folder structure using skill/behaviour/capability IDs
|
|
241
241
|
* @param {string} questionsDir - Path to questions directory
|
|
242
242
|
* @param {Array} skills - Skills array (with id property)
|
|
243
243
|
* @param {Array} behaviours - Behaviours array (with id property)
|
|
244
|
+
* @param {Array} capabilities - Capabilities array (with id property)
|
|
244
245
|
* @returns {Promise<Object>}
|
|
245
246
|
*/
|
|
246
|
-
async function loadQuestionFolder(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
247
|
+
async function loadQuestionFolder(
|
|
248
|
+
questionsDir,
|
|
249
|
+
skills,
|
|
250
|
+
behaviours,
|
|
251
|
+
capabilities,
|
|
252
|
+
) {
|
|
253
|
+
const [skillEntries, behaviourEntries, capabilityEntries] = await Promise.all(
|
|
254
|
+
[
|
|
255
|
+
Promise.all(
|
|
256
|
+
skills.map(async (skill) => {
|
|
257
|
+
const content = await tryLoadYamlFile(
|
|
258
|
+
`${questionsDir}/skills/${skill.id}.yaml`,
|
|
259
|
+
);
|
|
260
|
+
return [skill.id, content || {}];
|
|
261
|
+
}),
|
|
262
|
+
),
|
|
263
|
+
Promise.all(
|
|
264
|
+
behaviours.map(async (behaviour) => {
|
|
265
|
+
const content = await tryLoadYamlFile(
|
|
266
|
+
`${questionsDir}/behaviours/${behaviour.id}.yaml`,
|
|
267
|
+
);
|
|
268
|
+
return [behaviour.id, content || {}];
|
|
269
|
+
}),
|
|
270
|
+
),
|
|
271
|
+
Promise.all(
|
|
272
|
+
capabilities.map(async (capability) => {
|
|
273
|
+
const content = await tryLoadYamlFile(
|
|
274
|
+
`${questionsDir}/capabilities/${capability.id}.yaml`,
|
|
275
|
+
);
|
|
276
|
+
return [capability.id, content || {}];
|
|
277
|
+
}),
|
|
278
|
+
),
|
|
279
|
+
],
|
|
280
|
+
);
|
|
265
281
|
|
|
266
282
|
return {
|
|
267
283
|
skillLevels: Object.fromEntries(skillEntries),
|
|
268
284
|
behaviourMaturities: Object.fromEntries(behaviourEntries),
|
|
285
|
+
capabilityLevels: Object.fromEntries(capabilityEntries),
|
|
269
286
|
};
|
|
270
287
|
}
|
|
271
288
|
|
|
@@ -293,11 +310,12 @@ export async function loadAllData(dataDir = "./data") {
|
|
|
293
310
|
loadYamlFile(`${dataDir}/framework.yaml`),
|
|
294
311
|
]);
|
|
295
312
|
|
|
296
|
-
// Load questions using skill/behaviour IDs
|
|
313
|
+
// Load questions using skill/behaviour/capability IDs
|
|
297
314
|
const questions = await loadQuestionFolder(
|
|
298
315
|
`${dataDir}/questions`,
|
|
299
316
|
skills,
|
|
300
317
|
behaviours,
|
|
318
|
+
capabilities,
|
|
301
319
|
);
|
|
302
320
|
|
|
303
321
|
return {
|
package/src/pages/interview.js
CHANGED
|
@@ -81,8 +81,8 @@ export function renderInterviewDetail(params) {
|
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// State for current interview type (default to first:
|
|
85
|
-
let currentType = "
|
|
84
|
+
// State for current interview type (default to first: Mission Fit)
|
|
85
|
+
let currentType = "mission";
|
|
86
86
|
|
|
87
87
|
const page = div(
|
|
88
88
|
{ className: "interview-detail-page" },
|
|
@@ -194,23 +194,32 @@ function createInterviewSummary(interview) {
|
|
|
194
194
|
{ className: "interview-summary-header" },
|
|
195
195
|
h2({}, `${typeInfo.icon} ${typeInfo.name}`),
|
|
196
196
|
p({ className: "text-muted" }, typeInfo.description),
|
|
197
|
+
typeInfo.panel
|
|
198
|
+
? p({ className: "text-muted" }, `Panel: ${typeInfo.panel}`)
|
|
199
|
+
: null,
|
|
197
200
|
),
|
|
198
201
|
div(
|
|
199
202
|
{ className: "interview-summary-stats" },
|
|
200
203
|
createBadge(`${interview.questions.length} questions`, "default"),
|
|
201
204
|
createBadge(`~${interview.expectedDurationMinutes} minutes`, "secondary"),
|
|
202
|
-
interview.coverage.skills
|
|
205
|
+
interview.coverage.skills?.length > 0
|
|
203
206
|
? createBadge(
|
|
204
207
|
`${interview.coverage.skills.length} skills covered`,
|
|
205
208
|
"primary",
|
|
206
209
|
)
|
|
207
210
|
: null,
|
|
208
|
-
interview.coverage.behaviours
|
|
211
|
+
interview.coverage.behaviours?.length > 0
|
|
209
212
|
? createBadge(
|
|
210
213
|
`${interview.coverage.behaviours.length} behaviours covered`,
|
|
211
214
|
"primary",
|
|
212
215
|
)
|
|
213
216
|
: null,
|
|
217
|
+
interview.coverage.capabilities?.length > 0
|
|
218
|
+
? createBadge(
|
|
219
|
+
`${interview.coverage.capabilities.length} capabilities covered`,
|
|
220
|
+
"primary",
|
|
221
|
+
)
|
|
222
|
+
: null,
|
|
214
223
|
),
|
|
215
224
|
);
|
|
216
225
|
}
|
|
@@ -226,6 +235,9 @@ function createQuestionsDisplay(interview, framework) {
|
|
|
226
235
|
const behaviourQuestions = interview.questions.filter(
|
|
227
236
|
(q) => q.targetType === "behaviour",
|
|
228
237
|
);
|
|
238
|
+
const capabilityQuestions = interview.questions.filter(
|
|
239
|
+
(q) => q.targetType === "capability",
|
|
240
|
+
);
|
|
229
241
|
|
|
230
242
|
const sections = [];
|
|
231
243
|
|
|
@@ -241,12 +253,21 @@ function createQuestionsDisplay(interview, framework) {
|
|
|
241
253
|
if (behaviourQuestions.length > 0) {
|
|
242
254
|
sections.push(
|
|
243
255
|
createDetailSection({
|
|
244
|
-
title: `${getConceptEmoji(framework, "behaviour")}
|
|
256
|
+
title: `${getConceptEmoji(framework, "behaviour")} Stakeholder Simulation (${behaviourQuestions.length})`,
|
|
245
257
|
content: createQuestionsList(behaviourQuestions),
|
|
246
258
|
}),
|
|
247
259
|
);
|
|
248
260
|
}
|
|
249
261
|
|
|
262
|
+
if (capabilityQuestions.length > 0) {
|
|
263
|
+
sections.push(
|
|
264
|
+
createDetailSection({
|
|
265
|
+
title: `${getConceptEmoji(framework, "capability") || "🧩"} Decomposition Questions (${capabilityQuestions.length})`,
|
|
266
|
+
content: createQuestionsList(capabilityQuestions),
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
250
271
|
if (sections.length === 0) {
|
|
251
272
|
return div(
|
|
252
273
|
{ className: "card" },
|
|
@@ -262,6 +283,7 @@ function createQuestionsDisplay(interview, framework) {
|
|
|
262
283
|
|
|
263
284
|
/**
|
|
264
285
|
* Create questions list
|
|
286
|
+
* @param {Array} questions - Questions to display
|
|
265
287
|
*/
|
|
266
288
|
function createQuestionsList(questions) {
|
|
267
289
|
return div(
|
|
@@ -272,10 +294,41 @@ function createQuestionsList(questions) {
|
|
|
272
294
|
|
|
273
295
|
/**
|
|
274
296
|
* Create question card
|
|
297
|
+
* @param {Object} questionEntry - Question entry
|
|
298
|
+
* @param {number} number - Question number
|
|
275
299
|
*/
|
|
276
300
|
function createQuestionCard(questionEntry, number) {
|
|
277
301
|
const { question, targetName, targetLevel } = questionEntry;
|
|
278
302
|
|
|
303
|
+
const contextSection = question.context
|
|
304
|
+
? div(
|
|
305
|
+
{ className: "question-context" },
|
|
306
|
+
h4({}, "Context:"),
|
|
307
|
+
p({}, question.context),
|
|
308
|
+
)
|
|
309
|
+
: null;
|
|
310
|
+
|
|
311
|
+
const decompositionPromptsList =
|
|
312
|
+
question.decompositionPrompts && question.decompositionPrompts.length > 0
|
|
313
|
+
? div(
|
|
314
|
+
{ className: "question-prompts" },
|
|
315
|
+
h4({}, "Guide candidate thinking:"),
|
|
316
|
+
ul(
|
|
317
|
+
{},
|
|
318
|
+
...question.decompositionPrompts.map((prompt) => li({}, prompt)),
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
: null;
|
|
322
|
+
|
|
323
|
+
const simulationPromptsList =
|
|
324
|
+
question.simulationPrompts && question.simulationPrompts.length > 0
|
|
325
|
+
? div(
|
|
326
|
+
{ className: "question-prompts" },
|
|
327
|
+
h4({}, "Steer the simulation:"),
|
|
328
|
+
ul({}, ...question.simulationPrompts.map((prompt) => li({}, prompt))),
|
|
329
|
+
)
|
|
330
|
+
: null;
|
|
331
|
+
|
|
279
332
|
const followUpsList =
|
|
280
333
|
question.followUps && question.followUps.length > 0
|
|
281
334
|
? div(
|
|
@@ -309,6 +362,9 @@ function createQuestionCard(questionEntry, number) {
|
|
|
309
362
|
),
|
|
310
363
|
),
|
|
311
364
|
div({ className: "question-text" }, question.text),
|
|
365
|
+
contextSection,
|
|
366
|
+
decompositionPromptsList,
|
|
367
|
+
simulationPromptsList,
|
|
312
368
|
followUpsList,
|
|
313
369
|
lookingForList,
|
|
314
370
|
);
|