@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
|
@@ -13,6 +13,80 @@ import Mustache from "mustache";
|
|
|
13
13
|
import { BEHAVIOUR_MATURITY_ORDER } from "@forwardimpact/map/levels";
|
|
14
14
|
import { trimValue, trimFields } from "../shared.js";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Build expectations paragraph from job expectations
|
|
18
|
+
* @param {Object|undefined} expectations
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
function buildExpectationsParagraph(expectations) {
|
|
22
|
+
if (!expectations) return "";
|
|
23
|
+
const exp = expectations;
|
|
24
|
+
const sentences = [];
|
|
25
|
+
|
|
26
|
+
if (exp.impactScope) {
|
|
27
|
+
sentences.push(`This role encompasses ${exp.impactScope.toLowerCase()}.`);
|
|
28
|
+
}
|
|
29
|
+
if (exp.autonomyExpectation) {
|
|
30
|
+
let autonomySentence = `You will ${exp.autonomyExpectation.toLowerCase()}`;
|
|
31
|
+
if (exp.influenceScope) {
|
|
32
|
+
autonomySentence +=
|
|
33
|
+
`, ${exp.influenceScope.toLowerCase()}` +
|
|
34
|
+
(exp.influenceScope.endsWith(".") ? "" : ".");
|
|
35
|
+
} else {
|
|
36
|
+
autonomySentence += exp.autonomyExpectation.endsWith(".") ? "" : ".";
|
|
37
|
+
}
|
|
38
|
+
sentences.push(autonomySentence);
|
|
39
|
+
} else if (exp.influenceScope) {
|
|
40
|
+
sentences.push(
|
|
41
|
+
exp.influenceScope + (exp.influenceScope.endsWith(".") ? "" : "."),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (exp.complexityHandled) {
|
|
45
|
+
sentences.push(`You will handle ${exp.complexityHandled.toLowerCase()}.`);
|
|
46
|
+
}
|
|
47
|
+
return sentences.length > 0 ? sentences.join(" ") : "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build capability skill sections at the highest proficiency
|
|
52
|
+
* @param {Object} job
|
|
53
|
+
* @returns {Array}
|
|
54
|
+
*/
|
|
55
|
+
function buildCapabilitySkills(job) {
|
|
56
|
+
const derivedResponsibilities = job.derivedResponsibilities || [];
|
|
57
|
+
if (derivedResponsibilities.length === 0) return [];
|
|
58
|
+
|
|
59
|
+
const highestProficiency = derivedResponsibilities[0].proficiency;
|
|
60
|
+
const topResponsibilities = derivedResponsibilities.filter(
|
|
61
|
+
(r) => r.proficiency === highestProficiency,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const skillsByCapability = {};
|
|
65
|
+
for (const skill of job.skillMatrix) {
|
|
66
|
+
if (skill.proficiency !== highestProficiency) continue;
|
|
67
|
+
if (!skillsByCapability[skill.capability]) {
|
|
68
|
+
skillsByCapability[skill.capability] = [];
|
|
69
|
+
}
|
|
70
|
+
skillsByCapability[skill.capability].push(skill);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return topResponsibilities
|
|
74
|
+
.filter((r) => skillsByCapability[r.capability]?.length > 0)
|
|
75
|
+
.map((r) => {
|
|
76
|
+
const skills = [...skillsByCapability[r.capability]].sort((a, b) =>
|
|
77
|
+
(a.skillName || "").localeCompare(b.skillName || ""),
|
|
78
|
+
);
|
|
79
|
+
return {
|
|
80
|
+
capabilityHeading: r.capabilityName.toUpperCase(),
|
|
81
|
+
responsibilityDescription: r.responsibility,
|
|
82
|
+
skills: skills.map((s) => ({
|
|
83
|
+
skillName: s.skillName,
|
|
84
|
+
proficiencyDescription: s.proficiencyDescription || "",
|
|
85
|
+
})),
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
16
90
|
/**
|
|
17
91
|
* Prepare job data for template rendering
|
|
18
92
|
* @param {Object} params
|
|
@@ -29,42 +103,7 @@ function prepareJobDescriptionData({ job, discipline, level, track }) {
|
|
|
29
103
|
roleSummary = roleSummary.replace(/\{roleTitle\}/g, roleTitle);
|
|
30
104
|
roleSummary = roleSummary.replace(/\{specialization\}/g, specialization);
|
|
31
105
|
|
|
32
|
-
|
|
33
|
-
let expectationsParagraph = "";
|
|
34
|
-
if (job.expectations) {
|
|
35
|
-
const exp = job.expectations;
|
|
36
|
-
const expectationSentences = [];
|
|
37
|
-
|
|
38
|
-
if (exp.impactScope) {
|
|
39
|
-
expectationSentences.push(
|
|
40
|
-
`This role encompasses ${exp.impactScope.toLowerCase()}.`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
if (exp.autonomyExpectation) {
|
|
44
|
-
let autonomySentence = `You will ${exp.autonomyExpectation.toLowerCase()}`;
|
|
45
|
-
if (exp.influenceScope) {
|
|
46
|
-
autonomySentence +=
|
|
47
|
-
`, ${exp.influenceScope.toLowerCase()}` +
|
|
48
|
-
(exp.influenceScope.endsWith(".") ? "" : ".");
|
|
49
|
-
} else {
|
|
50
|
-
autonomySentence += exp.autonomyExpectation.endsWith(".") ? "" : ".";
|
|
51
|
-
}
|
|
52
|
-
expectationSentences.push(autonomySentence);
|
|
53
|
-
} else if (exp.influenceScope) {
|
|
54
|
-
expectationSentences.push(
|
|
55
|
-
exp.influenceScope + (exp.influenceScope.endsWith(".") ? "" : "."),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
if (exp.complexityHandled) {
|
|
59
|
-
expectationSentences.push(
|
|
60
|
-
`You will handle ${exp.complexityHandled.toLowerCase()}.`,
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (expectationSentences.length > 0) {
|
|
65
|
-
expectationsParagraph = expectationSentences.join(" ");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
106
|
+
const expectationsParagraph = buildExpectationsParagraph(job.expectations);
|
|
68
107
|
|
|
69
108
|
// Sort behaviours by maturity level (highest first)
|
|
70
109
|
const sortedBehaviours = [...job.behaviourProfile].sort((a, b) => {
|
|
@@ -76,45 +115,7 @@ function prepareJobDescriptionData({ job, discipline, level, track }) {
|
|
|
76
115
|
return indexB - indexA;
|
|
77
116
|
});
|
|
78
117
|
|
|
79
|
-
|
|
80
|
-
let capabilitySkills = [];
|
|
81
|
-
const derivedResponsibilities = job.derivedResponsibilities || [];
|
|
82
|
-
if (derivedResponsibilities.length > 0) {
|
|
83
|
-
// derivedResponsibilities is sorted: highest proficiency first, then by skill count
|
|
84
|
-
const highestProficiency = derivedResponsibilities[0].proficiency;
|
|
85
|
-
|
|
86
|
-
// Filter responsibilities to only the highest proficiency
|
|
87
|
-
const topResponsibilities = derivedResponsibilities.filter(
|
|
88
|
-
(r) => r.proficiency === highestProficiency,
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Group skill matrix entries by capability at the highest level
|
|
92
|
-
const skillsByCapability = {};
|
|
93
|
-
for (const skill of job.skillMatrix) {
|
|
94
|
-
if (skill.proficiency !== highestProficiency) continue;
|
|
95
|
-
if (!skillsByCapability[skill.capability]) {
|
|
96
|
-
skillsByCapability[skill.capability] = [];
|
|
97
|
-
}
|
|
98
|
-
skillsByCapability[skill.capability].push(skill);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Build capability sections in skill count order
|
|
102
|
-
capabilitySkills = topResponsibilities
|
|
103
|
-
.filter((r) => skillsByCapability[r.capability]?.length > 0)
|
|
104
|
-
.map((r) => {
|
|
105
|
-
const skills = [...skillsByCapability[r.capability]].sort((a, b) =>
|
|
106
|
-
(a.skillName || "").localeCompare(b.skillName || ""),
|
|
107
|
-
);
|
|
108
|
-
return {
|
|
109
|
-
capabilityHeading: r.capabilityName.toUpperCase(),
|
|
110
|
-
responsibilityDescription: r.responsibility,
|
|
111
|
-
skills: skills.map((s) => ({
|
|
112
|
-
skillName: s.skillName,
|
|
113
|
-
proficiencyDescription: s.proficiencyDescription || "",
|
|
114
|
-
})),
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
+
const capabilitySkills = buildCapabilitySkills(job);
|
|
118
119
|
|
|
119
120
|
// Build qualification summary with placeholder replacement
|
|
120
121
|
const qualificationSummary =
|
|
@@ -33,6 +33,107 @@ import { createToolkitTable } from "../toolkit/dom.js";
|
|
|
33
33
|
* @param {string} [options.jobTemplate] - Mustache template for job description
|
|
34
34
|
* @returns {HTMLElement}
|
|
35
35
|
*/
|
|
36
|
+
/**
|
|
37
|
+
* Build a job data object from a view for template rendering
|
|
38
|
+
* @param {Object} view
|
|
39
|
+
* @returns {Object}
|
|
40
|
+
*/
|
|
41
|
+
function buildJobFromView(view) {
|
|
42
|
+
return {
|
|
43
|
+
title: view.title,
|
|
44
|
+
skillMatrix: view.skillMatrix,
|
|
45
|
+
behaviourProfile: view.behaviourProfile,
|
|
46
|
+
expectations: view.expectations,
|
|
47
|
+
derivedResponsibilities: view.derivedResponsibilities,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create the tables section for a job detail view
|
|
53
|
+
* @param {Object} view
|
|
54
|
+
* @returns {HTMLElement}
|
|
55
|
+
*/
|
|
56
|
+
function createJobTablesSection(view) {
|
|
57
|
+
return div(
|
|
58
|
+
{ className: "job-tables-section" },
|
|
59
|
+
createDetailSection({
|
|
60
|
+
title: "Behaviour Profile",
|
|
61
|
+
content: createBehaviourProfile(view.behaviourProfile),
|
|
62
|
+
}),
|
|
63
|
+
createDetailSection({
|
|
64
|
+
title: "Skill Matrix",
|
|
65
|
+
content: createSkillMatrix(view.skillMatrix, {
|
|
66
|
+
capabilityOrder: view.capabilityOrder,
|
|
67
|
+
}),
|
|
68
|
+
}),
|
|
69
|
+
view.toolkit && view.toolkit.length > 0
|
|
70
|
+
? createDetailSection({
|
|
71
|
+
title: "Tool Kit",
|
|
72
|
+
content: createToolkitTable(view.toolkit),
|
|
73
|
+
})
|
|
74
|
+
: null,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create the page header with breadcrumb links
|
|
80
|
+
* @param {Object} view
|
|
81
|
+
* @param {boolean} showBackLink
|
|
82
|
+
* @returns {HTMLElement}
|
|
83
|
+
*/
|
|
84
|
+
function createJobHeader(view, showBackLink) {
|
|
85
|
+
return div(
|
|
86
|
+
{ className: "page-header" },
|
|
87
|
+
showBackLink
|
|
88
|
+
? createBackLink("/job-builder", "← Back to Job Builder")
|
|
89
|
+
: null,
|
|
90
|
+
h1({ className: "page-title" }, view.title),
|
|
91
|
+
div(
|
|
92
|
+
{ className: "page-description" },
|
|
93
|
+
"Generated from: ",
|
|
94
|
+
a({ href: `#/discipline/${view.disciplineId}` }, view.disciplineName),
|
|
95
|
+
" × ",
|
|
96
|
+
a({ href: `#/level/${view.levelId}` }, view.levelId),
|
|
97
|
+
" × ",
|
|
98
|
+
a({ href: `#/track/${view.trackId}` }, view.trackName),
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create the expectations section if expectations exist
|
|
105
|
+
* @param {Object} view
|
|
106
|
+
* @returns {HTMLElement|null}
|
|
107
|
+
*/
|
|
108
|
+
function createExpectationsSection(view) {
|
|
109
|
+
if (!view.expectations || Object.keys(view.expectations).length === 0) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return createDetailSection({
|
|
113
|
+
title: "Expectations",
|
|
114
|
+
content: createExpectationsCard(view.expectations),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create the radar charts section
|
|
120
|
+
* @param {Object} view
|
|
121
|
+
* @returns {HTMLElement}
|
|
122
|
+
*/
|
|
123
|
+
function createRadarSection(view) {
|
|
124
|
+
return div(
|
|
125
|
+
{ className: "section auto-grid-lg" },
|
|
126
|
+
createBehaviourRadar(view.behaviourProfile, {
|
|
127
|
+
title: "Behaviours Radar",
|
|
128
|
+
size: 420,
|
|
129
|
+
}),
|
|
130
|
+
createSkillRadar(view.skillMatrix, {
|
|
131
|
+
title: "Skills Radar",
|
|
132
|
+
size: 420,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
36
137
|
export function jobToDOM(view, options = {}) {
|
|
37
138
|
const {
|
|
38
139
|
showBackLink = true,
|
|
@@ -46,107 +147,22 @@ export function jobToDOM(view, options = {}) {
|
|
|
46
147
|
} = options;
|
|
47
148
|
|
|
48
149
|
const hasEntities = discipline && level && jobTemplate;
|
|
150
|
+
const job = hasEntities ? buildJobFromView(view) : null;
|
|
151
|
+
const descParams = hasEntities
|
|
152
|
+
? { job, discipline, level, track, template: jobTemplate }
|
|
153
|
+
: null;
|
|
49
154
|
|
|
50
155
|
return div(
|
|
51
156
|
{ className: "job-detail-page" },
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
: null,
|
|
58
|
-
h1({ className: "page-title" }, view.title),
|
|
59
|
-
div(
|
|
60
|
-
{ className: "page-description" },
|
|
61
|
-
"Generated from: ",
|
|
62
|
-
a({ href: `#/discipline/${view.disciplineId}` }, view.disciplineName),
|
|
63
|
-
" × ",
|
|
64
|
-
a({ href: `#/level/${view.levelId}` }, view.levelId),
|
|
65
|
-
" × ",
|
|
66
|
-
a({ href: `#/track/${view.trackId}` }, view.trackName),
|
|
67
|
-
),
|
|
68
|
-
),
|
|
69
|
-
|
|
70
|
-
// Expectations card
|
|
71
|
-
view.expectations && Object.keys(view.expectations).length > 0
|
|
72
|
-
? createDetailSection({
|
|
73
|
-
title: "Expectations",
|
|
74
|
-
content: createExpectationsCard(view.expectations),
|
|
75
|
-
})
|
|
157
|
+
createJobHeader(view, showBackLink),
|
|
158
|
+
createExpectationsSection(view),
|
|
159
|
+
createRadarSection(view),
|
|
160
|
+
showJobDescriptionHtml && descParams
|
|
161
|
+
? createJobDescriptionHtml(descParams)
|
|
76
162
|
: null,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{ className: "section auto-grid-lg" },
|
|
81
|
-
createBehaviourRadar(view.behaviourProfile, {
|
|
82
|
-
title: "Behaviours Radar",
|
|
83
|
-
size: 420,
|
|
84
|
-
}),
|
|
85
|
-
createSkillRadar(view.skillMatrix, {
|
|
86
|
-
title: "Skills Radar",
|
|
87
|
-
size: 420,
|
|
88
|
-
}),
|
|
89
|
-
),
|
|
90
|
-
|
|
91
|
-
// Job Description HTML (for print view)
|
|
92
|
-
showJobDescriptionHtml && hasEntities
|
|
93
|
-
? createJobDescriptionHtml({
|
|
94
|
-
job: {
|
|
95
|
-
title: view.title,
|
|
96
|
-
skillMatrix: view.skillMatrix,
|
|
97
|
-
behaviourProfile: view.behaviourProfile,
|
|
98
|
-
expectations: view.expectations,
|
|
99
|
-
derivedResponsibilities: view.derivedResponsibilities,
|
|
100
|
-
},
|
|
101
|
-
discipline,
|
|
102
|
-
level,
|
|
103
|
-
track,
|
|
104
|
-
template: jobTemplate,
|
|
105
|
-
})
|
|
106
|
-
: null,
|
|
107
|
-
|
|
108
|
-
// Behaviour profile, Skill matrix, Toolkit, Driver coverage tables
|
|
109
|
-
showTables
|
|
110
|
-
? div(
|
|
111
|
-
{ className: "job-tables-section" },
|
|
112
|
-
// Behaviour profile table
|
|
113
|
-
createDetailSection({
|
|
114
|
-
title: "Behaviour Profile",
|
|
115
|
-
content: createBehaviourProfile(view.behaviourProfile),
|
|
116
|
-
}),
|
|
117
|
-
|
|
118
|
-
createDetailSection({
|
|
119
|
-
title: "Skill Matrix",
|
|
120
|
-
content: createSkillMatrix(view.skillMatrix, {
|
|
121
|
-
capabilityOrder: view.capabilityOrder,
|
|
122
|
-
}),
|
|
123
|
-
}),
|
|
124
|
-
|
|
125
|
-
// Toolkit (after skill matrix)
|
|
126
|
-
view.toolkit && view.toolkit.length > 0
|
|
127
|
-
? createDetailSection({
|
|
128
|
-
title: "Tool Kit",
|
|
129
|
-
content: createToolkitTable(view.toolkit),
|
|
130
|
-
})
|
|
131
|
-
: null,
|
|
132
|
-
)
|
|
133
|
-
: null,
|
|
134
|
-
|
|
135
|
-
// Job Description (copyable markdown)
|
|
136
|
-
showJobDescriptionMarkdown && hasEntities
|
|
137
|
-
? createJobDescriptionSection({
|
|
138
|
-
job: {
|
|
139
|
-
title: view.title,
|
|
140
|
-
skillMatrix: view.skillMatrix,
|
|
141
|
-
behaviourProfile: view.behaviourProfile,
|
|
142
|
-
expectations: view.expectations,
|
|
143
|
-
derivedResponsibilities: view.derivedResponsibilities,
|
|
144
|
-
},
|
|
145
|
-
discipline,
|
|
146
|
-
level,
|
|
147
|
-
track,
|
|
148
|
-
template: jobTemplate,
|
|
149
|
-
})
|
|
163
|
+
showTables ? createJobTablesSection(view) : null,
|
|
164
|
+
showJobDescriptionMarkdown && descParams
|
|
165
|
+
? createJobDescriptionSection(descParams)
|
|
150
166
|
: null,
|
|
151
167
|
);
|
|
152
168
|
}
|
|
@@ -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
|
|