@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.
- package/README.md +3 -3
- package/bin/fit-pathway.js +28 -28
- package/package.json +4 -4
- package/src/commands/agent.js +8 -8
- package/src/commands/build.js +7 -7
- package/src/commands/dev.js +7 -7
- package/src/commands/driver.js +1 -1
- 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/commands/skill.js +1 -1
- package/src/commands/track.js +1 -1
- package/src/commands/update.js +12 -14
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/checklist.js +1 -1
- package/src/components/comparison-radar.js +3 -3
- package/src/components/detail.js +3 -3
- 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/behaviour/dom.js +1 -1
- package/src/formatters/discipline/dom.js +1 -1
- package/src/formatters/driver/dom.js +1 -1
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/dom.js +1 -1
- package/src/formatters/interview/markdown.js +1 -1
- package/src/formatters/interview/shared.js +20 -20
- package/src/formatters/job/description.js +18 -18
- 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 +32 -28
- package/src/formatters/{grade → level}/markdown.js +20 -29
- 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 +2 -2
- package/src/formatters/skill/microdata.js +3 -3
- package/src/formatters/skill/shared.js +3 -3
- package/src/formatters/track/dom.js +1 -1
- package/src/formatters/track/markdown.js +1 -1
- package/src/handout-main.js +13 -16
- package/src/handout.html +4 -4
- package/src/index.html +5 -5
- package/src/lib/card-mappers.js +17 -17
- 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 +6 -6
- 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/behaviour.js +1 -1
- package/src/pages/discipline.js +1 -1
- package/src/pages/driver.js +1 -1
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +9 -9
- package/src/pages/job-builder.js +6 -6
- package/src/pages/job.js +7 -7
- package/src/pages/landing.js +9 -9
- 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 +8 -8
- package/src/pages/skill.js +1 -4
- package/src/pages/stage.js +1 -1
- package/src/pages/tool.js +1 -1
- package/src/pages/track.js +1 -1
- package/src/slide-main.js +22 -22
- package/src/slides/chapter.js +4 -4
- package/src/slides/index.js +11 -11
- 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/slides.html +4 -4
- package/src/types.js +1 -1
- package/templates/install.template.sh +11 -8
- 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
|
@@ -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}
|
|
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.
|
|
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
|
-
|
|
134
|
+
level,
|
|
135
135
|
track,
|
|
136
136
|
skills,
|
|
137
137
|
behaviours,
|
|
138
138
|
questions,
|
|
139
139
|
interviewType = "mission",
|
|
140
140
|
}) {
|
|
141
|
-
if (!discipline || !
|
|
141
|
+
if (!discipline || !level) return null;
|
|
142
142
|
|
|
143
143
|
const job = getOrCreateJob({
|
|
144
144
|
discipline,
|
|
145
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
237
|
+
* @param {Array} [params.levels] - All levels for validation
|
|
238
238
|
* @returns {InterviewBuilderPreview}
|
|
239
239
|
*/
|
|
240
240
|
export function prepareInterviewBuilderPreview({
|
|
241
241
|
discipline,
|
|
242
|
-
|
|
242
|
+
level,
|
|
243
243
|
track,
|
|
244
244
|
behaviourCount,
|
|
245
|
-
|
|
245
|
+
levels,
|
|
246
246
|
}) {
|
|
247
247
|
// Track is optional (null = generalist)
|
|
248
|
-
if (!discipline || !
|
|
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
|
-
|
|
260
|
+
level,
|
|
261
261
|
track,
|
|
262
|
-
|
|
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,
|
|
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}
|
|
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.
|
|
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
|
-
|
|
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 || !
|
|
321
|
+
if (!discipline || !level) return null;
|
|
322
322
|
|
|
323
323
|
const job = getOrCreateJob({
|
|
324
324
|
discipline,
|
|
325
|
-
|
|
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
|
-
|
|
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/
|
|
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.
|
|
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,
|
|
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
|
|
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
|
|
89
|
-
const
|
|
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
|
|
91
|
+
// Filter responsibilities to only the highest proficiency
|
|
92
92
|
const topResponsibilities = derivedResponsibilities.filter(
|
|
93
|
-
(r) => r.
|
|
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.
|
|
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
|
-
|
|
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
|
-
(
|
|
126
|
+
(level.qualificationSummary || "").replace(
|
|
127
127
|
/\{typicalExperienceRange\}/g,
|
|
128
|
-
|
|
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
|
-
|
|
141
|
-
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, {
|
|
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.
|
|
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,
|
|
173
|
+
{ job, discipline, level, track },
|
|
174
174
|
template,
|
|
175
175
|
) {
|
|
176
|
-
const data = prepareJobDescriptionData({ job, discipline,
|
|
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.
|
|
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
|
-
|
|
43
|
+
level,
|
|
44
44
|
track,
|
|
45
45
|
jobTemplate,
|
|
46
46
|
} = options;
|
|
47
47
|
|
|
48
|
-
const hasEntities = discipline &&
|
|
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: `#/
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
} from "@forwardimpact/
|
|
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
|
),
|