@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.
Files changed (96) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +28 -28
  3. package/package.json +4 -4
  4. package/src/commands/agent.js +8 -8
  5. package/src/commands/build.js +7 -7
  6. package/src/commands/dev.js +7 -7
  7. package/src/commands/driver.js +1 -1
  8. package/src/commands/index.js +1 -1
  9. package/src/commands/init.js +1 -1
  10. package/src/commands/interview.js +8 -8
  11. package/src/commands/job.js +19 -19
  12. package/src/commands/level.js +60 -0
  13. package/src/commands/progress.js +20 -20
  14. package/src/commands/questions.js +3 -3
  15. package/src/commands/skill.js +1 -1
  16. package/src/commands/track.js +1 -1
  17. package/src/commands/update.js +12 -14
  18. package/src/components/action-buttons.js +3 -3
  19. package/src/components/builder.js +25 -25
  20. package/src/components/checklist.js +1 -1
  21. package/src/components/comparison-radar.js +3 -3
  22. package/src/components/detail.js +3 -3
  23. package/src/components/grid.js +1 -1
  24. package/src/components/radar-chart.js +3 -3
  25. package/src/components/skill-matrix.js +7 -7
  26. package/src/css/pages/landing.css +5 -5
  27. package/src/formatters/behaviour/dom.js +1 -1
  28. package/src/formatters/discipline/dom.js +1 -1
  29. package/src/formatters/driver/dom.js +1 -1
  30. package/src/formatters/index.js +5 -5
  31. package/src/formatters/interview/dom.js +1 -1
  32. package/src/formatters/interview/markdown.js +1 -1
  33. package/src/formatters/interview/shared.js +20 -20
  34. package/src/formatters/job/description.js +18 -18
  35. package/src/formatters/job/dom.js +12 -12
  36. package/src/formatters/job/markdown.js +7 -7
  37. package/src/formatters/json-ld.js +24 -24
  38. package/src/formatters/{grade → level}/dom.js +32 -28
  39. package/src/formatters/{grade → level}/markdown.js +20 -29
  40. package/src/formatters/{grade → level}/microdata.js +28 -38
  41. package/src/formatters/level/shared.js +86 -0
  42. package/src/formatters/progress/markdown.js +2 -2
  43. package/src/formatters/progress/shared.js +48 -48
  44. package/src/formatters/questions/markdown.js +8 -6
  45. package/src/formatters/questions/shared.js +7 -7
  46. package/src/formatters/skill/dom.js +4 -4
  47. package/src/formatters/skill/markdown.js +2 -2
  48. package/src/formatters/skill/microdata.js +3 -3
  49. package/src/formatters/skill/shared.js +3 -3
  50. package/src/formatters/track/dom.js +1 -1
  51. package/src/formatters/track/markdown.js +1 -1
  52. package/src/handout-main.js +13 -16
  53. package/src/handout.html +4 -4
  54. package/src/index.html +5 -5
  55. package/src/lib/card-mappers.js +17 -17
  56. package/src/lib/cli-command.js +3 -3
  57. package/src/lib/cli-output.js +2 -2
  58. package/src/lib/job-cache.js +11 -11
  59. package/src/lib/render.js +6 -6
  60. package/src/lib/state.js +2 -2
  61. package/src/lib/yaml-loader.js +9 -9
  62. package/src/main.js +10 -10
  63. package/src/pages/agent-builder.js +11 -11
  64. package/src/pages/assessment-results.js +27 -23
  65. package/src/pages/behaviour.js +1 -1
  66. package/src/pages/discipline.js +1 -1
  67. package/src/pages/driver.js +1 -1
  68. package/src/pages/interview-builder.js +6 -6
  69. package/src/pages/interview.js +9 -9
  70. package/src/pages/job-builder.js +6 -6
  71. package/src/pages/job.js +7 -7
  72. package/src/pages/landing.js +9 -9
  73. package/src/pages/level.js +122 -0
  74. package/src/pages/progress-builder.js +8 -8
  75. package/src/pages/progress.js +74 -74
  76. package/src/pages/self-assessment.js +8 -8
  77. package/src/pages/skill.js +1 -4
  78. package/src/pages/stage.js +1 -1
  79. package/src/pages/tool.js +1 -1
  80. package/src/pages/track.js +1 -1
  81. package/src/slide-main.js +22 -22
  82. package/src/slides/chapter.js +4 -4
  83. package/src/slides/index.js +11 -11
  84. package/src/slides/interview.js +2 -2
  85. package/src/slides/job.js +3 -3
  86. package/src/slides/level.js +32 -0
  87. package/src/slides/overview.js +9 -9
  88. package/src/slides/progress.js +13 -13
  89. package/src/slides.html +4 -4
  90. package/src/types.js +1 -1
  91. package/templates/install.template.sh +11 -8
  92. package/templates/job.template.md +2 -2
  93. package/src/commands/grade.js +0 -60
  94. package/src/formatters/grade/shared.js +0 -86
  95. package/src/pages/grade.js +0 -122
  96. package/src/slides/grade.js +0 -32
@@ -1,27 +1,27 @@
1
1
  /**
2
- * Grade formatting for markdown/CLI output
2
+ * Level formatting for markdown/CLI output
3
3
  */
4
4
 
5
5
  import { tableToMarkdown, capitalize } from "../shared.js";
6
- import { prepareGradesList, prepareGradeDetail } from "./shared.js";
7
- import { getConceptEmoji } from "@forwardimpact/schema/levels";
6
+ import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
7
+ import { getConceptEmoji } from "@forwardimpact/map/levels";
8
8
 
9
9
  /**
10
- * Format grade list as markdown
11
- * @param {Array} grades - Raw grade entities
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 gradeListToMarkdown(grades, framework) {
16
- const { items } = prepareGradesList(grades);
17
- const emoji = framework ? getConceptEmoji(framework, "grade") : "📊";
18
- const lines = [`# ${emoji} Grades`, ""];
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.baseSkillLevels?.primary || "-"),
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 grade detail as markdown
35
- * @param {Object} grade - Raw grade entity
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 gradeToMarkdown(grade, framework) {
40
- const view = prepareGradeDetail(grade);
41
- const emoji = framework ? getConceptEmoji(framework, "grade") : "📊";
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 levels
61
- lines.push("## Base Skill Levels", "");
62
- const skillRows = Object.entries(view.baseSkillLevels).map(
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
- const behaviourRows = Object.entries(view.baseBehaviourMaturity).map(
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
- * Grade formatting for microdata HTML output
2
+ * Level formatting for microdata HTML output
3
3
  *
4
- * Generates clean, class-less HTML with microdata aligned with grades.schema.json
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 { prepareGradesList, prepareGradeDetail } from "./shared.js";
19
+ import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
20
20
 
21
21
  /**
22
- * Format grade list as microdata HTML
23
- * @param {Array} grades - Raw grade entities
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 gradeListToMicrodata(grades) {
27
- const { items } = prepareGradesList(grades);
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: "Grade", itemid: `#${g.id}` })}
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
- "Grades",
41
+ "Levels",
42
42
  `<main>
43
- <h1>Grades</h1>
43
+ <h1>Levels</h1>
44
44
  ${content}
45
45
  </main>`,
46
46
  );
47
47
  }
48
48
 
49
49
  /**
50
- * Format grade detail as microdata HTML
51
- * @param {Object} grade - Raw grade entity
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 gradeToMicrodata(grade) {
55
- const view = prepareGradeDetail(grade);
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 levels - using BaseSkillLevels itemtype
82
- if (view.baseSkillLevels && Object.keys(view.baseSkillLevels).length > 0) {
83
- const levelPairs = Object.entries(view.baseSkillLevels).map(
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 Levels",
93
- `${openTag("div", { itemtype: "BaseSkillLevels", itemprop: "baseSkillLevels" })}
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
- view.baseBehaviourMaturity &&
104
- Object.keys(view.baseBehaviourMaturity).length > 0
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 GradeExpectations itemtype
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: "GradeExpectations", itemprop: "expectations" })}
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: "Grade", itemid: `#${view.id}` })}
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.levelChange !== 0,
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.levelChange),
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
- getNextGrade,
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 grade for progression
20
- * @param {Object} currentGrade
21
- * @param {Array} grades
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 getDefaultTargetGrade(currentGrade, grades) {
25
- return getNextGrade(currentGrade, grades);
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.grade
32
+ * @param {Object} params.level
33
33
  * @param {Object} params.track
34
- * @param {Array} [params.grades] - All grades for validation
34
+ * @param {Array} [params.levels] - All levels for validation
35
35
  * @returns {boolean}
36
36
  */
37
- export function isValidCombination({ discipline, grade, track, grades }) {
38
- return isValidJobCombination({ discipline, grade, track, grades });
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.grade
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
- grade,
64
+ level,
65
65
  track,
66
66
  skills,
67
67
  behaviours,
68
68
  capabilities,
69
69
  }) {
70
- if (!discipline || !grade) return null;
70
+ if (!discipline || !level) return null;
71
71
 
72
72
  const job = getOrCreateJob({
73
73
  discipline,
74
- grade,
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} nextGrade
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.grade
107
+ * @param {Object|null} params.level
108
108
  * @param {Object|null} params.track
109
- * @param {Array} params.grades - All grades
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
- grade,
115
+ level,
116
116
  track,
117
- grades,
117
+ levels,
118
118
  tracks,
119
119
  }) {
120
120
  // Track is optional (null = generalist)
121
- if (!discipline || !grade) {
121
+ if (!discipline || !level) {
122
122
  return {
123
123
  isValid: false,
124
124
  title: null,
125
125
  invalidReason: null,
126
- nextGrade: null,
126
+ nextLevel: null,
127
127
  validTracks: [],
128
128
  };
129
129
  }
130
130
 
131
131
  const validCombination = isValidJobCombination({
132
132
  discipline,
133
- grade,
133
+ level,
134
134
  track,
135
- grades,
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
- nextGrade: null,
146
+ nextLevel: null,
147
147
  validTracks: [],
148
148
  };
149
149
  }
150
150
 
151
- const title = generateJobTitle(discipline, grade, track);
152
- const nextGrade = getNextGrade(grade, grades);
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, grade, track: t, grades }),
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
- nextGrade: nextGrade
166
- ? { id: nextGrade.id, name: nextGrade.managementTitle }
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.fromGrade
187
+ * @param {Object} params.fromLevel
188
188
  * @param {Object} params.fromTrack
189
189
  * @param {Object} params.toDiscipline
190
- * @param {Object} params.toGrade
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
- fromGrade,
199
+ fromLevel,
200
200
  fromTrack,
201
201
  toDiscipline,
202
- toGrade,
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 || !fromGrade) return null;
210
- if (!toDiscipline || !toGrade) return null;
209
+ if (!fromDiscipline || !fromLevel) return null;
210
+ if (!toDiscipline || !toLevel) return null;
211
211
 
212
212
  const fromJob = getOrCreateJob({
213
213
  discipline: fromDiscipline,
214
- grade: fromGrade,
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
- grade: toGrade,
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
- levelChange: s.change,
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.levelChange > 0).length,
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.levelChange !== 0).length +
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
- gradeId: fromGrade.id,
268
+ levelId: fromLevel.id,
269
269
  trackId: fromTrack?.id || null,
270
270
  },
271
271
  toJob: {
272
272
  disciplineId: toDiscipline.id,
273
- gradeId: toGrade.id,
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.currentGrade
295
+ * @param {Object} params.currentLevel
296
296
  * @param {Object} params.currentTrack
297
297
  * @param {Object} params.targetDiscipline
298
- * @param {Object} params.targetGrade
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
- currentGrade,
306
+ currentLevel,
307
307
  currentTrack,
308
308
  targetDiscipline,
309
- targetGrade,
309
+ targetLevel,
310
310
  targetTrack,
311
311
  skills,
312
312
  behaviours,
313
313
  }) {
314
314
  const analysis = analyzeCustomProgression({
315
315
  discipline,
316
- currentGrade,
316
+ currentLevel,
317
317
  currentTrack,
318
318
  targetDiscipline,
319
- targetGrade,
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 { SKILL_LEVELS, BEHAVIOUR_MATURITIES } from "./shared.js";
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
- SKILL_LEVELS.map((l) => pad(LEVEL_ABBREVS[l], 7)).join("") +
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
- SKILL_LEVELS.map((l) => {
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
- SKILL_LEVELS.map((l) => pad(String(levelTotals[l] || 0), 7)).join("") +
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 SKILL_LEVELS) {
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" ? SKILL_LEVELS : BEHAVIOUR_MATURITIES;
255
+ sourceType === "skill" ? SKILL_PROFICIENCIES : BEHAVIOUR_MATURITIES;
254
256
 
255
257
  for (const level of orderedLevels) {
256
258
  if (!byLevel[level]) continue;