@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
@@ -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: {
@@ -10,7 +10,7 @@
10
10
 
11
11
  import Mustache from "mustache";
12
12
 
13
- import { BEHAVIOUR_MATURITY_ORDER } from "@forwardimpact/schema/levels";
13
+ import { BEHAVIOUR_MATURITY_ORDER } from "@forwardimpact/map/levels";
14
14
  import { trimValue, trimFields } from "../shared.js";
15
15
 
16
16
  /**
@@ -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,
@@ -9,7 +9,7 @@ import {
9
9
  } from "../shared.js";
10
10
  import { formatLevel } from "../../lib/render.js";
11
11
  import { formatJobDescription } from "./description.js";
12
- import { SKILL_LEVEL_ORDER } from "@forwardimpact/schema/levels";
12
+ import { SKILL_PROFICIENCY_ORDER } from "@forwardimpact/map/levels";
13
13
  import { toolkitToMarkdown } from "../toolkit/markdown.js";
14
14
 
15
15
  /**
@@ -23,7 +23,7 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
23
23
  const lines = [
24
24
  `# ${view.title}`,
25
25
  "",
26
- `${view.disciplineName} × ${view.gradeId} × ${view.trackName}`,
26
+ `${view.disciplineName} × ${view.levelId} × ${view.trackName}`,
27
27
  "",
28
28
  ];
29
29
 
@@ -46,8 +46,8 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
46
46
  // Skill Matrix - sorted by level descending
47
47
  lines.push("## Skill Matrix", "");
48
48
  const sortedSkills = [...view.skillMatrix].sort((a, b) => {
49
- const levelA = SKILL_LEVEL_ORDER.indexOf(a.level);
50
- const levelB = SKILL_LEVEL_ORDER.indexOf(b.level);
49
+ const levelA = SKILL_PROFICIENCY_ORDER.indexOf(a.level);
50
+ const levelB = SKILL_PROFICIENCY_ORDER.indexOf(b.level);
51
51
  if (levelB !== levelA) {
52
52
  return levelB - levelA;
53
53
  }
@@ -55,7 +55,7 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
55
55
  });
56
56
  const skillRows = sortedSkills.map((s) => [
57
57
  s.skillName,
58
- formatLevel(s.level),
58
+ formatLevel(s.proficiency),
59
59
  ]);
60
60
  lines.push(tableToMarkdown(["Skill", "Level"], skillRows));
61
61
  lines.push("");
@@ -86,7 +86,7 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
86
86
  }
87
87
 
88
88
  // Job Description (copyable markdown)
89
- if (entities.discipline && entities.grade && jobTemplate) {
89
+ if (entities.discipline && entities.level && jobTemplate) {
90
90
  lines.push("---", "");
91
91
  lines.push("## Job Description", "");
92
92
  lines.push("```markdown");
@@ -101,7 +101,7 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
101
101
  derivedResponsibilities: view.derivedResponsibilities,
102
102
  },
103
103
  discipline: entities.discipline,
104
- grade: entities.grade,
104
+ level: entities.level,
105
105
  track: entities.track,
106
106
  },
107
107
  jobTemplate,
@@ -51,13 +51,13 @@ export function skillToJsonLd(skill, { capabilities = [] } = {}) {
51
51
  capability: skill.capability,
52
52
  ...(capability && { capabilityName: capability.name }),
53
53
  ...(skill.isHumanOnly && { isHumanOnly: true }),
54
- levelDescriptions: Object.entries(skill.levelDescriptions || {}).map(
55
- ([level, description]) => ({
56
- "@type": "SkillLevelDescription",
57
- level: `${VOCAB_BASE}${level}`,
58
- description,
59
- }),
60
- ),
54
+ proficiencyDescriptions: Object.entries(
55
+ skill.proficiencyDescriptions || {},
56
+ ).map(([level, description]) => ({
57
+ "@type": "SkillProficiencyDescription",
58
+ level: `${VOCAB_BASE}${level}`,
59
+ description,
60
+ })),
61
61
  };
62
62
  }
63
63
 
@@ -147,29 +147,29 @@ export function trackToJsonLd(track) {
147
147
  }
148
148
 
149
149
  /**
150
- * Generate JSON-LD for a grade entity
151
- * @param {Object} grade - Raw grade entity
150
+ * Generate JSON-LD for a level entity
151
+ * @param {Object} level - Raw level entity
152
152
  * @returns {Object}
153
153
  */
154
- export function gradeToJsonLd(grade) {
154
+ export function levelToJsonLd(level) {
155
155
  return {
156
- ...baseJsonLd("Grade", grade.id),
157
- identifier: grade.id,
158
- name: grade.displayName || grade.name,
159
- ...(grade.ordinalRank && { ordinalRank: grade.ordinalRank }),
160
- ...(grade.typicalExperienceRange && {
161
- typicalExperienceRange: grade.typicalExperienceRange,
156
+ ...baseJsonLd("Level", level.id),
157
+ identifier: level.id,
158
+ name: level.displayName || level.name,
159
+ ...(level.ordinalRank && { ordinalRank: level.ordinalRank }),
160
+ ...(level.typicalExperienceRange && {
161
+ typicalExperienceRange: level.typicalExperienceRange,
162
162
  }),
163
- ...(grade.baseSkillLevels && {
164
- baseSkillLevels: {
165
- "@type": "BaseSkillLevels",
166
- primary: `${VOCAB_BASE}${grade.baseSkillLevels.primary}`,
167
- secondary: `${VOCAB_BASE}${grade.baseSkillLevels.secondary}`,
168
- broad: `${VOCAB_BASE}${grade.baseSkillLevels.broad}`,
163
+ ...(level.baseSkillProficiencies && {
164
+ baseSkillProficiencies: {
165
+ "@type": "BaseSkillProficiencies",
166
+ primary: `${VOCAB_BASE}${level.baseSkillProficiencies.primary}`,
167
+ secondary: `${VOCAB_BASE}${level.baseSkillProficiencies.secondary}`,
168
+ broad: `${VOCAB_BASE}${level.baseSkillProficiencies.broad}`,
169
169
  },
170
170
  }),
171
- ...(grade.baseBehaviourMaturity && {
172
- baseBehaviourMaturity: `${VOCAB_BASE}${grade.baseBehaviourMaturity}`,
171
+ ...(level.baseBehaviourMaturity && {
172
+ baseBehaviourMaturity: `${VOCAB_BASE}${level.baseBehaviourMaturity}`,
173
173
  }),
174
174
  };
175
175
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Grade formatting for DOM output
2
+ * Level formatting for DOM output
3
3
  */
4
4
 
5
5
  import {
@@ -19,33 +19,33 @@ import {
19
19
  import { createBackLink } from "../../components/nav.js";
20
20
  import { createLevelDots } from "../../components/detail.js";
21
21
  import {
22
- SKILL_LEVEL_ORDER,
22
+ SKILL_PROFICIENCY_ORDER,
23
23
  BEHAVIOUR_MATURITY_ORDER,
24
24
  getConceptEmoji,
25
- } from "@forwardimpact/schema/levels";
25
+ } from "@forwardimpact/map/levels";
26
26
  import { createJobBuilderButton } from "../../components/action-buttons.js";
27
- import { prepareGradeDetail } from "./shared.js";
28
- import { createJsonLdScript, gradeToJsonLd } from "../json-ld.js";
27
+ import { prepareLevelDetail } from "./shared.js";
28
+ import { createJsonLdScript, levelToJsonLd } from "../json-ld.js";
29
29
 
30
30
  /**
31
- * Format grade detail as DOM elements
32
- * @param {Object} grade - Raw grade entity
31
+ * Format level detail as DOM elements
32
+ * @param {Object} level - Raw level entity
33
33
  * @param {Object} options - Formatting options
34
34
  * @param {Object} [options.framework] - Framework config for emojis
35
35
  * @param {boolean} [options.showBackLink=true] - Whether to show back navigation link
36
36
  * @returns {HTMLElement}
37
37
  */
38
- export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
39
- const view = prepareGradeDetail(grade);
40
- const emoji = framework ? getConceptEmoji(framework, "grade") : "📊";
38
+ export function levelToDOM(level, { framework, showBackLink = true } = {}) {
39
+ const view = prepareLevelDetail(level);
40
+ const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
41
41
  return div(
42
- { className: "detail-page grade-detail" },
42
+ { className: "detail-page level-detail" },
43
43
  // JSON-LD structured data
44
- createJsonLdScript(gradeToJsonLd(grade)),
44
+ createJsonLdScript(levelToJsonLd(level)),
45
45
  // Header
46
46
  div(
47
47
  { className: "page-header" },
48
- showBackLink ? createBackLink("/grade", "← Back to Grades") : null,
48
+ showBackLink ? createBackLink("/level", "← Back to Levels") : null,
49
49
  heading1({ className: "page-title" }, `${emoji} `, view.displayName),
50
50
  div(
51
51
  { className: "page-meta" },
@@ -61,8 +61,8 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
61
61
  ? div(
62
62
  { className: "page-actions" },
63
63
  createJobBuilderButton({
64
- paramName: "grade",
65
- paramValue: grade.id,
64
+ paramName: "level",
65
+ paramValue: level.id,
66
66
  }),
67
67
  )
68
68
  : null,
@@ -111,15 +111,15 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
111
111
  )
112
112
  : null,
113
113
 
114
- // Base Skill Levels and Base Behaviour Maturity in two columns
114
+ // Base Skill Proficiencies and Base Behaviour Maturity in two columns
115
115
  div(
116
116
  { className: "detail-section" },
117
117
  div(
118
118
  { className: "content-columns" },
119
- // Base Skill Levels column
119
+ // Base Skill Proficiencies column
120
120
  div(
121
121
  { className: "column" },
122
- heading2({ className: "section-title" }, "Base Skill Levels"),
122
+ heading2({ className: "section-title" }, "Base Skill Proficiencies"),
123
123
  table(
124
124
  { className: "level-table" },
125
125
  thead({}, tr({}, th({}, "Type"), th({}, "Level"))),
@@ -130,10 +130,12 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
130
130
  td({}, span({ className: "badge badge-primary" }, "Primary")),
131
131
  td(
132
132
  {},
133
- view.baseSkillLevels?.primary
133
+ view.baseSkillProficiencies?.primary
134
134
  ? createLevelDots(
135
- SKILL_LEVEL_ORDER.indexOf(view.baseSkillLevels.primary),
136
- SKILL_LEVEL_ORDER.length,
135
+ SKILL_PROFICIENCY_ORDER.indexOf(
136
+ view.baseSkillProficiencies.primary,
137
+ ),
138
+ SKILL_PROFICIENCY_ORDER.length,
137
139
  )
138
140
  : span({ className: "text-muted" }, "—"),
139
141
  ),
@@ -146,12 +148,12 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
146
148
  ),
147
149
  td(
148
150
  {},
149
- view.baseSkillLevels?.secondary
151
+ view.baseSkillProficiencies?.secondary
150
152
  ? createLevelDots(
151
- SKILL_LEVEL_ORDER.indexOf(
152
- view.baseSkillLevels.secondary,
153
+ SKILL_PROFICIENCY_ORDER.indexOf(
154
+ view.baseSkillProficiencies.secondary,
153
155
  ),
154
- SKILL_LEVEL_ORDER.length,
156
+ SKILL_PROFICIENCY_ORDER.length,
155
157
  )
156
158
  : span({ className: "text-muted" }, "—"),
157
159
  ),
@@ -161,10 +163,12 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
161
163
  td({}, span({ className: "badge badge-broad" }, "Broad")),
162
164
  td(
163
165
  {},
164
- view.baseSkillLevels?.broad
166
+ view.baseSkillProficiencies?.broad
165
167
  ? createLevelDots(
166
- SKILL_LEVEL_ORDER.indexOf(view.baseSkillLevels.broad),
167
- SKILL_LEVEL_ORDER.length,
168
+ SKILL_PROFICIENCY_ORDER.indexOf(
169
+ view.baseSkillProficiencies.broad,
170
+ ),
171
+ SKILL_PROFICIENCY_ORDER.length,
168
172
  )
169
173
  : span({ className: "text-muted" }, "—"),
170
174
  ),