@forwardimpact/pathway 0.21.0 → 0.22.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/README.md +3 -3
- package/bin/fit-pathway.js +22 -22
- package/package.json +3 -3
- package/src/commands/agent.js +7 -7
- package/src/commands/index.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/interview.js +8 -8
- package/src/commands/job.js +19 -19
- package/src/commands/level.js +60 -0
- package/src/commands/progress.js +20 -20
- package/src/commands/questions.js +3 -3
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/comparison-radar.js +3 -3
- package/src/components/detail.js +2 -2
- package/src/components/grid.js +1 -1
- package/src/components/radar-chart.js +3 -3
- package/src/components/skill-matrix.js +7 -7
- package/src/css/pages/landing.css +5 -5
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/shared.js +20 -20
- package/src/formatters/job/description.js +17 -17
- package/src/formatters/job/dom.js +12 -12
- package/src/formatters/job/markdown.js +7 -7
- package/src/formatters/json-ld.js +24 -24
- package/src/formatters/{grade → level}/dom.js +31 -27
- package/src/formatters/{grade → level}/markdown.js +19 -28
- package/src/formatters/{grade → level}/microdata.js +28 -38
- package/src/formatters/level/shared.js +86 -0
- package/src/formatters/progress/markdown.js +2 -2
- package/src/formatters/progress/shared.js +48 -48
- package/src/formatters/questions/markdown.js +8 -6
- package/src/formatters/questions/shared.js +7 -7
- package/src/formatters/skill/dom.js +4 -4
- package/src/formatters/skill/markdown.js +1 -1
- package/src/formatters/skill/microdata.js +3 -3
- package/src/formatters/skill/shared.js +2 -2
- package/src/handout-main.js +12 -12
- package/src/index.html +1 -1
- package/src/lib/card-mappers.js +16 -16
- package/src/lib/cli-command.js +3 -3
- package/src/lib/cli-output.js +2 -2
- package/src/lib/job-cache.js +11 -11
- package/src/lib/render.js +5 -5
- package/src/lib/state.js +2 -2
- package/src/lib/yaml-loader.js +9 -9
- package/src/main.js +10 -10
- package/src/pages/agent-builder.js +11 -11
- package/src/pages/assessment-results.js +27 -23
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +8 -8
- package/src/pages/job-builder.js +6 -6
- package/src/pages/job.js +7 -7
- package/src/pages/landing.js +8 -8
- package/src/pages/level.js +122 -0
- package/src/pages/progress-builder.js +8 -8
- package/src/pages/progress.js +74 -74
- package/src/pages/self-assessment.js +7 -7
- package/src/slide-main.js +22 -22
- package/src/slides/chapter.js +4 -4
- package/src/slides/index.js +10 -10
- package/src/slides/interview.js +2 -2
- package/src/slides/job.js +3 -3
- package/src/slides/level.js +32 -0
- package/src/slides/overview.js +9 -9
- package/src/slides/progress.js +13 -13
- package/src/types.js +1 -1
- package/templates/job.template.md +2 -2
- package/src/commands/grade.js +0 -60
- package/src/formatters/grade/shared.js +0 -86
- package/src/pages/grade.js +0 -122
- package/src/slides/grade.js +0 -32
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Builder component for discipline/
|
|
2
|
+
* Builder component for discipline/level/track selection pages
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -32,7 +32,7 @@ import { createReactive } from "../lib/reactive.js";
|
|
|
32
32
|
/**
|
|
33
33
|
* @typedef {Object} BuilderSelection
|
|
34
34
|
* @property {Object} discipline - Selected discipline
|
|
35
|
-
* @property {Object}
|
|
35
|
+
* @property {Object} level - Selected level
|
|
36
36
|
* @property {Object} track - Selected track
|
|
37
37
|
*/
|
|
38
38
|
|
|
@@ -78,10 +78,10 @@ export function createBuilder({
|
|
|
78
78
|
const selection = createReactive({
|
|
79
79
|
discipline: urlParams.get("discipline") || "",
|
|
80
80
|
track: urlParams.get("track") || "",
|
|
81
|
-
|
|
81
|
+
level: urlParams.get("level") || "",
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
const
|
|
84
|
+
const sortedLevels = [...data.levels].sort((a, b) => a.level - b.level);
|
|
85
85
|
|
|
86
86
|
// Create elements that need references
|
|
87
87
|
const previewContainer = div(
|
|
@@ -155,9 +155,9 @@ export function createBuilder({
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// Subscribe to selection changes - all updates happen here
|
|
158
|
-
selection.subscribe(({ discipline, track,
|
|
159
|
-
// Track is now optional - only discipline and
|
|
160
|
-
if (!discipline || !
|
|
158
|
+
selection.subscribe(({ discipline, track, level }) => {
|
|
159
|
+
// Track is now optional - only discipline and level are required
|
|
160
|
+
if (!discipline || !level) {
|
|
161
161
|
previewContainer.innerHTML = "";
|
|
162
162
|
previewContainer.appendChild(
|
|
163
163
|
p({ className: "text-muted" }, emptyPreviewText),
|
|
@@ -168,9 +168,9 @@ export function createBuilder({
|
|
|
168
168
|
|
|
169
169
|
const disciplineObj = data.disciplines.find((d) => d.id === discipline);
|
|
170
170
|
const trackObj = track ? data.tracks.find((t) => t.id === track) : null;
|
|
171
|
-
const
|
|
171
|
+
const levelObj = data.levels.find((g) => g.id === level);
|
|
172
172
|
|
|
173
|
-
if (!disciplineObj || !
|
|
173
|
+
if (!disciplineObj || !levelObj) {
|
|
174
174
|
previewContainer.innerHTML = "";
|
|
175
175
|
previewContainer.appendChild(
|
|
176
176
|
p({ className: "text-muted" }, "Invalid selection. Please try again."),
|
|
@@ -182,7 +182,7 @@ export function createBuilder({
|
|
|
182
182
|
const selectionObj = {
|
|
183
183
|
discipline: disciplineObj,
|
|
184
184
|
track: trackObj,
|
|
185
|
-
|
|
185
|
+
level: levelObj,
|
|
186
186
|
};
|
|
187
187
|
const preview = previewPresenter(selectionObj, data);
|
|
188
188
|
|
|
@@ -193,8 +193,8 @@ export function createBuilder({
|
|
|
193
193
|
|
|
194
194
|
// Wire up button
|
|
195
195
|
actionButton.addEventListener("click", () => {
|
|
196
|
-
const { discipline, track,
|
|
197
|
-
window.location.hash = detailPath({ discipline, track,
|
|
196
|
+
const { discipline, track, level } = selection.get();
|
|
197
|
+
window.location.hash = detailPath({ discipline, track, level });
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
// Build the page
|
|
@@ -234,17 +234,17 @@ export function createBuilder({
|
|
|
234
234
|
getDisplayName: (d) => d.specialization || d.name,
|
|
235
235
|
}),
|
|
236
236
|
),
|
|
237
|
-
//
|
|
237
|
+
// Level selector (second)
|
|
238
238
|
div(
|
|
239
239
|
{ className: "form-group" },
|
|
240
|
-
label({ className: "form-label" }, labels.
|
|
240
|
+
label({ className: "form-label" }, labels.level || "Level"),
|
|
241
241
|
createSelectWithValue({
|
|
242
|
-
id: "
|
|
243
|
-
items:
|
|
244
|
-
initialValue: selection.get().
|
|
245
|
-
placeholder: "Select a
|
|
242
|
+
id: "level-select",
|
|
243
|
+
items: sortedLevels,
|
|
244
|
+
initialValue: selection.get().level,
|
|
245
|
+
placeholder: "Select a level...",
|
|
246
246
|
onChange: (value) => {
|
|
247
|
-
selection.update((prev) => ({ ...prev,
|
|
247
|
+
selection.update((prev) => ({ ...prev, level: value }));
|
|
248
248
|
},
|
|
249
249
|
getDisplayName: (g) => g.id,
|
|
250
250
|
}),
|
|
@@ -284,7 +284,7 @@ export function createBuilder({
|
|
|
284
284
|
|
|
285
285
|
// Trigger initial update if preselected
|
|
286
286
|
const initial = selection.get();
|
|
287
|
-
if (initial.discipline || initial.track || initial.
|
|
287
|
+
if (initial.discipline || initial.track || initial.level) {
|
|
288
288
|
setTimeout(() => selection.set(selection.get()), 0);
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -370,12 +370,12 @@ export function createProgressPreview(preview, selection) {
|
|
|
370
370
|
);
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
-
const { discipline,
|
|
373
|
+
const { discipline, level, track } = selection;
|
|
374
374
|
|
|
375
375
|
// Build badges array - track is optional
|
|
376
376
|
const badges = [
|
|
377
377
|
createBadge(discipline.specialization, "discipline"),
|
|
378
|
-
createBadge(
|
|
378
|
+
createBadge(level.id, "level"),
|
|
379
379
|
];
|
|
380
380
|
if (track) {
|
|
381
381
|
badges.push(createBadge(track.name, "track"));
|
|
@@ -394,19 +394,19 @@ export function createProgressPreview(preview, selection) {
|
|
|
394
394
|
div({ className: "preview-label" }, "Progression Paths Available"),
|
|
395
395
|
div(
|
|
396
396
|
{ className: "preview-paths" },
|
|
397
|
-
preview.
|
|
397
|
+
preview.nextLevel
|
|
398
398
|
? div(
|
|
399
399
|
{ className: "path-item" },
|
|
400
400
|
span({ className: "path-icon" }, "📈"),
|
|
401
401
|
span(
|
|
402
402
|
{},
|
|
403
|
-
`Next
|
|
403
|
+
`Next Level: ${preview.nextLevel.id} - ${preview.nextLevel.name}`,
|
|
404
404
|
),
|
|
405
405
|
)
|
|
406
406
|
: div(
|
|
407
407
|
{ className: "path-item text-muted" },
|
|
408
408
|
span({ className: "path-icon" }, "🏆"),
|
|
409
|
-
span({}, "You're at the highest
|
|
409
|
+
span({}, "You're at the highest level!"),
|
|
410
410
|
),
|
|
411
411
|
preview.validTracks.length > 0
|
|
412
412
|
? div(
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { ComparisonRadarChart } from "../lib/radar.js";
|
|
10
10
|
import { div, h3 } from "../lib/render.js";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
getSkillProficiencyIndex,
|
|
13
13
|
getBehaviourMaturityIndex,
|
|
14
14
|
formatLevel,
|
|
15
15
|
} from "../lib/render.js";
|
|
@@ -97,7 +97,7 @@ export function createComparisonSkillRadar(
|
|
|
97
97
|
|
|
98
98
|
currentData.push({
|
|
99
99
|
label: skillName,
|
|
100
|
-
value: currentSkill ?
|
|
100
|
+
value: currentSkill ? getSkillProficiencyIndex(currentSkill.level) : 0,
|
|
101
101
|
maxValue: 5,
|
|
102
102
|
description: currentSkill
|
|
103
103
|
? `${formatLevel(currentSkill.type)} - ${formatLevel(currentSkill.level)}`
|
|
@@ -106,7 +106,7 @@ export function createComparisonSkillRadar(
|
|
|
106
106
|
|
|
107
107
|
targetData.push({
|
|
108
108
|
label: skillName,
|
|
109
|
-
value: targetSkill ?
|
|
109
|
+
value: targetSkill ? getSkillProficiencyIndex(targetSkill.level) : 0,
|
|
110
110
|
maxValue: 5,
|
|
111
111
|
description: targetSkill
|
|
112
112
|
? `${formatLevel(targetSkill.type)} - ${formatLevel(targetSkill.level)}`
|
package/src/components/detail.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createBackLink } from "./nav.js";
|
|
|
21
21
|
import { createTag } from "./card.js";
|
|
22
22
|
import { formatLevel } from "../lib/render.js";
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
SKILL_PROFICIENCY_ORDER,
|
|
25
25
|
BEHAVIOUR_MATURITY_ORDER,
|
|
26
26
|
} from "@forwardimpact/map/levels";
|
|
27
27
|
|
|
@@ -80,7 +80,7 @@ export function createDetailSection({ title, content }) {
|
|
|
80
80
|
*/
|
|
81
81
|
export function createLevelTable(descriptions, type = "skill") {
|
|
82
82
|
const levels =
|
|
83
|
-
type === "skill" ?
|
|
83
|
+
type === "skill" ? SKILL_PROFICIENCY_ORDER : BEHAVIOUR_MATURITY_ORDER;
|
|
84
84
|
|
|
85
85
|
const levelLabels = Object.fromEntries(
|
|
86
86
|
levels.map((level, index) => [level, String(index + 1)]),
|
package/src/components/grid.js
CHANGED
|
@@ -59,7 +59,7 @@ export function createFixedGrid(columns, children, options = {}) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Create a grid for form selectors (discipline/
|
|
62
|
+
* Create a grid for form selectors (discipline/level/track dropdowns)
|
|
63
63
|
* Uses auto-grid-sm (200px min)
|
|
64
64
|
* @param {HTMLElement[]} children - Form control elements
|
|
65
65
|
* @returns {HTMLElement}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { RadarChart } from "../lib/radar.js";
|
|
9
9
|
import { div, h3 } from "../lib/render.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
getSkillProficiencyIndex,
|
|
12
12
|
getBehaviourMaturityIndex,
|
|
13
13
|
formatLevel,
|
|
14
14
|
} from "../lib/render.js";
|
|
@@ -33,9 +33,9 @@ export function createSkillRadar(skillMatrix, options = {}) {
|
|
|
33
33
|
|
|
34
34
|
const data = skillMatrix.map((skill) => ({
|
|
35
35
|
label: skill.skillName,
|
|
36
|
-
value:
|
|
36
|
+
value: getSkillProficiencyIndex(skill.proficiency),
|
|
37
37
|
maxValue: 5,
|
|
38
|
-
description: `${formatLevel(skill.type)} skill - ${formatLevel(skill.
|
|
38
|
+
description: `${formatLevel(skill.type)} skill - ${formatLevel(skill.proficiency)}`,
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
41
|
const chart = new RadarChart({
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
td,
|
|
16
16
|
a,
|
|
17
17
|
} from "../lib/render.js";
|
|
18
|
-
import {
|
|
18
|
+
import { getSkillProficiencyIndex } from "../lib/render.js";
|
|
19
19
|
import { createLevelCell } from "./detail.js";
|
|
20
20
|
import { createBadge } from "./card.js";
|
|
21
|
-
import {
|
|
21
|
+
import { SKILL_PROFICIENCY_ORDER } from "@forwardimpact/map/levels";
|
|
22
22
|
import { truncate } from "../formatters/shared.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -28,8 +28,8 @@ import { truncate } from "../formatters/shared.js";
|
|
|
28
28
|
*/
|
|
29
29
|
function sortByLevelDescending(skills) {
|
|
30
30
|
return [...skills].sort((a, b) => {
|
|
31
|
-
const levelA =
|
|
32
|
-
const levelB =
|
|
31
|
+
const levelA = SKILL_PROFICIENCY_ORDER.indexOf(a.level);
|
|
32
|
+
const levelB = SKILL_PROFICIENCY_ORDER.indexOf(b.level);
|
|
33
33
|
if (levelB !== levelA) {
|
|
34
34
|
return levelB - levelA;
|
|
35
35
|
}
|
|
@@ -50,7 +50,7 @@ export function createSkillMatrix(skillMatrix) {
|
|
|
50
50
|
const sortedSkills = sortByLevelDescending(skillMatrix);
|
|
51
51
|
|
|
52
52
|
const rows = sortedSkills.map((skill) => {
|
|
53
|
-
const levelIndex =
|
|
53
|
+
const levelIndex = getSkillProficiencyIndex(skill.proficiency);
|
|
54
54
|
|
|
55
55
|
return tr(
|
|
56
56
|
{ className: skill.isHumanOnly ? "human-only-row" : "" },
|
|
@@ -69,10 +69,10 @@ export function createSkillMatrix(skillMatrix) {
|
|
|
69
69
|
: null,
|
|
70
70
|
),
|
|
71
71
|
td({}, createBadge(skill.capability, skill.capability)),
|
|
72
|
-
createLevelCell(levelIndex, 5, skill.
|
|
72
|
+
createLevelCell(levelIndex, 5, skill.proficiency),
|
|
73
73
|
td(
|
|
74
74
|
{ className: "skill-description" },
|
|
75
|
-
truncate(skill.
|
|
75
|
+
truncate(skill.proficiencyDescription, 80),
|
|
76
76
|
),
|
|
77
77
|
);
|
|
78
78
|
});
|
|
@@ -63,20 +63,20 @@
|
|
|
63
63
|
margin: 0;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/*
|
|
67
|
-
.
|
|
66
|
+
/* Levels timeline */
|
|
67
|
+
.levels-timeline {
|
|
68
68
|
display: flex;
|
|
69
69
|
flex-direction: column;
|
|
70
70
|
gap: var(--space-md);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
.
|
|
73
|
+
.level-timeline-item {
|
|
74
74
|
display: flex;
|
|
75
75
|
gap: var(--space-lg);
|
|
76
76
|
align-items: flex-start;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
.
|
|
79
|
+
.level-level-marker {
|
|
80
80
|
width: 50px;
|
|
81
81
|
height: 50px;
|
|
82
82
|
border-radius: 50%;
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
flex-shrink: 0;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
.
|
|
93
|
+
.level-timeline-content {
|
|
94
94
|
flex: 1;
|
|
95
95
|
}
|
|
96
96
|
}
|
package/src/formatters/index.js
CHANGED
|
@@ -55,10 +55,10 @@ export {
|
|
|
55
55
|
disciplineToMicrodata,
|
|
56
56
|
} from "./discipline/microdata.js";
|
|
57
57
|
|
|
58
|
-
//
|
|
59
|
-
export {
|
|
60
|
-
export {
|
|
61
|
-
export {
|
|
58
|
+
// Level formatters
|
|
59
|
+
export { levelListToMarkdown, levelToMarkdown } from "./level/markdown.js";
|
|
60
|
+
export { levelToDOM } from "./level/dom.js";
|
|
61
|
+
export { levelListToMicrodata, levelToMicrodata } from "./level/microdata.js";
|
|
62
62
|
|
|
63
63
|
// Track formatters
|
|
64
64
|
export { trackListToMarkdown, trackToMarkdown } from "./track/markdown.js";
|
|
@@ -75,7 +75,7 @@ export {
|
|
|
75
75
|
behaviourToJsonLd,
|
|
76
76
|
disciplineToJsonLd,
|
|
77
77
|
trackToJsonLd,
|
|
78
|
-
|
|
78
|
+
levelToJsonLd,
|
|
79
79
|
driverToJsonLd,
|
|
80
80
|
stageToJsonLd,
|
|
81
81
|
} from "./json-ld.js";
|
|
@@ -108,7 +108,7 @@ function groupQuestionsIntoSections(questions) {
|
|
|
108
108
|
* @property {string} interviewType - 'mission', 'decomposition', or 'stakeholder'
|
|
109
109
|
* @property {string} disciplineId
|
|
110
110
|
* @property {string} disciplineName
|
|
111
|
-
* @property {string}
|
|
111
|
+
* @property {string} levelId
|
|
112
112
|
* @property {string} trackId
|
|
113
113
|
* @property {string} trackName
|
|
114
114
|
* @property {Array} sections
|
|
@@ -121,7 +121,7 @@ function groupQuestionsIntoSections(questions) {
|
|
|
121
121
|
* Prepare interview questions for a job
|
|
122
122
|
* @param {Object} params
|
|
123
123
|
* @param {Object} params.discipline
|
|
124
|
-
* @param {Object} params.
|
|
124
|
+
* @param {Object} params.level
|
|
125
125
|
* @param {Object} params.track
|
|
126
126
|
* @param {Array} params.skills
|
|
127
127
|
* @param {Array} params.behaviours
|
|
@@ -131,18 +131,18 @@ function groupQuestionsIntoSections(questions) {
|
|
|
131
131
|
*/
|
|
132
132
|
export function prepareInterviewDetail({
|
|
133
133
|
discipline,
|
|
134
|
-
|
|
134
|
+
level,
|
|
135
135
|
track,
|
|
136
136
|
skills,
|
|
137
137
|
behaviours,
|
|
138
138
|
questions,
|
|
139
139
|
interviewType = "mission",
|
|
140
140
|
}) {
|
|
141
|
-
if (!discipline || !
|
|
141
|
+
if (!discipline || !level) return null;
|
|
142
142
|
|
|
143
143
|
const job = getOrCreateJob({
|
|
144
144
|
discipline,
|
|
145
|
-
|
|
145
|
+
level,
|
|
146
146
|
track,
|
|
147
147
|
skills,
|
|
148
148
|
behaviours,
|
|
@@ -208,7 +208,7 @@ export function prepareInterviewDetail({
|
|
|
208
208
|
interviewType,
|
|
209
209
|
disciplineId: discipline.id,
|
|
210
210
|
disciplineName: discipline.specialization || discipline.name,
|
|
211
|
-
|
|
211
|
+
levelId: level.id,
|
|
212
212
|
trackId: track?.id || null,
|
|
213
213
|
trackName: track?.name || null,
|
|
214
214
|
sections: allSections,
|
|
@@ -231,21 +231,21 @@ export function prepareInterviewDetail({
|
|
|
231
231
|
* Prepare interview builder preview for form validation
|
|
232
232
|
* @param {Object} params
|
|
233
233
|
* @param {Object|null} params.discipline
|
|
234
|
-
* @param {Object|null} params.
|
|
234
|
+
* @param {Object|null} params.level
|
|
235
235
|
* @param {Object|null} params.track
|
|
236
236
|
* @param {number} params.behaviourCount - Total behaviours in the system
|
|
237
|
-
* @param {Array} [params.
|
|
237
|
+
* @param {Array} [params.levels] - All levels for validation
|
|
238
238
|
* @returns {InterviewBuilderPreview}
|
|
239
239
|
*/
|
|
240
240
|
export function prepareInterviewBuilderPreview({
|
|
241
241
|
discipline,
|
|
242
|
-
|
|
242
|
+
level,
|
|
243
243
|
track,
|
|
244
244
|
behaviourCount,
|
|
245
|
-
|
|
245
|
+
levels,
|
|
246
246
|
}) {
|
|
247
247
|
// Track is optional (null = generalist)
|
|
248
|
-
if (!discipline || !
|
|
248
|
+
if (!discipline || !level) {
|
|
249
249
|
return {
|
|
250
250
|
isValid: false,
|
|
251
251
|
title: null,
|
|
@@ -257,9 +257,9 @@ export function prepareInterviewBuilderPreview({
|
|
|
257
257
|
|
|
258
258
|
const validCombination = isValidJobCombination({
|
|
259
259
|
discipline,
|
|
260
|
-
|
|
260
|
+
level,
|
|
261
261
|
track,
|
|
262
|
-
|
|
262
|
+
levels,
|
|
263
263
|
});
|
|
264
264
|
|
|
265
265
|
if (!validCombination) {
|
|
@@ -275,7 +275,7 @@ export function prepareInterviewBuilderPreview({
|
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
const title = generateJobTitle(discipline,
|
|
278
|
+
const title = generateJobTitle(discipline, level, track);
|
|
279
279
|
const totalSkills = getDisciplineSkillIds(discipline).length;
|
|
280
280
|
|
|
281
281
|
return {
|
|
@@ -292,7 +292,7 @@ export function prepareInterviewBuilderPreview({
|
|
|
292
292
|
* @property {string} title
|
|
293
293
|
* @property {string} disciplineId
|
|
294
294
|
* @property {string} disciplineName
|
|
295
|
-
* @property {string}
|
|
295
|
+
* @property {string} levelId
|
|
296
296
|
* @property {string} trackId
|
|
297
297
|
* @property {string} trackName
|
|
298
298
|
* @property {Object.<string, Object>} interviews - Keyed by type
|
|
@@ -302,7 +302,7 @@ export function prepareInterviewBuilderPreview({
|
|
|
302
302
|
* Prepare all interview types for a job (for toggle UI)
|
|
303
303
|
* @param {Object} params
|
|
304
304
|
* @param {Object} params.discipline
|
|
305
|
-
* @param {Object} params.
|
|
305
|
+
* @param {Object} params.level
|
|
306
306
|
* @param {Object} params.track
|
|
307
307
|
* @param {Array} params.skills
|
|
308
308
|
* @param {Array} params.behaviours
|
|
@@ -311,18 +311,18 @@ export function prepareInterviewBuilderPreview({
|
|
|
311
311
|
*/
|
|
312
312
|
export function prepareAllInterviews({
|
|
313
313
|
discipline,
|
|
314
|
-
|
|
314
|
+
level,
|
|
315
315
|
track,
|
|
316
316
|
skills,
|
|
317
317
|
behaviours,
|
|
318
318
|
questions,
|
|
319
319
|
}) {
|
|
320
320
|
// Track is optional (null = generalist)
|
|
321
|
-
if (!discipline || !
|
|
321
|
+
if (!discipline || !level) return null;
|
|
322
322
|
|
|
323
323
|
const job = getOrCreateJob({
|
|
324
324
|
discipline,
|
|
325
|
-
|
|
325
|
+
level,
|
|
326
326
|
track,
|
|
327
327
|
skills,
|
|
328
328
|
behaviours,
|
|
@@ -350,7 +350,7 @@ export function prepareAllInterviews({
|
|
|
350
350
|
title: job.title,
|
|
351
351
|
disciplineId: discipline.id,
|
|
352
352
|
disciplineName: discipline.specialization || discipline.name,
|
|
353
|
-
|
|
353
|
+
levelId: level.id,
|
|
354
354
|
trackId: track?.id || null,
|
|
355
355
|
trackName: track?.name || null,
|
|
356
356
|
interviews: {
|
|
@@ -18,11 +18,11 @@ import { trimValue, trimFields } from "../shared.js";
|
|
|
18
18
|
* @param {Object} params
|
|
19
19
|
* @param {Object} params.job - The job definition
|
|
20
20
|
* @param {Object} params.discipline - The discipline
|
|
21
|
-
* @param {Object} params.
|
|
21
|
+
* @param {Object} params.level - The level
|
|
22
22
|
* @param {Object} [params.track] - The track (optional)
|
|
23
23
|
* @returns {Object} Data object ready for Mustache template
|
|
24
24
|
*/
|
|
25
|
-
function prepareJobDescriptionData({ job, discipline,
|
|
25
|
+
function prepareJobDescriptionData({ job, discipline, level, track }) {
|
|
26
26
|
// Build role summary from discipline - use manager version if applicable
|
|
27
27
|
const isManagement = discipline.isManagement === true;
|
|
28
28
|
let roleSummary =
|
|
@@ -81,22 +81,22 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
|
81
81
|
return indexB - indexA;
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
// Build capability skill sections at the highest skill
|
|
84
|
+
// Build capability skill sections at the highest skill proficiency
|
|
85
85
|
let capabilitySkills = [];
|
|
86
86
|
const derivedResponsibilities = job.derivedResponsibilities || [];
|
|
87
87
|
if (derivedResponsibilities.length > 0) {
|
|
88
|
-
// derivedResponsibilities is sorted: highest
|
|
89
|
-
const
|
|
88
|
+
// derivedResponsibilities is sorted: highest proficiency first, then by ordinalRank
|
|
89
|
+
const highestProficiency = derivedResponsibilities[0].proficiency;
|
|
90
90
|
|
|
91
|
-
// Filter responsibilities to only the highest
|
|
91
|
+
// Filter responsibilities to only the highest proficiency
|
|
92
92
|
const topResponsibilities = derivedResponsibilities.filter(
|
|
93
|
-
(r) => r.
|
|
93
|
+
(r) => r.proficiency === highestProficiency,
|
|
94
94
|
);
|
|
95
95
|
|
|
96
96
|
// Group skill matrix entries by capability at the highest level
|
|
97
97
|
const skillsByCapability = {};
|
|
98
98
|
for (const skill of job.skillMatrix) {
|
|
99
|
-
if (skill.
|
|
99
|
+
if (skill.proficiency !== highestProficiency) continue;
|
|
100
100
|
if (!skillsByCapability[skill.capability]) {
|
|
101
101
|
skillsByCapability[skill.capability] = [];
|
|
102
102
|
}
|
|
@@ -115,7 +115,7 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
|
115
115
|
responsibilityDescription: r.responsibility,
|
|
116
116
|
skills: skills.map((s) => ({
|
|
117
117
|
skillName: s.skillName,
|
|
118
|
-
|
|
118
|
+
proficiencyDescription: s.proficiencyDescription || "",
|
|
119
119
|
})),
|
|
120
120
|
};
|
|
121
121
|
});
|
|
@@ -123,9 +123,9 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
|
123
123
|
|
|
124
124
|
// Build qualification summary with placeholder replacement
|
|
125
125
|
const qualificationSummary =
|
|
126
|
-
(
|
|
126
|
+
(level.qualificationSummary || "").replace(
|
|
127
127
|
/\{typicalExperienceRange\}/g,
|
|
128
|
-
|
|
128
|
+
level.typicalExperienceRange || "",
|
|
129
129
|
) || null;
|
|
130
130
|
|
|
131
131
|
const behaviours = trimFields(sortedBehaviours, {
|
|
@@ -137,8 +137,8 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
|
137
137
|
|
|
138
138
|
return {
|
|
139
139
|
title: job.title,
|
|
140
|
-
|
|
141
|
-
typicalExperienceRange:
|
|
140
|
+
levelId: level.id,
|
|
141
|
+
typicalExperienceRange: level.typicalExperienceRange,
|
|
142
142
|
trackName: track?.name || null,
|
|
143
143
|
hasTrack: !!track,
|
|
144
144
|
roleSummary: trimValue(roleSummary),
|
|
@@ -151,7 +151,7 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
|
151
151
|
capabilitySkills: capabilitySkills.map((cap) => ({
|
|
152
152
|
...cap,
|
|
153
153
|
responsibilityDescription: trimValue(cap.responsibilityDescription),
|
|
154
|
-
skills: trimFields(cap.skills, {
|
|
154
|
+
skills: trimFields(cap.skills, { proficiencyDescription: "optional" }),
|
|
155
155
|
})),
|
|
156
156
|
hasCapabilitySkills: capabilitySkills.length > 0,
|
|
157
157
|
qualificationSummary: trimmedQualificationSummary,
|
|
@@ -164,15 +164,15 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
|
164
164
|
* @param {Object} params
|
|
165
165
|
* @param {Object} params.job - The job definition
|
|
166
166
|
* @param {Object} params.discipline - The discipline
|
|
167
|
-
* @param {Object} params.
|
|
167
|
+
* @param {Object} params.level - The level
|
|
168
168
|
* @param {Object} [params.track] - The track (optional)
|
|
169
169
|
* @param {string} template - Mustache template string
|
|
170
170
|
* @returns {string} Markdown formatted job description
|
|
171
171
|
*/
|
|
172
172
|
export function formatJobDescription(
|
|
173
|
-
{ job, discipline,
|
|
173
|
+
{ job, discipline, level, track },
|
|
174
174
|
template,
|
|
175
175
|
) {
|
|
176
|
-
const data = prepareJobDescriptionData({ job, discipline,
|
|
176
|
+
const data = prepareJobDescriptionData({ job, discipline, level, track });
|
|
177
177
|
return Mustache.render(template, data);
|
|
178
178
|
}
|
|
@@ -28,7 +28,7 @@ import { createToolkitTable } from "../toolkit/dom.js";
|
|
|
28
28
|
* @param {boolean} [options.showJobDescriptionHtml=false] - Whether to show HTML job description (for print)
|
|
29
29
|
* @param {boolean} [options.showJobDescriptionMarkdown=true] - Whether to show copyable markdown section
|
|
30
30
|
* @param {Object} [options.discipline] - Discipline entity for job description
|
|
31
|
-
* @param {Object} [options.
|
|
31
|
+
* @param {Object} [options.level] - Level entity for job description
|
|
32
32
|
* @param {Object} [options.track] - Track entity for job description
|
|
33
33
|
* @param {string} [options.jobTemplate] - Mustache template for job description
|
|
34
34
|
* @returns {HTMLElement}
|
|
@@ -40,12 +40,12 @@ export function jobToDOM(view, options = {}) {
|
|
|
40
40
|
showJobDescriptionHtml = false,
|
|
41
41
|
showJobDescriptionMarkdown = true,
|
|
42
42
|
discipline,
|
|
43
|
-
|
|
43
|
+
level,
|
|
44
44
|
track,
|
|
45
45
|
jobTemplate,
|
|
46
46
|
} = options;
|
|
47
47
|
|
|
48
|
-
const hasEntities = discipline &&
|
|
48
|
+
const hasEntities = discipline && level && jobTemplate;
|
|
49
49
|
|
|
50
50
|
return div(
|
|
51
51
|
{ className: "job-detail-page" },
|
|
@@ -61,7 +61,7 @@ export function jobToDOM(view, options = {}) {
|
|
|
61
61
|
"Generated from: ",
|
|
62
62
|
a({ href: `#/discipline/${view.disciplineId}` }, view.disciplineName),
|
|
63
63
|
" × ",
|
|
64
|
-
a({ href: `#/
|
|
64
|
+
a({ href: `#/level/${view.levelId}` }, view.levelId),
|
|
65
65
|
" × ",
|
|
66
66
|
a({ href: `#/track/${view.trackId}` }, view.trackName),
|
|
67
67
|
),
|
|
@@ -99,7 +99,7 @@ export function jobToDOM(view, options = {}) {
|
|
|
99
99
|
derivedResponsibilities: view.derivedResponsibilities,
|
|
100
100
|
},
|
|
101
101
|
discipline,
|
|
102
|
-
|
|
102
|
+
level,
|
|
103
103
|
track,
|
|
104
104
|
template: jobTemplate,
|
|
105
105
|
})
|
|
@@ -156,7 +156,7 @@ export function jobToDOM(view, options = {}) {
|
|
|
156
156
|
derivedResponsibilities: view.derivedResponsibilities,
|
|
157
157
|
},
|
|
158
158
|
discipline,
|
|
159
|
-
|
|
159
|
+
level,
|
|
160
160
|
track,
|
|
161
161
|
template: jobTemplate,
|
|
162
162
|
})
|
|
@@ -211,7 +211,7 @@ function getScoreColor(score) {
|
|
|
211
211
|
* @param {Object} params
|
|
212
212
|
* @param {Object} params.job - The job definition
|
|
213
213
|
* @param {Object} params.discipline - The discipline
|
|
214
|
-
* @param {Object} params.
|
|
214
|
+
* @param {Object} params.level - The level
|
|
215
215
|
* @param {Object} params.track - The track
|
|
216
216
|
* @param {string} params.template - Mustache template for job description
|
|
217
217
|
* @returns {HTMLElement} The job description section element
|
|
@@ -219,7 +219,7 @@ function getScoreColor(score) {
|
|
|
219
219
|
export function createJobDescriptionSection({
|
|
220
220
|
job,
|
|
221
221
|
discipline,
|
|
222
|
-
|
|
222
|
+
level,
|
|
223
223
|
track,
|
|
224
224
|
template,
|
|
225
225
|
}) {
|
|
@@ -227,7 +227,7 @@ export function createJobDescriptionSection({
|
|
|
227
227
|
{
|
|
228
228
|
job,
|
|
229
229
|
discipline,
|
|
230
|
-
|
|
230
|
+
level,
|
|
231
231
|
track,
|
|
232
232
|
},
|
|
233
233
|
template,
|
|
@@ -253,7 +253,7 @@ export function createJobDescriptionSection({
|
|
|
253
253
|
* @param {Object} params
|
|
254
254
|
* @param {Object} params.job - The job definition
|
|
255
255
|
* @param {Object} params.discipline - The discipline
|
|
256
|
-
* @param {Object} params.
|
|
256
|
+
* @param {Object} params.level - The level
|
|
257
257
|
* @param {Object} params.track - The track
|
|
258
258
|
* @param {string} params.template - Mustache template for job description
|
|
259
259
|
* @returns {HTMLElement} The job description HTML element (print-only)
|
|
@@ -261,7 +261,7 @@ export function createJobDescriptionSection({
|
|
|
261
261
|
export function createJobDescriptionHtml({
|
|
262
262
|
job,
|
|
263
263
|
discipline,
|
|
264
|
-
|
|
264
|
+
level,
|
|
265
265
|
track,
|
|
266
266
|
template,
|
|
267
267
|
}) {
|
|
@@ -269,7 +269,7 @@ export function createJobDescriptionHtml({
|
|
|
269
269
|
{
|
|
270
270
|
job,
|
|
271
271
|
discipline,
|
|
272
|
-
|
|
272
|
+
level,
|
|
273
273
|
track,
|
|
274
274
|
},
|
|
275
275
|
template,
|