@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
package/src/slide-main.js
CHANGED
|
@@ -18,7 +18,7 @@ import { renderSkillSlide } from "./slides/skill.js";
|
|
|
18
18
|
import { renderBehaviourSlide } from "./slides/behaviour.js";
|
|
19
19
|
import { renderDriverSlide } from "./slides/driver.js";
|
|
20
20
|
import { renderDisciplineSlide } from "./slides/discipline.js";
|
|
21
|
-
import {
|
|
21
|
+
import { renderLevelSlide } from "./slides/level.js";
|
|
22
22
|
import { renderTrackSlide } from "./slides/track.js";
|
|
23
23
|
import { renderJobSlide } from "./slides/job.js";
|
|
24
24
|
import { renderInterviewSlide } from "./slides/interview.js";
|
|
@@ -140,9 +140,9 @@ function setupRoutes() {
|
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
//
|
|
144
|
-
router.on("/
|
|
145
|
-
|
|
143
|
+
// Levels
|
|
144
|
+
router.on("/level/:id", (params) => {
|
|
145
|
+
renderLevelSlide({ render: renderSlide, data: getState().data, params });
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
// Tracks
|
|
@@ -150,12 +150,12 @@ function setupRoutes() {
|
|
|
150
150
|
renderTrackSlide({ render: renderSlide, data: getState().data, params });
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
// Jobs - new format: discipline/
|
|
154
|
-
router.on("/job/:discipline/:
|
|
153
|
+
// Jobs - new format: discipline/level/track (track optional)
|
|
154
|
+
router.on("/job/:discipline/:level/:track", (params) => {
|
|
155
155
|
renderJobSlide({ render: renderSlide, data: getState().data, params });
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
router.on("/job/:discipline/:
|
|
158
|
+
router.on("/job/:discipline/:level", (params) => {
|
|
159
159
|
renderJobSlide({
|
|
160
160
|
render: renderSlide,
|
|
161
161
|
data: getState().data,
|
|
@@ -163,8 +163,8 @@ function setupRoutes() {
|
|
|
163
163
|
});
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
-
// Interviews - new format: discipline/
|
|
167
|
-
router.on("/interview/:discipline/:
|
|
166
|
+
// Interviews - new format: discipline/level/track (track optional)
|
|
167
|
+
router.on("/interview/:discipline/:level/:track", (params) => {
|
|
168
168
|
renderInterviewSlide({
|
|
169
169
|
render: renderSlide,
|
|
170
170
|
data: getState().data,
|
|
@@ -172,7 +172,7 @@ function setupRoutes() {
|
|
|
172
172
|
});
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
router.on("/interview/:discipline/:
|
|
175
|
+
router.on("/interview/:discipline/:level", (params) => {
|
|
176
176
|
renderInterviewSlide({
|
|
177
177
|
render: renderSlide,
|
|
178
178
|
data: getState().data,
|
|
@@ -180,12 +180,12 @@ function setupRoutes() {
|
|
|
180
180
|
});
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
-
// Progress - new format: discipline/
|
|
184
|
-
router.on("/progress/:discipline/:
|
|
183
|
+
// Progress - new format: discipline/level/track (track optional)
|
|
184
|
+
router.on("/progress/:discipline/:level/:track", (params) => {
|
|
185
185
|
renderProgressSlide({ render: renderSlide, data: getState().data, params });
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
router.on("/progress/:discipline/:
|
|
188
|
+
router.on("/progress/:discipline/:level", (params) => {
|
|
189
189
|
renderProgressSlide({
|
|
190
190
|
render: renderSlide,
|
|
191
191
|
data: getState().data,
|
|
@@ -211,15 +211,15 @@ function buildSlideOrder(data) {
|
|
|
211
211
|
data.disciplines.forEach((d) => order.push(`/discipline/${d.id}`));
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
//
|
|
215
|
-
if (data.
|
|
214
|
+
// Levels (moved before Tracks)
|
|
215
|
+
if (data.levels && data.levels.length > 0) {
|
|
216
216
|
boundaries.push(order.length);
|
|
217
|
-
order.push("/chapter/
|
|
218
|
-
order.push("/overview/
|
|
219
|
-
data.
|
|
217
|
+
order.push("/chapter/level");
|
|
218
|
+
order.push("/overview/level");
|
|
219
|
+
data.levels.forEach((g) => order.push(`/level/${g.id}`));
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
// Tracks (moved after
|
|
222
|
+
// Tracks (moved after Levels)
|
|
223
223
|
if (data.tracks && data.tracks.length > 0) {
|
|
224
224
|
boundaries.push(order.length);
|
|
225
225
|
order.push("/chapter/track");
|
|
@@ -254,7 +254,7 @@ function buildSlideOrder(data) {
|
|
|
254
254
|
// Jobs
|
|
255
255
|
const jobs = generateAllJobs({
|
|
256
256
|
disciplines: data.disciplines,
|
|
257
|
-
|
|
257
|
+
levels: data.levels,
|
|
258
258
|
tracks: data.tracks,
|
|
259
259
|
skills: data.skills,
|
|
260
260
|
behaviours: data.behaviours,
|
|
@@ -267,8 +267,8 @@ function buildSlideOrder(data) {
|
|
|
267
267
|
jobs.forEach((job) =>
|
|
268
268
|
order.push(
|
|
269
269
|
job.track
|
|
270
|
-
? `/job/${job.discipline.id}/${job.
|
|
271
|
-
: `/job/${job.discipline.id}/${job.
|
|
270
|
+
? `/job/${job.discipline.id}/${job.level.id}/${job.track.id}`
|
|
271
|
+
: `/job/${job.discipline.id}/${job.level.id}`,
|
|
272
272
|
),
|
|
273
273
|
);
|
|
274
274
|
}
|
package/src/slides/chapter.js
CHANGED
|
@@ -38,10 +38,10 @@ export function renderChapterSlide({ render, data, params }) {
|
|
|
38
38
|
emojiIcon: framework.entityDefinitions.discipline.emojiIcon,
|
|
39
39
|
description: framework.entityDefinitions.discipline.description,
|
|
40
40
|
},
|
|
41
|
-
|
|
42
|
-
title: framework.entityDefinitions.
|
|
43
|
-
emojiIcon: framework.entityDefinitions.
|
|
44
|
-
description: framework.entityDefinitions.
|
|
41
|
+
level: {
|
|
42
|
+
title: framework.entityDefinitions.level.title,
|
|
43
|
+
emojiIcon: framework.entityDefinitions.level.emojiIcon,
|
|
44
|
+
description: framework.entityDefinitions.level.description,
|
|
45
45
|
},
|
|
46
46
|
track: {
|
|
47
47
|
title: framework.entityDefinitions.track.title,
|
package/src/slides/index.js
CHANGED
|
@@ -55,25 +55,25 @@ export function renderSlideIndex({ render, data }) {
|
|
|
55
55
|
),
|
|
56
56
|
),
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Levels
|
|
59
59
|
div(
|
|
60
60
|
{ className: "slide-section" },
|
|
61
61
|
a(
|
|
62
|
-
{ href: "#/overview/
|
|
62
|
+
{ href: "#/overview/level" },
|
|
63
63
|
heading2(
|
|
64
64
|
{ className: "slide-section-title" },
|
|
65
|
-
`${getConceptEmoji(data.framework, "
|
|
66
|
-
span({ className: "gradient-text" }, "
|
|
65
|
+
`${getConceptEmoji(data.framework, "level")} `,
|
|
66
|
+
span({ className: "gradient-text" }, "Levels"),
|
|
67
67
|
),
|
|
68
68
|
),
|
|
69
69
|
ul(
|
|
70
70
|
{ className: "related-list" },
|
|
71
|
-
...data.
|
|
71
|
+
...data.levels.map((level) =>
|
|
72
72
|
li(
|
|
73
73
|
{},
|
|
74
74
|
a(
|
|
75
|
-
{ href: `#/
|
|
76
|
-
`${
|
|
75
|
+
{ href: `#/level/${level.id}` },
|
|
76
|
+
`${level.id} - ${level.professionalTitle}`,
|
|
77
77
|
),
|
|
78
78
|
),
|
|
79
79
|
),
|
|
@@ -174,7 +174,7 @@ export function renderSlideIndex({ render, data }) {
|
|
|
174
174
|
{ className: "related-list" },
|
|
175
175
|
...generateAllJobs({
|
|
176
176
|
disciplines: data.disciplines,
|
|
177
|
-
|
|
177
|
+
levels: data.levels,
|
|
178
178
|
tracks: data.tracks,
|
|
179
179
|
skills: data.skills,
|
|
180
180
|
behaviours: data.behaviours,
|
|
@@ -185,8 +185,8 @@ export function renderSlideIndex({ render, data }) {
|
|
|
185
185
|
a(
|
|
186
186
|
{
|
|
187
187
|
href: job.track
|
|
188
|
-
? `#/job/${job.discipline.id}/${job.
|
|
189
|
-
: `#/job/${job.discipline.id}/${job.
|
|
188
|
+
? `#/job/${job.discipline.id}/${job.level.id}/${job.track.id}`
|
|
189
|
+
: `#/job/${job.discipline.id}/${job.level.id}`,
|
|
190
190
|
},
|
|
191
191
|
job.title,
|
|
192
192
|
),
|
package/src/slides/interview.js
CHANGED
|
@@ -20,7 +20,7 @@ import { interviewToDOM } from "../formatters/index.js";
|
|
|
20
20
|
*/
|
|
21
21
|
export function renderInterviewSlide({ render, data, params }) {
|
|
22
22
|
const discipline = data.disciplines.find((d) => d.id === params.discipline);
|
|
23
|
-
const
|
|
23
|
+
const level = data.levels.find((g) => g.id === params.level);
|
|
24
24
|
const track = data.tracks.find((t) => t.id === params.track);
|
|
25
25
|
|
|
26
26
|
// Get interview type from URL query or default to full
|
|
@@ -29,7 +29,7 @@ export function renderInterviewSlide({ render, data, params }) {
|
|
|
29
29
|
|
|
30
30
|
const view = prepareInterviewDetail({
|
|
31
31
|
discipline,
|
|
32
|
-
|
|
32
|
+
level,
|
|
33
33
|
track,
|
|
34
34
|
skills: data.skills,
|
|
35
35
|
behaviours: data.behaviours,
|
package/src/slides/job.js
CHANGED
|
@@ -17,12 +17,12 @@ import { jobToDOM } from "../formatters/index.js";
|
|
|
17
17
|
*/
|
|
18
18
|
export function renderJobSlide({ render, data, params }) {
|
|
19
19
|
const discipline = data.disciplines.find((d) => d.id === params.discipline);
|
|
20
|
-
const
|
|
20
|
+
const level = data.levels.find((g) => g.id === params.level);
|
|
21
21
|
const track = data.tracks.find((t) => t.id === params.track);
|
|
22
22
|
|
|
23
23
|
const view = prepareJobDetail({
|
|
24
24
|
discipline,
|
|
25
|
-
|
|
25
|
+
level,
|
|
26
26
|
track,
|
|
27
27
|
skills: data.skills,
|
|
28
28
|
behaviours: data.behaviours,
|
|
@@ -49,7 +49,7 @@ export function renderJobSlide({ render, data, params }) {
|
|
|
49
49
|
showJobDescriptionHtml: true,
|
|
50
50
|
showJobDescriptionMarkdown: false,
|
|
51
51
|
discipline,
|
|
52
|
-
|
|
52
|
+
level,
|
|
53
53
|
track,
|
|
54
54
|
}),
|
|
55
55
|
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level Slide View
|
|
3
|
+
*
|
|
4
|
+
* Printer-friendly view of a level.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { div, h1, p } from "../lib/render.js";
|
|
8
|
+
import { levelToDOM } from "../formatters/index.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Render level slide
|
|
12
|
+
* @param {Object} params
|
|
13
|
+
* @param {Function} params.render
|
|
14
|
+
* @param {Object} params.data
|
|
15
|
+
* @param {Object} params.params
|
|
16
|
+
*/
|
|
17
|
+
export function renderLevelSlide({ render, data, params }) {
|
|
18
|
+
const level = data.levels.find((g) => g.id === params.id);
|
|
19
|
+
|
|
20
|
+
if (!level) {
|
|
21
|
+
render(
|
|
22
|
+
div(
|
|
23
|
+
{ className: "slide-error" },
|
|
24
|
+
h1({}, "Level Not Found"),
|
|
25
|
+
p({}, `No level found with ID: ${params.id}`),
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render(levelToDOM(level, { framework: data.framework, showBackLink: false }));
|
|
32
|
+
}
|
package/src/slides/overview.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
skillToCardConfig,
|
|
13
13
|
behaviourToCardConfig,
|
|
14
14
|
driverToCardConfig,
|
|
15
|
-
|
|
15
|
+
levelToCardConfig,
|
|
16
16
|
trackToCardConfig,
|
|
17
17
|
jobToCardConfig,
|
|
18
18
|
} from "../lib/card-mappers.js";
|
|
@@ -20,7 +20,7 @@ import { prepareDisciplinesList } from "../formatters/discipline/shared.js";
|
|
|
20
20
|
import { prepareSkillsList } from "../formatters/skill/shared.js";
|
|
21
21
|
import { prepareBehavioursList } from "../formatters/behaviour/shared.js";
|
|
22
22
|
import { prepareDriversList } from "../formatters/driver/shared.js";
|
|
23
|
-
import {
|
|
23
|
+
import { prepareLevelsList } from "../formatters/level/shared.js";
|
|
24
24
|
import { prepareTracksList } from "../formatters/track/shared.js";
|
|
25
25
|
import { generateAllJobs } from "@forwardimpact/libpathway/derivation";
|
|
26
26
|
|
|
@@ -92,12 +92,12 @@ export function renderOverviewSlide({ render, data, params }) {
|
|
|
92
92
|
mapper: disciplineToCardConfig,
|
|
93
93
|
isGrouped: true,
|
|
94
94
|
},
|
|
95
|
-
|
|
96
|
-
title: framework.entityDefinitions.
|
|
97
|
-
emojiIcon: framework.entityDefinitions.
|
|
98
|
-
description: framework.entityDefinitions.
|
|
99
|
-
entities:
|
|
100
|
-
mapper:
|
|
95
|
+
level: {
|
|
96
|
+
title: framework.entityDefinitions.level.title,
|
|
97
|
+
emojiIcon: framework.entityDefinitions.level.emojiIcon,
|
|
98
|
+
description: framework.entityDefinitions.level.description,
|
|
99
|
+
entities: prepareLevelsList(data.levels).items,
|
|
100
|
+
mapper: levelToCardConfig,
|
|
101
101
|
},
|
|
102
102
|
track: {
|
|
103
103
|
title: framework.entityDefinitions.track.title,
|
|
@@ -112,7 +112,7 @@ export function renderOverviewSlide({ render, data, params }) {
|
|
|
112
112
|
description: framework.entityDefinitions.job.description,
|
|
113
113
|
entities: generateAllJobs({
|
|
114
114
|
disciplines: data.disciplines,
|
|
115
|
-
|
|
115
|
+
levels: data.levels,
|
|
116
116
|
tracks: data.tracks,
|
|
117
117
|
skills: data.skills,
|
|
118
118
|
behaviours: data.behaviours,
|
package/src/slides/progress.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { div, h1, p } from "../lib/render.js";
|
|
8
8
|
import {
|
|
9
9
|
prepareProgressDetail,
|
|
10
|
-
|
|
10
|
+
getDefaultTargetLevel,
|
|
11
11
|
} from "../formatters/progress/shared.js";
|
|
12
12
|
import { progressToDOM } from "../formatters/index.js";
|
|
13
13
|
|
|
@@ -20,12 +20,12 @@ import { progressToDOM } from "../formatters/index.js";
|
|
|
20
20
|
*/
|
|
21
21
|
export function renderProgressSlide({ render, data, params }) {
|
|
22
22
|
const discipline = data.disciplines.find((d) => d.id === params.discipline);
|
|
23
|
-
const
|
|
23
|
+
const level = data.levels.find((g) => g.id === params.level);
|
|
24
24
|
const track = params.track
|
|
25
25
|
? data.tracks.find((t) => t.id === params.track)
|
|
26
26
|
: null;
|
|
27
27
|
|
|
28
|
-
if (!discipline || !
|
|
28
|
+
if (!discipline || !level) {
|
|
29
29
|
render(
|
|
30
30
|
div(
|
|
31
31
|
{ className: "slide-error" },
|
|
@@ -36,23 +36,23 @@ export function renderProgressSlide({ render, data, params }) {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Get compare
|
|
39
|
+
// Get compare level from URL query or default to next level
|
|
40
40
|
const urlParams = new URLSearchParams(window.location.search);
|
|
41
|
-
const
|
|
41
|
+
const compareLevelId = urlParams.get("compare");
|
|
42
42
|
|
|
43
|
-
let
|
|
44
|
-
if (
|
|
45
|
-
|
|
43
|
+
let targetLevel;
|
|
44
|
+
if (compareLevelId) {
|
|
45
|
+
targetLevel = data.levels.find((g) => g.id === compareLevelId);
|
|
46
46
|
} else {
|
|
47
|
-
|
|
47
|
+
targetLevel = getDefaultTargetLevel(level, data.levels);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
if (!
|
|
50
|
+
if (!targetLevel) {
|
|
51
51
|
render(
|
|
52
52
|
div(
|
|
53
53
|
{ className: "slide-error" },
|
|
54
54
|
h1({}, "No Progression Available"),
|
|
55
|
-
p({}, "No next
|
|
55
|
+
p({}, "No next level available for this role."),
|
|
56
56
|
),
|
|
57
57
|
);
|
|
58
58
|
return;
|
|
@@ -60,10 +60,10 @@ export function renderProgressSlide({ render, data, params }) {
|
|
|
60
60
|
|
|
61
61
|
const view = prepareProgressDetail({
|
|
62
62
|
fromDiscipline: discipline,
|
|
63
|
-
|
|
63
|
+
fromLevel: level,
|
|
64
64
|
fromTrack: track,
|
|
65
65
|
toDiscipline: discipline,
|
|
66
|
-
|
|
66
|
+
toLevel: targetLevel,
|
|
67
67
|
toTrack: track,
|
|
68
68
|
skills: data.skills,
|
|
69
69
|
behaviours: data.behaviours,
|
package/src/types.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* @property {boolean} [humanOnly] - Whether this skill requires human presence
|
|
20
20
|
* @property {'primary'|'secondary'|'tertiary'} type - Skill type in this role
|
|
21
21
|
* @property {string} level - Level ID (e.g., "advanced", "expert")
|
|
22
|
-
* @property {string}
|
|
22
|
+
* @property {string} proficiencyDescription - Human-readable level description
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
// ============================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# {{{title}}}
|
|
2
2
|
|
|
3
|
-
- **Level:** {{{
|
|
3
|
+
- **Level:** {{{levelId}}}
|
|
4
4
|
- **Experience:** {{{typicalExperienceRange}}}
|
|
5
5
|
{{#hasTrack}}- **Track:** {{{trackName}}}
|
|
6
6
|
{{/hasTrack}}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
{{{responsibilityDescription}}}:
|
|
35
35
|
|
|
36
36
|
{{#skills}}
|
|
37
|
-
- **{{{skillName}}}:** {{{
|
|
37
|
+
- **{{{skillName}}}:** {{{proficiencyDescription}}}
|
|
38
38
|
{{/skills}}
|
|
39
39
|
{{/capabilitySkills}}
|
|
40
40
|
{{/hasCapabilitySkills}}
|
package/src/commands/grade.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Grade CLI Command
|
|
3
|
-
*
|
|
4
|
-
* Handles grade summary, listing, and detail display in the terminal.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx pathway grade # Summary with stats
|
|
8
|
-
* npx pathway grade --list # IDs only (for piping)
|
|
9
|
-
* npx pathway grade <id> # Detail view
|
|
10
|
-
* npx pathway grade --validate # Validation checks
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { createEntityCommand } from "./command-factory.js";
|
|
14
|
-
import { gradeToMarkdown } from "../formatters/grade/markdown.js";
|
|
15
|
-
import { formatTable } from "../lib/cli-output.js";
|
|
16
|
-
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
17
|
-
import { capitalize } from "../formatters/shared.js";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Format grade summary output
|
|
21
|
-
* @param {Array} grades - Raw grade entities
|
|
22
|
-
* @param {Object} data - Full data context
|
|
23
|
-
*/
|
|
24
|
-
function formatSummary(grades, data) {
|
|
25
|
-
const { framework } = data;
|
|
26
|
-
const emoji = framework ? getConceptEmoji(framework, "grade") : "📊";
|
|
27
|
-
|
|
28
|
-
console.log(`\n${emoji} Grades\n`);
|
|
29
|
-
|
|
30
|
-
const rows = grades.map((g) => [
|
|
31
|
-
g.id,
|
|
32
|
-
g.displayName || g.id,
|
|
33
|
-
g.typicalExperienceRange || "-",
|
|
34
|
-
capitalize(g.baseSkillLevels?.primary || "-"),
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
console.log(formatTable(["ID", "Name", "Experience", "Primary Level"], rows));
|
|
38
|
-
console.log(`\nTotal: ${grades.length} grades`);
|
|
39
|
-
console.log(`\nRun 'npx pathway grade --list' for IDs`);
|
|
40
|
-
console.log(`Run 'npx pathway grade <id>' for details\n`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Format grade detail output
|
|
45
|
-
* @param {Object} grade - Raw grade entity
|
|
46
|
-
* @param {Object} framework - Framework config
|
|
47
|
-
*/
|
|
48
|
-
function formatDetail(grade, framework) {
|
|
49
|
-
console.log(gradeToMarkdown(grade, framework));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const runGradeCommand = createEntityCommand({
|
|
53
|
-
entityName: "grade",
|
|
54
|
-
pluralName: "grades",
|
|
55
|
-
findEntity: (data, id) => data.grades.find((g) => g.id === id),
|
|
56
|
-
presentDetail: (entity) => entity,
|
|
57
|
-
formatSummary,
|
|
58
|
-
formatDetail,
|
|
59
|
-
emojiIcon: "📊",
|
|
60
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Grade presentation helpers
|
|
3
|
-
*
|
|
4
|
-
* Shared utilities for formatting grade data across DOM and markdown outputs.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Get grade display name (shows both professional and management titles)
|
|
9
|
-
* @param {Object} grade
|
|
10
|
-
* @returns {string}
|
|
11
|
-
*/
|
|
12
|
-
export function getGradeDisplayName(grade) {
|
|
13
|
-
if (grade.professionalTitle && grade.managementTitle) {
|
|
14
|
-
if (grade.professionalTitle === grade.managementTitle) {
|
|
15
|
-
return grade.professionalTitle;
|
|
16
|
-
}
|
|
17
|
-
return `${grade.professionalTitle} / ${grade.managementTitle}`;
|
|
18
|
-
}
|
|
19
|
-
return grade.professionalTitle || grade.managementTitle || grade.id;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {Object} GradeListItem
|
|
24
|
-
* @property {string} id
|
|
25
|
-
* @property {string} displayName
|
|
26
|
-
* @property {number} ordinalRank
|
|
27
|
-
* @property {string|null} typicalExperienceRange
|
|
28
|
-
* @property {Object} baseSkillLevels
|
|
29
|
-
* @property {string|null} impactScope
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Transform grades for list view
|
|
34
|
-
* @param {Array} grades - Raw grade entities
|
|
35
|
-
* @returns {{ items: GradeListItem[] }}
|
|
36
|
-
*/
|
|
37
|
-
export function prepareGradesList(grades) {
|
|
38
|
-
const sortedGrades = [...grades].sort(
|
|
39
|
-
(a, b) => a.ordinalRank - b.ordinalRank,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const items = sortedGrades.map((grade) => ({
|
|
43
|
-
id: grade.id,
|
|
44
|
-
displayName: getGradeDisplayName(grade),
|
|
45
|
-
ordinalRank: grade.ordinalRank,
|
|
46
|
-
typicalExperienceRange: grade.typicalExperienceRange || null,
|
|
47
|
-
baseSkillLevels: grade.baseSkillLevels || {},
|
|
48
|
-
impactScope: grade.expectations?.impactScope || null,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
return { items };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @typedef {Object} GradeDetailView
|
|
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} baseSkillLevels
|
|
63
|
-
* @property {Object} baseBehaviourMaturity
|
|
64
|
-
* @property {Object} expectations
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Transform grade for detail view
|
|
69
|
-
* @param {Object} grade - Raw grade entity
|
|
70
|
-
* @returns {GradeDetailView|null}
|
|
71
|
-
*/
|
|
72
|
-
export function prepareGradeDetail(grade) {
|
|
73
|
-
if (!grade) return null;
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
id: grade.id,
|
|
77
|
-
displayName: getGradeDisplayName(grade),
|
|
78
|
-
professionalTitle: grade.professionalTitle || null,
|
|
79
|
-
managementTitle: grade.managementTitle || null,
|
|
80
|
-
ordinalRank: grade.ordinalRank,
|
|
81
|
-
typicalExperienceRange: grade.typicalExperienceRange || null,
|
|
82
|
-
baseSkillLevels: grade.baseSkillLevels || {},
|
|
83
|
-
baseBehaviourMaturity: grade.baseBehaviourMaturity || {},
|
|
84
|
-
expectations: grade.expectations || {},
|
|
85
|
-
};
|
|
86
|
-
}
|
package/src/pages/grade.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Grades pages
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { render, div, h1, h3, p, formatLevel } from "../lib/render.js";
|
|
6
|
-
import { getState } from "../lib/state.js";
|
|
7
|
-
import { createBadge } from "../components/card.js";
|
|
8
|
-
import { renderNotFound } from "../components/error-page.js";
|
|
9
|
-
import { prepareGradesList } from "../formatters/grade/shared.js";
|
|
10
|
-
import { gradeToDOM } from "../formatters/grade/dom.js";
|
|
11
|
-
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Render grades list page
|
|
15
|
-
*/
|
|
16
|
-
export function renderGradesList() {
|
|
17
|
-
const { data } = getState();
|
|
18
|
-
const { framework } = data;
|
|
19
|
-
const gradeEmoji = getConceptEmoji(framework, "grade");
|
|
20
|
-
|
|
21
|
-
// Transform data for list view
|
|
22
|
-
const { items } = prepareGradesList(data.grades);
|
|
23
|
-
|
|
24
|
-
const page = div(
|
|
25
|
-
{ className: "grades-page" },
|
|
26
|
-
// Header
|
|
27
|
-
div(
|
|
28
|
-
{ className: "page-header" },
|
|
29
|
-
h1(
|
|
30
|
-
{ className: "page-title" },
|
|
31
|
-
`${gradeEmoji} ${framework.entityDefinitions.grade.title}`,
|
|
32
|
-
),
|
|
33
|
-
p(
|
|
34
|
-
{ className: "page-description" },
|
|
35
|
-
framework.entityDefinitions.grade.description.trim(),
|
|
36
|
-
),
|
|
37
|
-
),
|
|
38
|
-
|
|
39
|
-
// Grades timeline
|
|
40
|
-
div(
|
|
41
|
-
{ className: "grades-timeline" },
|
|
42
|
-
...items.map((grade) => createGradeTimelineItem(grade)),
|
|
43
|
-
),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
render(page);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Create a grade timeline item
|
|
51
|
-
* @param {Object} grade
|
|
52
|
-
* @returns {HTMLElement}
|
|
53
|
-
*/
|
|
54
|
-
function createGradeTimelineItem(grade) {
|
|
55
|
-
const item = div(
|
|
56
|
-
{ className: "grade-timeline-item" },
|
|
57
|
-
div({ className: "grade-level-marker" }, String(grade.ordinalRank)),
|
|
58
|
-
div(
|
|
59
|
-
{ className: "grade-timeline-content card card-clickable" },
|
|
60
|
-
div(
|
|
61
|
-
{ className: "card-header" },
|
|
62
|
-
h3({ className: "card-title" }, grade.displayName),
|
|
63
|
-
createBadge(grade.id, "default"),
|
|
64
|
-
),
|
|
65
|
-
grade.typicalExperienceRange
|
|
66
|
-
? p(
|
|
67
|
-
{ className: "text-muted", style: "margin: 0.25rem 0" },
|
|
68
|
-
`${grade.typicalExperienceRange} experience`,
|
|
69
|
-
)
|
|
70
|
-
: null,
|
|
71
|
-
div(
|
|
72
|
-
{ className: "card-meta", style: "margin-top: 0.5rem" },
|
|
73
|
-
createBadge(
|
|
74
|
-
`Primary: ${formatLevel(grade.baseSkillLevels?.primary)}`,
|
|
75
|
-
"primary",
|
|
76
|
-
),
|
|
77
|
-
createBadge(
|
|
78
|
-
`Secondary: ${formatLevel(grade.baseSkillLevels?.secondary)}`,
|
|
79
|
-
"secondary",
|
|
80
|
-
),
|
|
81
|
-
createBadge(
|
|
82
|
-
`Broad: ${formatLevel(grade.baseSkillLevels?.broad)}`,
|
|
83
|
-
"broad",
|
|
84
|
-
),
|
|
85
|
-
),
|
|
86
|
-
grade.scope
|
|
87
|
-
? p(
|
|
88
|
-
{ className: "card-description", style: "margin-top: 0.75rem" },
|
|
89
|
-
`Scope: ${grade.scope}`,
|
|
90
|
-
)
|
|
91
|
-
: null,
|
|
92
|
-
),
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
item.querySelector(".card").addEventListener("click", () => {
|
|
96
|
-
window.location.hash = `/grade/${grade.id}`;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return item;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Render grade detail page
|
|
104
|
-
* @param {Object} params - Route params
|
|
105
|
-
*/
|
|
106
|
-
export function renderGradeDetail(params) {
|
|
107
|
-
const { data } = getState();
|
|
108
|
-
const grade = data.grades.find((g) => g.id === params.id);
|
|
109
|
-
|
|
110
|
-
if (!grade) {
|
|
111
|
-
renderNotFound({
|
|
112
|
-
entityType: "Grade",
|
|
113
|
-
entityId: params.id,
|
|
114
|
-
backPath: "/grade",
|
|
115
|
-
backText: "← Back to Grades",
|
|
116
|
-
});
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Use DOM formatter - it handles transformation internally
|
|
121
|
-
render(gradeToDOM(grade, { framework: data.framework }));
|
|
122
|
-
}
|