@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.
- package/README.md +3 -3
- package/bin/fit-pathway.js +22 -22
- package/package.json +3 -3
- package/src/commands/agent.js +7 -7
- package/src/commands/index.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/interview.js +8 -8
- package/src/commands/job.js +19 -19
- package/src/commands/level.js +60 -0
- package/src/commands/progress.js +20 -20
- package/src/commands/questions.js +3 -3
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/comparison-radar.js +3 -3
- package/src/components/detail.js +2 -2
- package/src/components/grid.js +1 -1
- package/src/components/radar-chart.js +3 -3
- package/src/components/skill-matrix.js +7 -7
- package/src/css/pages/landing.css +5 -5
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/shared.js +20 -20
- package/src/formatters/job/description.js +17 -17
- package/src/formatters/job/dom.js +12 -12
- package/src/formatters/job/markdown.js +7 -7
- package/src/formatters/json-ld.js +24 -24
- package/src/formatters/{grade → level}/dom.js +31 -27
- package/src/formatters/{grade → level}/markdown.js +19 -28
- package/src/formatters/{grade → level}/microdata.js +28 -38
- package/src/formatters/level/shared.js +86 -0
- package/src/formatters/progress/markdown.js +2 -2
- package/src/formatters/progress/shared.js +48 -48
- package/src/formatters/questions/markdown.js +8 -6
- package/src/formatters/questions/shared.js +7 -7
- package/src/formatters/skill/dom.js +4 -4
- package/src/formatters/skill/markdown.js +1 -1
- package/src/formatters/skill/microdata.js +3 -3
- package/src/formatters/skill/shared.js +2 -2
- package/src/handout-main.js +12 -12
- package/src/index.html +1 -1
- package/src/lib/card-mappers.js +16 -16
- package/src/lib/cli-command.js +3 -3
- package/src/lib/cli-output.js +2 -2
- package/src/lib/job-cache.js +11 -11
- package/src/lib/render.js +5 -5
- package/src/lib/state.js +2 -2
- package/src/lib/yaml-loader.js +9 -9
- package/src/main.js +10 -10
- package/src/pages/agent-builder.js +11 -11
- package/src/pages/assessment-results.js +27 -23
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +8 -8
- package/src/pages/job-builder.js +6 -6
- package/src/pages/job.js +7 -7
- package/src/pages/landing.js +8 -8
- package/src/pages/level.js +122 -0
- package/src/pages/progress-builder.js +8 -8
- package/src/pages/progress.js +74 -74
- package/src/pages/self-assessment.js +7 -7
- package/src/slide-main.js +22 -22
- package/src/slides/chapter.js +4 -4
- package/src/slides/index.js +10 -10
- package/src/slides/interview.js +2 -2
- package/src/slides/job.js +3 -3
- package/src/slides/level.js +32 -0
- package/src/slides/overview.js +9 -9
- package/src/slides/progress.js +13 -13
- package/src/types.js +1 -1
- package/templates/job.template.md +2 -2
- package/src/commands/grade.js +0 -60
- package/src/formatters/grade/shared.js +0 -86
- package/src/pages/grade.js +0 -122
- package/src/slides/grade.js +0 -32
|
@@ -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 {
|
|
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.
|
|
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 =
|
|
50
|
-
const levelB =
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
151
|
-
* @param {Object}
|
|
150
|
+
* Generate JSON-LD for a level entity
|
|
151
|
+
* @param {Object} level - Raw level entity
|
|
152
152
|
* @returns {Object}
|
|
153
153
|
*/
|
|
154
|
-
export function
|
|
154
|
+
export function levelToJsonLd(level) {
|
|
155
155
|
return {
|
|
156
|
-
...baseJsonLd("
|
|
157
|
-
identifier:
|
|
158
|
-
name:
|
|
159
|
-
...(
|
|
160
|
-
...(
|
|
161
|
-
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
|
-
...(
|
|
164
|
-
|
|
165
|
-
"@type": "
|
|
166
|
-
primary: `${VOCAB_BASE}${
|
|
167
|
-
secondary: `${VOCAB_BASE}${
|
|
168
|
-
broad: `${VOCAB_BASE}${
|
|
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
|
-
...(
|
|
172
|
-
baseBehaviourMaturity: `${VOCAB_BASE}${
|
|
171
|
+
...(level.baseBehaviourMaturity && {
|
|
172
|
+
baseBehaviourMaturity: `${VOCAB_BASE}${level.baseBehaviourMaturity}`,
|
|
173
173
|
}),
|
|
174
174
|
};
|
|
175
175
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
|
|
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 {
|
|
28
|
-
import { createJsonLdScript,
|
|
27
|
+
import { prepareLevelDetail } from "./shared.js";
|
|
28
|
+
import { createJsonLdScript, levelToJsonLd } from "../json-ld.js";
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* Format
|
|
32
|
-
* @param {Object}
|
|
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
|
|
39
|
-
const view =
|
|
40
|
-
const emoji = framework ? getConceptEmoji(framework, "
|
|
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
|
|
42
|
+
{ className: "detail-page level-detail" },
|
|
43
43
|
// JSON-LD structured data
|
|
44
|
-
createJsonLdScript(
|
|
44
|
+
createJsonLdScript(levelToJsonLd(level)),
|
|
45
45
|
// Header
|
|
46
46
|
div(
|
|
47
47
|
{ className: "page-header" },
|
|
48
|
-
showBackLink ? createBackLink("/
|
|
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: "
|
|
65
|
-
paramValue:
|
|
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
|
|
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
|
|
119
|
+
// Base Skill Proficiencies column
|
|
120
120
|
div(
|
|
121
121
|
{ className: "column" },
|
|
122
|
-
heading2({ className: "section-title" }, "Base Skill
|
|
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.
|
|
133
|
+
view.baseSkillProficiencies?.primary
|
|
134
134
|
? createLevelDots(
|
|
135
|
-
|
|
136
|
-
|
|
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.
|
|
151
|
+
view.baseSkillProficiencies?.secondary
|
|
150
152
|
? createLevelDots(
|
|
151
|
-
|
|
152
|
-
view.
|
|
153
|
+
SKILL_PROFICIENCY_ORDER.indexOf(
|
|
154
|
+
view.baseSkillProficiencies.secondary,
|
|
153
155
|
),
|
|
154
|
-
|
|
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.
|
|
166
|
+
view.baseSkillProficiencies?.broad
|
|
165
167
|
? createLevelDots(
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
*
|
|
2
|
+
* Level formatting for markdown/CLI output
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { tableToMarkdown, capitalize } from "../shared.js";
|
|
6
|
-
import {
|
|
6
|
+
import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
|
|
7
7
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Format
|
|
11
|
-
* @param {Array}
|
|
10
|
+
* Format level list as markdown
|
|
11
|
+
* @param {Array} levels - Raw level entities
|
|
12
12
|
* @param {Object} [framework] - Framework config for emojis
|
|
13
13
|
* @returns {string}
|
|
14
14
|
*/
|
|
15
|
-
export function
|
|
16
|
-
const { items } =
|
|
17
|
-
const emoji = framework ? getConceptEmoji(framework, "
|
|
18
|
-
const lines = [`# ${emoji}
|
|
15
|
+
export function levelListToMarkdown(levels, framework) {
|
|
16
|
+
const { items } = prepareLevelsList(levels);
|
|
17
|
+
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
18
|
+
const lines = [`# ${emoji} Levels`, ""];
|
|
19
19
|
|
|
20
20
|
const rows = items.map((g) => [
|
|
21
21
|
g.id,
|
|
22
22
|
g.displayName,
|
|
23
23
|
g.typicalExperienceRange || "-",
|
|
24
|
-
capitalize(g.
|
|
24
|
+
capitalize(g.baseSkillProficiencies?.primary || "-"),
|
|
25
25
|
]);
|
|
26
26
|
|
|
27
27
|
lines.push(tableToMarkdown(["ID", "Name", "Years", "Primary Level"], rows));
|
|
@@ -31,14 +31,14 @@ export function gradeListToMarkdown(grades, framework) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Format
|
|
35
|
-
* @param {Object}
|
|
34
|
+
* Format level detail as markdown
|
|
35
|
+
* @param {Object} level - Raw level entity
|
|
36
36
|
* @param {Object} [framework] - Framework config for emojis
|
|
37
37
|
* @returns {string}
|
|
38
38
|
*/
|
|
39
|
-
export function
|
|
40
|
-
const view =
|
|
41
|
-
const emoji = framework ? getConceptEmoji(framework, "
|
|
39
|
+
export function levelToMarkdown(level, framework) {
|
|
40
|
+
const view = prepareLevelDetail(level);
|
|
41
|
+
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
42
42
|
const lines = [`# ${emoji} ${view.displayName} (${view.id})`, ""];
|
|
43
43
|
|
|
44
44
|
if (view.typicalExperienceRange) {
|
|
@@ -57,27 +57,18 @@ export function gradeToMarkdown(grade, framework) {
|
|
|
57
57
|
lines.push("");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Base skill
|
|
61
|
-
lines.push("## Base Skill
|
|
62
|
-
const skillRows = Object.entries(view.
|
|
60
|
+
// Base skill proficiencies
|
|
61
|
+
lines.push("## Base Skill Proficiencies", "");
|
|
62
|
+
const skillRows = Object.entries(view.baseSkillProficiencies).map(
|
|
63
63
|
([type, level]) => [capitalize(type), capitalize(level)],
|
|
64
64
|
);
|
|
65
65
|
lines.push(tableToMarkdown(["Skill Type", "Level"], skillRows));
|
|
66
66
|
lines.push("");
|
|
67
67
|
|
|
68
68
|
// Base behaviour maturity
|
|
69
|
-
if (
|
|
70
|
-
view.baseBehaviourMaturity &&
|
|
71
|
-
Object.keys(view.baseBehaviourMaturity).length > 0
|
|
72
|
-
) {
|
|
69
|
+
if (view.baseBehaviourMaturity) {
|
|
73
70
|
lines.push("## Base Behaviour Maturity", "");
|
|
74
|
-
|
|
75
|
-
([type, maturity]) => [
|
|
76
|
-
capitalize(type),
|
|
77
|
-
capitalize(maturity.replace(/_/g, " ")),
|
|
78
|
-
],
|
|
79
|
-
);
|
|
80
|
-
lines.push(tableToMarkdown(["Type", "Maturity"], behaviourRows));
|
|
71
|
+
lines.push(capitalize(view.baseBehaviourMaturity.replace(/_/g, " ")));
|
|
81
72
|
lines.push("");
|
|
82
73
|
}
|
|
83
74
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Level formatting for microdata HTML output
|
|
3
3
|
*
|
|
4
|
-
* Generates clean, class-less HTML with microdata aligned with
|
|
4
|
+
* Generates clean, class-less HTML with microdata aligned with levels.schema.json
|
|
5
5
|
* RDF vocab: https://www.forwardimpact.team/schema/rdf/
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -16,19 +16,19 @@ import {
|
|
|
16
16
|
formatLevelName,
|
|
17
17
|
htmlDocument,
|
|
18
18
|
} from "../microdata-shared.js";
|
|
19
|
-
import {
|
|
19
|
+
import { prepareLevelsList, prepareLevelDetail } from "./shared.js";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Format
|
|
23
|
-
* @param {Array}
|
|
22
|
+
* Format level list as microdata HTML
|
|
23
|
+
* @param {Array} levels - Raw level entities
|
|
24
24
|
* @returns {string} HTML with microdata
|
|
25
25
|
*/
|
|
26
|
-
export function
|
|
27
|
-
const { items } =
|
|
26
|
+
export function levelListToMicrodata(levels) {
|
|
27
|
+
const { items } = prepareLevelsList(levels);
|
|
28
28
|
|
|
29
29
|
const content = items
|
|
30
30
|
.map(
|
|
31
|
-
(g) => `${openTag("article", { itemtype: "
|
|
31
|
+
(g) => `${openTag("article", { itemtype: "Level", itemid: `#${g.id}` })}
|
|
32
32
|
${prop("h2", "id", g.id)}
|
|
33
33
|
<p>${escapeHtml(g.displayName)}</p>
|
|
34
34
|
${g.typicalExperienceRange ? prop("p", "typicalExperienceRange", g.typicalExperienceRange) : ""}
|
|
@@ -38,21 +38,21 @@ ${metaTag("ordinalRank", String(g.ordinalRank))}
|
|
|
38
38
|
.join("\n");
|
|
39
39
|
|
|
40
40
|
return htmlDocument(
|
|
41
|
-
"
|
|
41
|
+
"Levels",
|
|
42
42
|
`<main>
|
|
43
|
-
<h1>
|
|
43
|
+
<h1>Levels</h1>
|
|
44
44
|
${content}
|
|
45
45
|
</main>`,
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Format
|
|
51
|
-
* @param {Object}
|
|
50
|
+
* Format level detail as microdata HTML
|
|
51
|
+
* @param {Object} level - Raw level entity
|
|
52
52
|
* @returns {string} HTML with microdata
|
|
53
53
|
*/
|
|
54
|
-
export function
|
|
55
|
-
const view =
|
|
54
|
+
export function levelToMicrodata(level) {
|
|
55
|
+
const view = prepareLevelDetail(level);
|
|
56
56
|
|
|
57
57
|
if (!view) return "";
|
|
58
58
|
|
|
@@ -78,9 +78,12 @@ export function gradeToMicrodata(grade) {
|
|
|
78
78
|
sections.push(section("Titles", dl(titlePairs), 2));
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Base skill
|
|
82
|
-
if (
|
|
83
|
-
|
|
81
|
+
// Base skill proficiencies - using BaseSkillProficiencies itemtype
|
|
82
|
+
if (
|
|
83
|
+
view.baseSkillProficiencies &&
|
|
84
|
+
Object.keys(view.baseSkillProficiencies).length > 0
|
|
85
|
+
) {
|
|
86
|
+
const levelPairs = Object.entries(view.baseSkillProficiencies).map(
|
|
84
87
|
([type, level]) => ({
|
|
85
88
|
term: formatLevelName(type),
|
|
86
89
|
definition: formatLevelName(level),
|
|
@@ -89,8 +92,8 @@ export function gradeToMicrodata(grade) {
|
|
|
89
92
|
);
|
|
90
93
|
sections.push(
|
|
91
94
|
section(
|
|
92
|
-
"Base Skill
|
|
93
|
-
`${openTag("div", { itemtype: "
|
|
95
|
+
"Base Skill Proficiencies",
|
|
96
|
+
`${openTag("div", { itemtype: "BaseSkillProficiencies", itemprop: "baseSkillProficiencies" })}
|
|
94
97
|
${dl(levelPairs)}
|
|
95
98
|
</div>`,
|
|
96
99
|
2,
|
|
@@ -99,26 +102,13 @@ ${dl(levelPairs)}
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
// Base behaviour maturity - link to BehaviourMaturity
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
) {
|
|
106
|
-
const maturityPairs = Object.entries(view.baseBehaviourMaturity).map(
|
|
107
|
-
([type, maturity]) => ({
|
|
108
|
-
term: formatLevelName(type),
|
|
109
|
-
definition: formatLevelName(maturity),
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
// Handle both single value and object cases
|
|
113
|
-
const maturityContent =
|
|
114
|
-
typeof grade.baseBehaviourMaturity === "string"
|
|
115
|
-
? `${linkTag("baseBehaviourMaturity", `#${grade.baseBehaviourMaturity}`)}
|
|
116
|
-
<p>${formatLevelName(grade.baseBehaviourMaturity)}</p>`
|
|
117
|
-
: dl(maturityPairs);
|
|
105
|
+
if (view.baseBehaviourMaturity) {
|
|
106
|
+
const maturityContent = `${linkTag("baseBehaviourMaturity", `#${level.baseBehaviourMaturity}`)}
|
|
107
|
+
<p>${formatLevelName(level.baseBehaviourMaturity)}</p>`;
|
|
118
108
|
sections.push(section("Base Behaviour Maturity", maturityContent, 2));
|
|
119
109
|
}
|
|
120
110
|
|
|
121
|
-
// Expectations - using
|
|
111
|
+
// Expectations - using LevelExpectations itemtype
|
|
122
112
|
if (view.expectations && Object.keys(view.expectations).length > 0) {
|
|
123
113
|
const expectationPairs = Object.entries(view.expectations).map(
|
|
124
114
|
([key, value]) => ({
|
|
@@ -130,7 +120,7 @@ ${dl(levelPairs)}
|
|
|
130
120
|
sections.push(
|
|
131
121
|
section(
|
|
132
122
|
"Expectations",
|
|
133
|
-
`${openTag("div", { itemtype: "
|
|
123
|
+
`${openTag("div", { itemtype: "LevelExpectations", itemprop: "expectations" })}
|
|
134
124
|
${dl(expectationPairs)}
|
|
135
125
|
</div>`,
|
|
136
126
|
2,
|
|
@@ -139,7 +129,7 @@ ${dl(expectationPairs)}
|
|
|
139
129
|
}
|
|
140
130
|
|
|
141
131
|
const body = `<main>
|
|
142
|
-
${openTag("article", { itemtype: "
|
|
132
|
+
${openTag("article", { itemtype: "Level", itemid: `#${view.id}` })}
|
|
143
133
|
<h1>${prop("span", "id", view.id)} — ${escapeHtml(view.displayName)}</h1>
|
|
144
134
|
${metaTag("ordinalRank", String(view.ordinalRank))}
|
|
145
135
|
${view.typicalExperienceRange ? prop("p", "typicalExperienceRange", `Experience: ${view.typicalExperienceRange}`) : ""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting level data across DOM and markdown outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get level display name (shows both professional and management titles)
|
|
9
|
+
* @param {Object} level
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function getLevelDisplayName(level) {
|
|
13
|
+
if (level.professionalTitle && level.managementTitle) {
|
|
14
|
+
if (level.professionalTitle === level.managementTitle) {
|
|
15
|
+
return level.professionalTitle;
|
|
16
|
+
}
|
|
17
|
+
return `${level.professionalTitle} / ${level.managementTitle}`;
|
|
18
|
+
}
|
|
19
|
+
return level.professionalTitle || level.managementTitle || level.id;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} LevelListItem
|
|
24
|
+
* @property {string} id
|
|
25
|
+
* @property {string} displayName
|
|
26
|
+
* @property {number} ordinalRank
|
|
27
|
+
* @property {string|null} typicalExperienceRange
|
|
28
|
+
* @property {Object} baseSkillProficiencies
|
|
29
|
+
* @property {string|null} impactScope
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Transform levels for list view
|
|
34
|
+
* @param {Array} levels - Raw level entities
|
|
35
|
+
* @returns {{ items: LevelListItem[] }}
|
|
36
|
+
*/
|
|
37
|
+
export function prepareLevelsList(levels) {
|
|
38
|
+
const sortedLevels = [...levels].sort(
|
|
39
|
+
(a, b) => a.ordinalRank - b.ordinalRank,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const items = sortedLevels.map((level) => ({
|
|
43
|
+
id: level.id,
|
|
44
|
+
displayName: getLevelDisplayName(level),
|
|
45
|
+
ordinalRank: level.ordinalRank,
|
|
46
|
+
typicalExperienceRange: level.typicalExperienceRange || null,
|
|
47
|
+
baseSkillProficiencies: level.baseSkillProficiencies || {},
|
|
48
|
+
impactScope: level.expectations?.impactScope || null,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return { items };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Object} LevelDetailView
|
|
56
|
+
* @property {string} id
|
|
57
|
+
* @property {string} displayName
|
|
58
|
+
* @property {string} professionalTitle
|
|
59
|
+
* @property {string} managementTitle
|
|
60
|
+
* @property {number} ordinalRank
|
|
61
|
+
* @property {string|null} typicalExperienceRange
|
|
62
|
+
* @property {Object} baseSkillProficiencies
|
|
63
|
+
* @property {Object} baseBehaviourMaturity
|
|
64
|
+
* @property {Object} expectations
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Transform level for detail view
|
|
69
|
+
* @param {Object} level - Raw level entity
|
|
70
|
+
* @returns {LevelDetailView|null}
|
|
71
|
+
*/
|
|
72
|
+
export function prepareLevelDetail(level) {
|
|
73
|
+
if (!level) return null;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
id: level.id,
|
|
77
|
+
displayName: getLevelDisplayName(level),
|
|
78
|
+
professionalTitle: level.professionalTitle || null,
|
|
79
|
+
managementTitle: level.managementTitle || null,
|
|
80
|
+
ordinalRank: level.ordinalRank,
|
|
81
|
+
typicalExperienceRange: level.typicalExperienceRange || null,
|
|
82
|
+
baseSkillProficiencies: level.baseSkillProficiencies || {},
|
|
83
|
+
baseBehaviourMaturity: level.baseBehaviourMaturity || {},
|
|
84
|
+
expectations: level.expectations || {},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -29,7 +29,7 @@ export function progressToMarkdown(view) {
|
|
|
29
29
|
|
|
30
30
|
// Skill changes
|
|
31
31
|
const skillsWithChanges = view.skillChanges.filter(
|
|
32
|
-
(s) => s.
|
|
32
|
+
(s) => s.proficiencyChange !== 0,
|
|
33
33
|
);
|
|
34
34
|
if (skillsWithChanges.length > 0) {
|
|
35
35
|
lines.push("## Skill Changes", "");
|
|
@@ -39,7 +39,7 @@ export function progressToMarkdown(view) {
|
|
|
39
39
|
formatLevel(s.fromLevel || "-"),
|
|
40
40
|
"→",
|
|
41
41
|
formatLevel(s.toLevel),
|
|
42
|
-
formatChange(s.
|
|
42
|
+
formatChange(s.proficiencyChange),
|
|
43
43
|
]);
|
|
44
44
|
lines.push(
|
|
45
45
|
tableToMarkdown(["Skill", "Type", "From", "", "To", "Change"], skillRows),
|