@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
@@ -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/map/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
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
  ),
@@ -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";
6
+ import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
7
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),