@forwardimpact/pathway 0.20.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 +28 -28
- package/package.json +4 -4
- package/src/commands/agent.js +8 -8
- package/src/commands/build.js +7 -7
- package/src/commands/dev.js +7 -7
- package/src/commands/driver.js +1 -1
- 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/commands/skill.js +1 -1
- package/src/commands/track.js +1 -1
- package/src/commands/update.js +12 -14
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/checklist.js +1 -1
- package/src/components/comparison-radar.js +3 -3
- package/src/components/detail.js +3 -3
- 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/behaviour/dom.js +1 -1
- package/src/formatters/discipline/dom.js +1 -1
- package/src/formatters/driver/dom.js +1 -1
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/dom.js +1 -1
- package/src/formatters/interview/markdown.js +1 -1
- package/src/formatters/interview/shared.js +20 -20
- package/src/formatters/job/description.js +18 -18
- 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 +32 -28
- package/src/formatters/{grade → level}/markdown.js +20 -29
- 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 +2 -2
- package/src/formatters/skill/microdata.js +3 -3
- package/src/formatters/skill/shared.js +3 -3
- package/src/formatters/track/dom.js +1 -1
- package/src/formatters/track/markdown.js +1 -1
- package/src/handout-main.js +13 -16
- package/src/handout.html +4 -4
- package/src/index.html +5 -5
- package/src/lib/card-mappers.js +17 -17
- 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 +6 -6
- 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/behaviour.js +1 -1
- package/src/pages/discipline.js +1 -1
- package/src/pages/driver.js +1 -1
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +9 -9
- package/src/pages/job-builder.js +6 -6
- package/src/pages/job.js +7 -7
- package/src/pages/landing.js +9 -9
- 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 +8 -8
- package/src/pages/skill.js +1 -4
- package/src/pages/stage.js +1 -1
- package/src/pages/tool.js +1 -1
- package/src/pages/track.js +1 -1
- package/src/slide-main.js +22 -22
- package/src/slides/chapter.js +4 -4
- package/src/slides/index.js +11 -11
- 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/slides.html +4 -4
- package/src/types.js +1 -1
- package/templates/install.template.sh +11 -8
- 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,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Level formatting for markdown/CLI output
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { tableToMarkdown, capitalize } from "../shared.js";
|
|
6
|
-
import {
|
|
7
|
-
import { getConceptEmoji } from "@forwardimpact/
|
|
6
|
+
import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
|
|
7
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Format
|
|
11
|
-
* @param {Array}
|
|
10
|
+
* Format level list as markdown
|
|
11
|
+
* @param {Array} levels - Raw level entities
|
|
12
12
|
* @param {Object} [framework] - Framework config for emojis
|
|
13
13
|
* @returns {string}
|
|
14
14
|
*/
|
|
15
|
-
export function
|
|
16
|
-
const { items } =
|
|
17
|
-
const emoji = framework ? getConceptEmoji(framework, "
|
|
18
|
-
const lines = [`# ${emoji}
|
|
15
|
+
export function levelListToMarkdown(levels, framework) {
|
|
16
|
+
const { items } = prepareLevelsList(levels);
|
|
17
|
+
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
18
|
+
const lines = [`# ${emoji} Levels`, ""];
|
|
19
19
|
|
|
20
20
|
const rows = items.map((g) => [
|
|
21
21
|
g.id,
|
|
22
22
|
g.displayName,
|
|
23
23
|
g.typicalExperienceRange || "-",
|
|
24
|
-
capitalize(g.
|
|
24
|
+
capitalize(g.baseSkillProficiencies?.primary || "-"),
|
|
25
25
|
]);
|
|
26
26
|
|
|
27
27
|
lines.push(tableToMarkdown(["ID", "Name", "Years", "Primary Level"], rows));
|
|
@@ -31,14 +31,14 @@ export function gradeListToMarkdown(grades, framework) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Format
|
|
35
|
-
* @param {Object}
|
|
34
|
+
* Format level detail as markdown
|
|
35
|
+
* @param {Object} level - Raw level entity
|
|
36
36
|
* @param {Object} [framework] - Framework config for emojis
|
|
37
37
|
* @returns {string}
|
|
38
38
|
*/
|
|
39
|
-
export function
|
|
40
|
-
const view =
|
|
41
|
-
const emoji = framework ? getConceptEmoji(framework, "
|
|
39
|
+
export function levelToMarkdown(level, framework) {
|
|
40
|
+
const view = prepareLevelDetail(level);
|
|
41
|
+
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
42
42
|
const lines = [`# ${emoji} ${view.displayName} (${view.id})`, ""];
|
|
43
43
|
|
|
44
44
|
if (view.typicalExperienceRange) {
|
|
@@ -57,27 +57,18 @@ export function gradeToMarkdown(grade, framework) {
|
|
|
57
57
|
lines.push("");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Base skill
|
|
61
|
-
lines.push("## Base Skill
|
|
62
|
-
const skillRows = Object.entries(view.
|
|
60
|
+
// Base skill proficiencies
|
|
61
|
+
lines.push("## Base Skill Proficiencies", "");
|
|
62
|
+
const skillRows = Object.entries(view.baseSkillProficiencies).map(
|
|
63
63
|
([type, level]) => [capitalize(type), capitalize(level)],
|
|
64
64
|
);
|
|
65
65
|
lines.push(tableToMarkdown(["Skill Type", "Level"], skillRows));
|
|
66
66
|
lines.push("");
|
|
67
67
|
|
|
68
68
|
// Base behaviour maturity
|
|
69
|
-
if (
|
|
70
|
-
view.baseBehaviourMaturity &&
|
|
71
|
-
Object.keys(view.baseBehaviourMaturity).length > 0
|
|
72
|
-
) {
|
|
69
|
+
if (view.baseBehaviourMaturity) {
|
|
73
70
|
lines.push("## Base Behaviour Maturity", "");
|
|
74
|
-
|
|
75
|
-
([type, maturity]) => [
|
|
76
|
-
capitalize(type),
|
|
77
|
-
capitalize(maturity.replace(/_/g, " ")),
|
|
78
|
-
],
|
|
79
|
-
);
|
|
80
|
-
lines.push(tableToMarkdown(["Type", "Maturity"], behaviourRows));
|
|
71
|
+
lines.push(capitalize(view.baseBehaviourMaturity.replace(/_/g, " ")));
|
|
81
72
|
lines.push("");
|
|
82
73
|
}
|
|
83
74
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Level formatting for microdata HTML output
|
|
3
3
|
*
|
|
4
|
-
* Generates clean, class-less HTML with microdata aligned with
|
|
4
|
+
* Generates clean, class-less HTML with microdata aligned with levels.schema.json
|
|
5
5
|
* RDF vocab: https://www.forwardimpact.team/schema/rdf/
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -16,19 +16,19 @@ import {
|
|
|
16
16
|
formatLevelName,
|
|
17
17
|
htmlDocument,
|
|
18
18
|
} from "../microdata-shared.js";
|
|
19
|
-
import {
|
|
19
|
+
import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Format
|
|
23
|
-
* @param {Array}
|
|
22
|
+
* Format level list as microdata HTML
|
|
23
|
+
* @param {Array} levels - Raw level entities
|
|
24
24
|
* @returns {string} HTML with microdata
|
|
25
25
|
*/
|
|
26
|
-
export function
|
|
27
|
-
const { items } =
|
|
26
|
+
export function levelListToMicrodata(levels) {
|
|
27
|
+
const { items } = prepareLevelsList(levels);
|
|
28
28
|
|
|
29
29
|
const content = items
|
|
30
30
|
.map(
|
|
31
|
-
(g) => `${openTag("article", { itemtype: "
|
|
31
|
+
(g) => `${openTag("article", { itemtype: "Level", itemid: `#${g.id}` })}
|
|
32
32
|
${prop("h2", "id", g.id)}
|
|
33
33
|
<p>${escapeHtml(g.displayName)}</p>
|
|
34
34
|
${g.typicalExperienceRange ? prop("p", "typicalExperienceRange", g.typicalExperienceRange) : ""}
|
|
@@ -38,21 +38,21 @@ ${metaTag("ordinalRank", String(g.ordinalRank))}
|
|
|
38
38
|
.join("\n");
|
|
39
39
|
|
|
40
40
|
return htmlDocument(
|
|
41
|
-
"
|
|
41
|
+
"Levels",
|
|
42
42
|
`<main>
|
|
43
|
-
<h1>
|
|
43
|
+
<h1>Levels</h1>
|
|
44
44
|
${content}
|
|
45
45
|
</main>`,
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Format
|
|
51
|
-
* @param {Object}
|
|
50
|
+
* Format level detail as microdata HTML
|
|
51
|
+
* @param {Object} level - Raw level entity
|
|
52
52
|
* @returns {string} HTML with microdata
|
|
53
53
|
*/
|
|
54
|
-
export function
|
|
55
|
-
const view =
|
|
54
|
+
export function levelToMicrodata(level) {
|
|
55
|
+
const view = prepareLevelDetail(level);
|
|
56
56
|
|
|
57
57
|
if (!view) return "";
|
|
58
58
|
|
|
@@ -78,9 +78,12 @@ export function gradeToMicrodata(grade) {
|
|
|
78
78
|
sections.push(section("Titles", dl(titlePairs), 2));
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Base skill
|
|
82
|
-
if (
|
|
83
|
-
|
|
81
|
+
// Base skill proficiencies - using BaseSkillProficiencies itemtype
|
|
82
|
+
if (
|
|
83
|
+
view.baseSkillProficiencies &&
|
|
84
|
+
Object.keys(view.baseSkillProficiencies).length > 0
|
|
85
|
+
) {
|
|
86
|
+
const levelPairs = Object.entries(view.baseSkillProficiencies).map(
|
|
84
87
|
([type, level]) => ({
|
|
85
88
|
term: formatLevelName(type),
|
|
86
89
|
definition: formatLevelName(level),
|
|
@@ -89,8 +92,8 @@ export function gradeToMicrodata(grade) {
|
|
|
89
92
|
);
|
|
90
93
|
sections.push(
|
|
91
94
|
section(
|
|
92
|
-
"Base Skill
|
|
93
|
-
`${openTag("div", { itemtype: "
|
|
95
|
+
"Base Skill Proficiencies",
|
|
96
|
+
`${openTag("div", { itemtype: "BaseSkillProficiencies", itemprop: "baseSkillProficiencies" })}
|
|
94
97
|
${dl(levelPairs)}
|
|
95
98
|
</div>`,
|
|
96
99
|
2,
|
|
@@ -99,26 +102,13 @@ ${dl(levelPairs)}
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// Base behaviour maturity - link to BehaviourMaturity
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
) {
|
|
106
|
-
const maturityPairs = Object.entries(view.baseBehaviourMaturity).map(
|
|
107
|
-
([type, maturity]) => ({
|
|
108
|
-
term: formatLevelName(type),
|
|
109
|
-
definition: formatLevelName(maturity),
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
// Handle both single value and object cases
|
|
113
|
-
const maturityContent =
|
|
114
|
-
typeof grade.baseBehaviourMaturity === "string"
|
|
115
|
-
? `${linkTag("baseBehaviourMaturity", `#${grade.baseBehaviourMaturity}`)}
|
|
116
|
-
<p>${formatLevelName(grade.baseBehaviourMaturity)}</p>`
|
|
117
|
-
: dl(maturityPairs);
|
|
105
|
+
if (view.baseBehaviourMaturity) {
|
|
106
|
+
const maturityContent = `${linkTag("baseBehaviourMaturity", `#${level.baseBehaviourMaturity}`)}
|
|
107
|
+
<p>${formatLevelName(level.baseBehaviourMaturity)}</p>`;
|
|
118
108
|
sections.push(section("Base Behaviour Maturity", maturityContent, 2));
|
|
119
109
|
}
|
|
120
110
|
|
|
121
|
-
// Expectations - using
|
|
111
|
+
// Expectations - using LevelExpectations itemtype
|
|
122
112
|
if (view.expectations && Object.keys(view.expectations).length > 0) {
|
|
123
113
|
const expectationPairs = Object.entries(view.expectations).map(
|
|
124
114
|
([key, value]) => ({
|
|
@@ -130,7 +120,7 @@ ${dl(levelPairs)}
|
|
|
130
120
|
sections.push(
|
|
131
121
|
section(
|
|
132
122
|
"Expectations",
|
|
133
|
-
`${openTag("div", { itemtype: "
|
|
123
|
+
`${openTag("div", { itemtype: "LevelExpectations", itemprop: "expectations" })}
|
|
134
124
|
${dl(expectationPairs)}
|
|
135
125
|
</div>`,
|
|
136
126
|
2,
|
|
@@ -139,7 +129,7 @@ ${dl(expectationPairs)}
|
|
|
139
129
|
}
|
|
140
130
|
|
|
141
131
|
const body = `<main>
|
|
142
|
-
${openTag("article", { itemtype: "
|
|
132
|
+
${openTag("article", { itemtype: "Level", itemid: `#${view.id}` })}
|
|
143
133
|
<h1>${prop("span", "id", view.id)} — ${escapeHtml(view.displayName)}</h1>
|
|
144
134
|
${metaTag("ordinalRank", String(view.ordinalRank))}
|
|
145
135
|
${view.typicalExperienceRange ? prop("p", "typicalExperienceRange", `Experience: ${view.typicalExperienceRange}`) : ""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting level data across DOM and markdown outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get level display name (shows both professional and management titles)
|
|
9
|
+
* @param {Object} level
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function getLevelDisplayName(level) {
|
|
13
|
+
if (level.professionalTitle && level.managementTitle) {
|
|
14
|
+
if (level.professionalTitle === level.managementTitle) {
|
|
15
|
+
return level.professionalTitle;
|
|
16
|
+
}
|
|
17
|
+
return `${level.professionalTitle} / ${level.managementTitle}`;
|
|
18
|
+
}
|
|
19
|
+
return level.professionalTitle || level.managementTitle || level.id;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} LevelListItem
|
|
24
|
+
* @property {string} id
|
|
25
|
+
* @property {string} displayName
|
|
26
|
+
* @property {number} ordinalRank
|
|
27
|
+
* @property {string|null} typicalExperienceRange
|
|
28
|
+
* @property {Object} baseSkillProficiencies
|
|
29
|
+
* @property {string|null} impactScope
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Transform levels for list view
|
|
34
|
+
* @param {Array} levels - Raw level entities
|
|
35
|
+
* @returns {{ items: LevelListItem[] }}
|
|
36
|
+
*/
|
|
37
|
+
export function prepareLevelsList(levels) {
|
|
38
|
+
const sortedLevels = [...levels].sort(
|
|
39
|
+
(a, b) => a.ordinalRank - b.ordinalRank,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const items = sortedLevels.map((level) => ({
|
|
43
|
+
id: level.id,
|
|
44
|
+
displayName: getLevelDisplayName(level),
|
|
45
|
+
ordinalRank: level.ordinalRank,
|
|
46
|
+
typicalExperienceRange: level.typicalExperienceRange || null,
|
|
47
|
+
baseSkillProficiencies: level.baseSkillProficiencies || {},
|
|
48
|
+
impactScope: level.expectations?.impactScope || null,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return { items };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Object} LevelDetailView
|
|
56
|
+
* @property {string} id
|
|
57
|
+
* @property {string} displayName
|
|
58
|
+
* @property {string} professionalTitle
|
|
59
|
+
* @property {string} managementTitle
|
|
60
|
+
* @property {number} ordinalRank
|
|
61
|
+
* @property {string|null} typicalExperienceRange
|
|
62
|
+
* @property {Object} baseSkillProficiencies
|
|
63
|
+
* @property {Object} baseBehaviourMaturity
|
|
64
|
+
* @property {Object} expectations
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Transform level for detail view
|
|
69
|
+
* @param {Object} level - Raw level entity
|
|
70
|
+
* @returns {LevelDetailView|null}
|
|
71
|
+
*/
|
|
72
|
+
export function prepareLevelDetail(level) {
|
|
73
|
+
if (!level) return null;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
id: level.id,
|
|
77
|
+
displayName: getLevelDisplayName(level),
|
|
78
|
+
professionalTitle: level.professionalTitle || null,
|
|
79
|
+
managementTitle: level.managementTitle || null,
|
|
80
|
+
ordinalRank: level.ordinalRank,
|
|
81
|
+
typicalExperienceRange: level.typicalExperienceRange || null,
|
|
82
|
+
baseSkillProficiencies: level.baseSkillProficiencies || {},
|
|
83
|
+
baseBehaviourMaturity: level.baseBehaviourMaturity || {},
|
|
84
|
+
expectations: level.expectations || {},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -29,7 +29,7 @@ export function progressToMarkdown(view) {
|
|
|
29
29
|
|
|
30
30
|
// Skill changes
|
|
31
31
|
const skillsWithChanges = view.skillChanges.filter(
|
|
32
|
-
(s) => s.
|
|
32
|
+
(s) => s.proficiencyChange !== 0,
|
|
33
33
|
);
|
|
34
34
|
if (skillsWithChanges.length > 0) {
|
|
35
35
|
lines.push("## Skill Changes", "");
|
|
@@ -39,7 +39,7 @@ export function progressToMarkdown(view) {
|
|
|
39
39
|
formatLevel(s.fromLevel || "-"),
|
|
40
40
|
"→",
|
|
41
41
|
formatLevel(s.toLevel),
|
|
42
|
-
formatChange(s.
|
|
42
|
+
formatChange(s.proficiencyChange),
|
|
43
43
|
]);
|
|
44
44
|
lines.push(
|
|
45
45
|
tableToMarkdown(["Skill", "Type", "From", "", "To", "Change"], skillRows),
|
|
@@ -11,31 +11,31 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
analyzeProgression,
|
|
13
13
|
analyzeCustomProgression,
|
|
14
|
-
|
|
14
|
+
getNextLevel,
|
|
15
15
|
} from "@forwardimpact/libpathway/progression";
|
|
16
16
|
import { getOrCreateJob } from "@forwardimpact/libpathway/job-cache";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Get the next
|
|
20
|
-
* @param {Object}
|
|
21
|
-
* @param {Array}
|
|
19
|
+
* Get the next level for progression
|
|
20
|
+
* @param {Object} currentLevel
|
|
21
|
+
* @param {Array} levels
|
|
22
22
|
* @returns {Object|null}
|
|
23
23
|
*/
|
|
24
|
-
export function
|
|
25
|
-
return
|
|
24
|
+
export function getDefaultTargetLevel(currentLevel, levels) {
|
|
25
|
+
return getNextLevel(currentLevel, levels);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Check if a job combination is valid
|
|
30
30
|
* @param {Object} params
|
|
31
31
|
* @param {Object} params.discipline
|
|
32
|
-
* @param {Object} params.
|
|
32
|
+
* @param {Object} params.level
|
|
33
33
|
* @param {Object} params.track
|
|
34
|
-
* @param {Array} [params.
|
|
34
|
+
* @param {Array} [params.levels] - All levels for validation
|
|
35
35
|
* @returns {boolean}
|
|
36
36
|
*/
|
|
37
|
-
export function isValidCombination({ discipline,
|
|
38
|
-
return isValidJobCombination({ discipline,
|
|
37
|
+
export function isValidCombination({ discipline, level, track, levels }) {
|
|
38
|
+
return isValidJobCombination({ discipline, level, track, levels });
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
@@ -52,7 +52,7 @@ export function isValidCombination({ discipline, grade, track, grades }) {
|
|
|
52
52
|
* Prepare current job summary for progress detail page
|
|
53
53
|
* @param {Object} params
|
|
54
54
|
* @param {Object} params.discipline
|
|
55
|
-
* @param {Object} params.
|
|
55
|
+
* @param {Object} params.level
|
|
56
56
|
* @param {Object} params.track
|
|
57
57
|
* @param {Array} params.skills
|
|
58
58
|
* @param {Array} params.behaviours
|
|
@@ -61,17 +61,17 @@ export function isValidCombination({ discipline, grade, track, grades }) {
|
|
|
61
61
|
*/
|
|
62
62
|
export function prepareCurrentJob({
|
|
63
63
|
discipline,
|
|
64
|
-
|
|
64
|
+
level,
|
|
65
65
|
track,
|
|
66
66
|
skills,
|
|
67
67
|
behaviours,
|
|
68
68
|
capabilities,
|
|
69
69
|
}) {
|
|
70
|
-
if (!discipline || !
|
|
70
|
+
if (!discipline || !level) return null;
|
|
71
71
|
|
|
72
72
|
const job = getOrCreateJob({
|
|
73
73
|
discipline,
|
|
74
|
-
|
|
74
|
+
level,
|
|
75
75
|
track,
|
|
76
76
|
skills,
|
|
77
77
|
behaviours,
|
|
@@ -96,7 +96,7 @@ export function prepareCurrentJob({
|
|
|
96
96
|
* @property {boolean} isValid
|
|
97
97
|
* @property {string|null} title
|
|
98
98
|
* @property {string|null} invalidReason
|
|
99
|
-
* @property {Object|null}
|
|
99
|
+
* @property {Object|null} nextLevel
|
|
100
100
|
* @property {Array} validTracks - Other valid tracks for comparison
|
|
101
101
|
*/
|
|
102
102
|
|
|
@@ -104,35 +104,35 @@ export function prepareCurrentJob({
|
|
|
104
104
|
* Prepare career progress builder preview for form validation
|
|
105
105
|
* @param {Object} params
|
|
106
106
|
* @param {Object|null} params.discipline
|
|
107
|
-
* @param {Object|null} params.
|
|
107
|
+
* @param {Object|null} params.level
|
|
108
108
|
* @param {Object|null} params.track
|
|
109
|
-
* @param {Array} params.
|
|
109
|
+
* @param {Array} params.levels - All levels
|
|
110
110
|
* @param {Array} params.tracks - All tracks
|
|
111
111
|
* @returns {CareerProgressPreview}
|
|
112
112
|
*/
|
|
113
113
|
export function prepareCareerProgressPreview({
|
|
114
114
|
discipline,
|
|
115
|
-
|
|
115
|
+
level,
|
|
116
116
|
track,
|
|
117
|
-
|
|
117
|
+
levels,
|
|
118
118
|
tracks,
|
|
119
119
|
}) {
|
|
120
120
|
// Track is optional (null = generalist)
|
|
121
|
-
if (!discipline || !
|
|
121
|
+
if (!discipline || !level) {
|
|
122
122
|
return {
|
|
123
123
|
isValid: false,
|
|
124
124
|
title: null,
|
|
125
125
|
invalidReason: null,
|
|
126
|
-
|
|
126
|
+
nextLevel: null,
|
|
127
127
|
validTracks: [],
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
const validCombination = isValidJobCombination({
|
|
132
132
|
discipline,
|
|
133
|
-
|
|
133
|
+
level,
|
|
134
134
|
track,
|
|
135
|
-
|
|
135
|
+
levels,
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
if (!validCombination) {
|
|
@@ -143,27 +143,27 @@ export function prepareCareerProgressPreview({
|
|
|
143
143
|
isValid: false,
|
|
144
144
|
title: null,
|
|
145
145
|
invalidReason: reason,
|
|
146
|
-
|
|
146
|
+
nextLevel: null,
|
|
147
147
|
validTracks: [],
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
const title = generateJobTitle(discipline,
|
|
152
|
-
const
|
|
151
|
+
const title = generateJobTitle(discipline, level, track);
|
|
152
|
+
const nextLevel = getNextLevel(level, levels);
|
|
153
153
|
|
|
154
154
|
// Find other valid tracks for comparison (exclude current track if any)
|
|
155
155
|
const validTracks = tracks.filter(
|
|
156
156
|
(t) =>
|
|
157
157
|
(!track || t.id !== track.id) &&
|
|
158
|
-
isValidJobCombination({ discipline,
|
|
158
|
+
isValidJobCombination({ discipline, level, track: t, levels }),
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
return {
|
|
162
162
|
isValid: true,
|
|
163
163
|
title,
|
|
164
164
|
invalidReason: null,
|
|
165
|
-
|
|
166
|
-
? { id:
|
|
165
|
+
nextLevel: nextLevel
|
|
166
|
+
? { id: nextLevel.id, name: nextLevel.managementTitle }
|
|
167
167
|
: null,
|
|
168
168
|
validTracks: validTracks.map((t) => ({ id: t.id, name: t.name })),
|
|
169
169
|
};
|
|
@@ -184,10 +184,10 @@ export function prepareCareerProgressPreview({
|
|
|
184
184
|
* Prepare career progression between two roles
|
|
185
185
|
* @param {Object} params
|
|
186
186
|
* @param {Object} params.fromDiscipline
|
|
187
|
-
* @param {Object} params.
|
|
187
|
+
* @param {Object} params.fromLevel
|
|
188
188
|
* @param {Object} params.fromTrack
|
|
189
189
|
* @param {Object} params.toDiscipline
|
|
190
|
-
* @param {Object} params.
|
|
190
|
+
* @param {Object} params.toLevel
|
|
191
191
|
* @param {Object} params.toTrack
|
|
192
192
|
* @param {Array} params.skills
|
|
193
193
|
* @param {Array} params.behaviours
|
|
@@ -196,22 +196,22 @@ export function prepareCareerProgressPreview({
|
|
|
196
196
|
*/
|
|
197
197
|
export function prepareProgressDetail({
|
|
198
198
|
fromDiscipline,
|
|
199
|
-
|
|
199
|
+
fromLevel,
|
|
200
200
|
fromTrack,
|
|
201
201
|
toDiscipline,
|
|
202
|
-
|
|
202
|
+
toLevel,
|
|
203
203
|
toTrack,
|
|
204
204
|
skills,
|
|
205
205
|
behaviours,
|
|
206
206
|
capabilities,
|
|
207
207
|
}) {
|
|
208
208
|
// Track is optional (null = generalist)
|
|
209
|
-
if (!fromDiscipline || !
|
|
210
|
-
if (!toDiscipline || !
|
|
209
|
+
if (!fromDiscipline || !fromLevel) return null;
|
|
210
|
+
if (!toDiscipline || !toLevel) return null;
|
|
211
211
|
|
|
212
212
|
const fromJob = getOrCreateJob({
|
|
213
213
|
discipline: fromDiscipline,
|
|
214
|
-
|
|
214
|
+
level: fromLevel,
|
|
215
215
|
track: fromTrack,
|
|
216
216
|
skills,
|
|
217
217
|
behaviours,
|
|
@@ -220,7 +220,7 @@ export function prepareProgressDetail({
|
|
|
220
220
|
|
|
221
221
|
const toJob = getOrCreateJob({
|
|
222
222
|
discipline: toDiscipline,
|
|
223
|
-
|
|
223
|
+
level: toLevel,
|
|
224
224
|
track: toTrack,
|
|
225
225
|
skills,
|
|
226
226
|
behaviours,
|
|
@@ -238,7 +238,7 @@ export function prepareProgressDetail({
|
|
|
238
238
|
type: s.type,
|
|
239
239
|
fromLevel: s.currentLevel,
|
|
240
240
|
toLevel: s.targetLevel,
|
|
241
|
-
|
|
241
|
+
proficiencyChange: s.change,
|
|
242
242
|
}));
|
|
243
243
|
|
|
244
244
|
// Transform behaviour changes
|
|
@@ -251,12 +251,12 @@ export function prepareProgressDetail({
|
|
|
251
251
|
}));
|
|
252
252
|
|
|
253
253
|
const summary = {
|
|
254
|
-
skillsToImprove: skillChanges.filter((s) => s.
|
|
254
|
+
skillsToImprove: skillChanges.filter((s) => s.proficiencyChange > 0).length,
|
|
255
255
|
behavioursToImprove: behaviourChanges.filter((b) => b.maturityChange > 0)
|
|
256
256
|
.length,
|
|
257
257
|
newSkills: skillChanges.filter((s) => !s.fromLevel && s.toLevel).length,
|
|
258
258
|
totalChanges:
|
|
259
|
-
skillChanges.filter((s) => s.
|
|
259
|
+
skillChanges.filter((s) => s.proficiencyChange !== 0).length +
|
|
260
260
|
behaviourChanges.filter((b) => b.maturityChange !== 0).length,
|
|
261
261
|
};
|
|
262
262
|
|
|
@@ -265,12 +265,12 @@ export function prepareProgressDetail({
|
|
|
265
265
|
toTitle: toJob.title,
|
|
266
266
|
fromJob: {
|
|
267
267
|
disciplineId: fromDiscipline.id,
|
|
268
|
-
|
|
268
|
+
levelId: fromLevel.id,
|
|
269
269
|
trackId: fromTrack?.id || null,
|
|
270
270
|
},
|
|
271
271
|
toJob: {
|
|
272
272
|
disciplineId: toDiscipline.id,
|
|
273
|
-
|
|
273
|
+
levelId: toLevel.id,
|
|
274
274
|
trackId: toTrack?.id || null,
|
|
275
275
|
},
|
|
276
276
|
skillChanges,
|
|
@@ -292,10 +292,10 @@ export function prepareProgressDetail({
|
|
|
292
292
|
* Prepare custom progression analysis between any two roles
|
|
293
293
|
* @param {Object} params
|
|
294
294
|
* @param {Object} params.discipline - Current discipline
|
|
295
|
-
* @param {Object} params.
|
|
295
|
+
* @param {Object} params.currentLevel
|
|
296
296
|
* @param {Object} params.currentTrack
|
|
297
297
|
* @param {Object} params.targetDiscipline
|
|
298
|
-
* @param {Object} params.
|
|
298
|
+
* @param {Object} params.targetLevel
|
|
299
299
|
* @param {Object} params.targetTrack
|
|
300
300
|
* @param {Array} params.skills
|
|
301
301
|
* @param {Array} params.behaviours
|
|
@@ -303,20 +303,20 @@ export function prepareProgressDetail({
|
|
|
303
303
|
*/
|
|
304
304
|
export function prepareCustomProgression({
|
|
305
305
|
discipline,
|
|
306
|
-
|
|
306
|
+
currentLevel,
|
|
307
307
|
currentTrack,
|
|
308
308
|
targetDiscipline,
|
|
309
|
-
|
|
309
|
+
targetLevel,
|
|
310
310
|
targetTrack,
|
|
311
311
|
skills,
|
|
312
312
|
behaviours,
|
|
313
313
|
}) {
|
|
314
314
|
const analysis = analyzeCustomProgression({
|
|
315
315
|
discipline,
|
|
316
|
-
|
|
316
|
+
currentLevel,
|
|
317
317
|
currentTrack,
|
|
318
318
|
targetDiscipline,
|
|
319
|
-
|
|
319
|
+
targetLevel,
|
|
320
320
|
targetTrack,
|
|
321
321
|
skills,
|
|
322
322
|
behaviours,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Formats questions for terminal output as tables and lists.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { SKILL_PROFICIENCIES, BEHAVIOUR_MATURITIES } from "./shared.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Level abbreviations for compact display
|
|
@@ -67,7 +67,7 @@ function formatStats(view, skills) {
|
|
|
67
67
|
// Header
|
|
68
68
|
const skillHeader =
|
|
69
69
|
pad("Skill", 30) +
|
|
70
|
-
|
|
70
|
+
SKILL_PROFICIENCIES.map((l) => pad(LEVEL_ABBREVS[l], 7)).join("") +
|
|
71
71
|
"TOTAL";
|
|
72
72
|
lines.push(skillHeader);
|
|
73
73
|
lines.push("─".repeat(75));
|
|
@@ -84,7 +84,7 @@ function formatStats(view, skills) {
|
|
|
84
84
|
|
|
85
85
|
const row =
|
|
86
86
|
pad(name, 30) +
|
|
87
|
-
|
|
87
|
+
SKILL_PROFICIENCIES.map((l) => {
|
|
88
88
|
const count = skillData[l] || 0;
|
|
89
89
|
levelTotals[l] = (levelTotals[l] || 0) + count;
|
|
90
90
|
return pad(String(count), 7);
|
|
@@ -98,7 +98,9 @@ function formatStats(view, skills) {
|
|
|
98
98
|
lines.push("─".repeat(75));
|
|
99
99
|
const totalsRow =
|
|
100
100
|
pad("TOTAL", 30) +
|
|
101
|
-
|
|
101
|
+
SKILL_PROFICIENCIES.map((l) => pad(String(levelTotals[l] || 0), 7)).join(
|
|
102
|
+
"",
|
|
103
|
+
) +
|
|
102
104
|
String(skillTotal);
|
|
103
105
|
lines.push(totalsRow);
|
|
104
106
|
lines.push("");
|
|
@@ -150,7 +152,7 @@ function formatStats(view, skills) {
|
|
|
150
152
|
const gaps = [];
|
|
151
153
|
for (const skillId of sortedSkillIds) {
|
|
152
154
|
const skillData = stats.skillStats[skillId];
|
|
153
|
-
for (const level of
|
|
155
|
+
for (const level of SKILL_PROFICIENCIES) {
|
|
154
156
|
if ((skillData[level] || 0) < 1) {
|
|
155
157
|
gaps.push(`${skillId}: missing ${level} questions`);
|
|
156
158
|
}
|
|
@@ -250,7 +252,7 @@ function formatSingleSource(view) {
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
const orderedLevels =
|
|
253
|
-
sourceType === "skill" ?
|
|
255
|
+
sourceType === "skill" ? SKILL_PROFICIENCIES : BEHAVIOUR_MATURITIES;
|
|
254
256
|
|
|
255
257
|
for (const level of orderedLevels) {
|
|
256
258
|
if (!byLevel[level]) continue;
|