@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.
Files changed (72) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +22 -22
  3. package/package.json +3 -3
  4. package/src/commands/agent.js +7 -7
  5. package/src/commands/index.js +1 -1
  6. package/src/commands/init.js +1 -1
  7. package/src/commands/interview.js +8 -8
  8. package/src/commands/job.js +19 -19
  9. package/src/commands/level.js +60 -0
  10. package/src/commands/progress.js +20 -20
  11. package/src/commands/questions.js +3 -3
  12. package/src/components/action-buttons.js +3 -3
  13. package/src/components/builder.js +25 -25
  14. package/src/components/comparison-radar.js +3 -3
  15. package/src/components/detail.js +2 -2
  16. package/src/components/grid.js +1 -1
  17. package/src/components/radar-chart.js +3 -3
  18. package/src/components/skill-matrix.js +7 -7
  19. package/src/css/pages/landing.css +5 -5
  20. package/src/formatters/index.js +5 -5
  21. package/src/formatters/interview/shared.js +20 -20
  22. package/src/formatters/job/description.js +17 -17
  23. package/src/formatters/job/dom.js +12 -12
  24. package/src/formatters/job/markdown.js +7 -7
  25. package/src/formatters/json-ld.js +24 -24
  26. package/src/formatters/{grade → level}/dom.js +31 -27
  27. package/src/formatters/{grade → level}/markdown.js +19 -28
  28. package/src/formatters/{grade → level}/microdata.js +28 -38
  29. package/src/formatters/level/shared.js +86 -0
  30. package/src/formatters/progress/markdown.js +2 -2
  31. package/src/formatters/progress/shared.js +48 -48
  32. package/src/formatters/questions/markdown.js +8 -6
  33. package/src/formatters/questions/shared.js +7 -7
  34. package/src/formatters/skill/dom.js +4 -4
  35. package/src/formatters/skill/markdown.js +1 -1
  36. package/src/formatters/skill/microdata.js +3 -3
  37. package/src/formatters/skill/shared.js +2 -2
  38. package/src/handout-main.js +12 -12
  39. package/src/index.html +1 -1
  40. package/src/lib/card-mappers.js +16 -16
  41. package/src/lib/cli-command.js +3 -3
  42. package/src/lib/cli-output.js +2 -2
  43. package/src/lib/job-cache.js +11 -11
  44. package/src/lib/render.js +5 -5
  45. package/src/lib/state.js +2 -2
  46. package/src/lib/yaml-loader.js +9 -9
  47. package/src/main.js +10 -10
  48. package/src/pages/agent-builder.js +11 -11
  49. package/src/pages/assessment-results.js +27 -23
  50. package/src/pages/interview-builder.js +6 -6
  51. package/src/pages/interview.js +8 -8
  52. package/src/pages/job-builder.js +6 -6
  53. package/src/pages/job.js +7 -7
  54. package/src/pages/landing.js +8 -8
  55. package/src/pages/level.js +122 -0
  56. package/src/pages/progress-builder.js +8 -8
  57. package/src/pages/progress.js +74 -74
  58. package/src/pages/self-assessment.js +7 -7
  59. package/src/slide-main.js +22 -22
  60. package/src/slides/chapter.js +4 -4
  61. package/src/slides/index.js +10 -10
  62. package/src/slides/interview.js +2 -2
  63. package/src/slides/job.js +3 -3
  64. package/src/slides/level.js +32 -0
  65. package/src/slides/overview.js +9 -9
  66. package/src/slides/progress.js +13 -13
  67. package/src/types.js +1 -1
  68. package/templates/job.template.md +2 -2
  69. package/src/commands/grade.js +0 -60
  70. package/src/formatters/grade/shared.js +0 -86
  71. package/src/pages/grade.js +0 -122
  72. package/src/slides/grade.js +0 -32
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Builder component for discipline/grade/track selection pages
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} grade - Selected grade
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
- grade: urlParams.get("grade") || "",
81
+ level: urlParams.get("level") || "",
82
82
  });
83
83
 
84
- const sortedGrades = [...data.grades].sort((a, b) => a.level - b.level);
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, grade }) => {
159
- // Track is now optional - only discipline and grade are required
160
- if (!discipline || !grade) {
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 gradeObj = data.grades.find((g) => g.id === grade);
171
+ const levelObj = data.levels.find((g) => g.id === level);
172
172
 
173
- if (!disciplineObj || !gradeObj) {
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
- grade: gradeObj,
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, grade } = selection.get();
197
- window.location.hash = detailPath({ discipline, track, grade });
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
- // Grade selector (second)
237
+ // Level selector (second)
238
238
  div(
239
239
  { className: "form-group" },
240
- label({ className: "form-label" }, labels.grade || "Grade"),
240
+ label({ className: "form-label" }, labels.level || "Level"),
241
241
  createSelectWithValue({
242
- id: "grade-select",
243
- items: sortedGrades,
244
- initialValue: selection.get().grade,
245
- placeholder: "Select a grade...",
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, grade: value }));
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.grade) {
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, grade, track } = selection;
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(grade.id, "grade"),
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.nextGrade
397
+ preview.nextLevel
398
398
  ? div(
399
399
  { className: "path-item" },
400
400
  span({ className: "path-icon" }, "📈"),
401
401
  span(
402
402
  {},
403
- `Next Grade: ${preview.nextGrade.id} - ${preview.nextGrade.name}`,
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 grade!"),
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
- getSkillLevelIndex,
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 ? getSkillLevelIndex(currentSkill.level) : 0,
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 ? getSkillLevelIndex(targetSkill.level) : 0,
109
+ value: targetSkill ? getSkillProficiencyIndex(targetSkill.level) : 0,
110
110
  maxValue: 5,
111
111
  description: targetSkill
112
112
  ? `${formatLevel(targetSkill.type)} - ${formatLevel(targetSkill.level)}`
@@ -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
- SKILL_LEVEL_ORDER,
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" ? SKILL_LEVEL_ORDER : BEHAVIOUR_MATURITY_ORDER;
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)]),
@@ -59,7 +59,7 @@ export function createFixedGrid(columns, children, options = {}) {
59
59
  }
60
60
 
61
61
  /**
62
- * Create a grid for form selectors (discipline/grade/track dropdowns)
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
- getSkillLevelIndex,
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: getSkillLevelIndex(skill.level),
36
+ value: getSkillProficiencyIndex(skill.proficiency),
37
37
  maxValue: 5,
38
- description: `${formatLevel(skill.type)} skill - ${formatLevel(skill.level)}`,
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 { getSkillLevelIndex } from "../lib/render.js";
18
+ import { getSkillProficiencyIndex } from "../lib/render.js";
19
19
  import { createLevelCell } from "./detail.js";
20
20
  import { createBadge } from "./card.js";
21
- import { SKILL_LEVEL_ORDER } from "@forwardimpact/map/levels";
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 = SKILL_LEVEL_ORDER.indexOf(a.level);
32
- const levelB = SKILL_LEVEL_ORDER.indexOf(b.level);
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 = getSkillLevelIndex(skill.level);
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.level),
72
+ createLevelCell(levelIndex, 5, skill.proficiency),
73
73
  td(
74
74
  { className: "skill-description" },
75
- truncate(skill.levelDescription, 80),
75
+ truncate(skill.proficiencyDescription, 80),
76
76
  ),
77
77
  );
78
78
  });
@@ -63,20 +63,20 @@
63
63
  margin: 0;
64
64
  }
65
65
 
66
- /* Grades timeline */
67
- .grades-timeline {
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
- .grade-timeline-item {
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
- .grade-level-marker {
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
- .grade-timeline-content {
93
+ .level-timeline-content {
94
94
  flex: 1;
95
95
  }
96
96
  }
@@ -55,10 +55,10 @@ export {
55
55
  disciplineToMicrodata,
56
56
  } from "./discipline/microdata.js";
57
57
 
58
- // Grade formatters
59
- export { gradeListToMarkdown, gradeToMarkdown } from "./grade/markdown.js";
60
- export { gradeToDOM } from "./grade/dom.js";
61
- export { gradeListToMicrodata, gradeToMicrodata } from "./grade/microdata.js";
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
- gradeToJsonLd,
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} gradeId
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.grade
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
- grade,
134
+ level,
135
135
  track,
136
136
  skills,
137
137
  behaviours,
138
138
  questions,
139
139
  interviewType = "mission",
140
140
  }) {
141
- if (!discipline || !grade) return null;
141
+ if (!discipline || !level) return null;
142
142
 
143
143
  const job = getOrCreateJob({
144
144
  discipline,
145
- grade,
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
- gradeId: grade.id,
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.grade
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.grades] - All grades for validation
237
+ * @param {Array} [params.levels] - All levels for validation
238
238
  * @returns {InterviewBuilderPreview}
239
239
  */
240
240
  export function prepareInterviewBuilderPreview({
241
241
  discipline,
242
- grade,
242
+ level,
243
243
  track,
244
244
  behaviourCount,
245
- grades,
245
+ levels,
246
246
  }) {
247
247
  // Track is optional (null = generalist)
248
- if (!discipline || !grade) {
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
- grade,
260
+ level,
261
261
  track,
262
- grades,
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, grade, track);
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} gradeId
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.grade
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
- grade,
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 || !grade) return null;
321
+ if (!discipline || !level) return null;
322
322
 
323
323
  const job = getOrCreateJob({
324
324
  discipline,
325
- grade,
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
- gradeId: grade.id,
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.grade - The grade
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, grade, track }) {
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 level
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 level first, then by ordinalRank
89
- const highestLevel = derivedResponsibilities[0].level;
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 level
91
+ // Filter responsibilities to only the highest proficiency
92
92
  const topResponsibilities = derivedResponsibilities.filter(
93
- (r) => r.level === highestLevel,
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.level !== highestLevel) continue;
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
- levelDescription: s.levelDescription || "",
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
- (grade.qualificationSummary || "").replace(
126
+ (level.qualificationSummary || "").replace(
127
127
  /\{typicalExperienceRange\}/g,
128
- grade.typicalExperienceRange || "",
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
- gradeId: grade.id,
141
- typicalExperienceRange: grade.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, { levelDescription: "optional" }),
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.grade - The grade
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, grade, track },
173
+ { job, discipline, level, track },
174
174
  template,
175
175
  ) {
176
- const data = prepareJobDescriptionData({ job, discipline, grade, track });
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.grade] - Grade entity for job description
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
- grade,
43
+ level,
44
44
  track,
45
45
  jobTemplate,
46
46
  } = options;
47
47
 
48
- const hasEntities = discipline && grade && jobTemplate;
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: `#/grade/${view.gradeId}` }, view.gradeId),
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
- grade,
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
- grade,
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.grade - The grade
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
- grade,
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
- grade,
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.grade - The grade
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
- grade,
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
- grade,
272
+ level,
273
273
  track,
274
274
  },
275
275
  template,