@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
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
deriveStageAgent,
|
|
27
27
|
generateSkillMarkdown,
|
|
28
28
|
deriveAgentSkills,
|
|
29
|
-
|
|
29
|
+
deriveReferenceLevel,
|
|
30
30
|
deriveToolkit,
|
|
31
31
|
buildAgentIndex,
|
|
32
32
|
} from "@forwardimpact/libpathway";
|
|
@@ -256,8 +256,8 @@ export async function renderAgentBuilder() {
|
|
|
256
256
|
return;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
// Get reference
|
|
260
|
-
const
|
|
259
|
+
// Get reference level for derivation
|
|
260
|
+
const level = deriveReferenceLevel(data.levels);
|
|
261
261
|
|
|
262
262
|
// Build agent index for all valid combinations
|
|
263
263
|
const agentIndex = buildAgentIndex({
|
|
@@ -274,7 +274,7 @@ export async function renderAgentBuilder() {
|
|
|
274
274
|
humanTrack,
|
|
275
275
|
agentDiscipline,
|
|
276
276
|
agentTrack,
|
|
277
|
-
|
|
277
|
+
level,
|
|
278
278
|
stages,
|
|
279
279
|
skills: data.skills,
|
|
280
280
|
behaviours: data.behaviours,
|
|
@@ -451,7 +451,7 @@ function createAllStagesPreview(context) {
|
|
|
451
451
|
humanTrack,
|
|
452
452
|
agentDiscipline,
|
|
453
453
|
agentTrack,
|
|
454
|
-
|
|
454
|
+
level,
|
|
455
455
|
stages,
|
|
456
456
|
skills,
|
|
457
457
|
behaviours,
|
|
@@ -468,7 +468,7 @@ function createAllStagesPreview(context) {
|
|
|
468
468
|
discipline: humanDiscipline,
|
|
469
469
|
track: humanTrack,
|
|
470
470
|
stage,
|
|
471
|
-
|
|
471
|
+
level,
|
|
472
472
|
skills,
|
|
473
473
|
behaviours,
|
|
474
474
|
agentBehaviours,
|
|
@@ -481,7 +481,7 @@ function createAllStagesPreview(context) {
|
|
|
481
481
|
discipline: humanDiscipline,
|
|
482
482
|
track: humanTrack,
|
|
483
483
|
stage,
|
|
484
|
-
|
|
484
|
+
level,
|
|
485
485
|
skills,
|
|
486
486
|
behaviours,
|
|
487
487
|
agentBehaviours,
|
|
@@ -498,7 +498,7 @@ function createAllStagesPreview(context) {
|
|
|
498
498
|
const derivedSkills = deriveAgentSkills({
|
|
499
499
|
discipline: humanDiscipline,
|
|
500
500
|
track: humanTrack,
|
|
501
|
-
|
|
501
|
+
level,
|
|
502
502
|
skills,
|
|
503
503
|
});
|
|
504
504
|
|
|
@@ -594,7 +594,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
594
594
|
humanTrack,
|
|
595
595
|
agentDiscipline,
|
|
596
596
|
agentTrack,
|
|
597
|
-
|
|
597
|
+
level,
|
|
598
598
|
skills,
|
|
599
599
|
behaviours,
|
|
600
600
|
agentBehaviours,
|
|
@@ -609,7 +609,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
609
609
|
discipline: humanDiscipline,
|
|
610
610
|
track: humanTrack,
|
|
611
611
|
stage,
|
|
612
|
-
|
|
612
|
+
level,
|
|
613
613
|
skills,
|
|
614
614
|
behaviours,
|
|
615
615
|
agentBehaviours,
|
|
@@ -623,7 +623,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
623
623
|
const derivedSkills = deriveAgentSkills({
|
|
624
624
|
discipline: humanDiscipline,
|
|
625
625
|
track: humanTrack,
|
|
626
|
-
|
|
626
|
+
level,
|
|
627
627
|
skills,
|
|
628
628
|
});
|
|
629
629
|
|
|
@@ -45,14 +45,14 @@ export function renderAssessmentResults() {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
// Find matching jobs with realistic scoring
|
|
48
|
-
const { matches, matchesByTier,
|
|
48
|
+
const { matches, matchesByTier, estimatedLevel } = findRealisticMatches({
|
|
49
49
|
selfAssessment,
|
|
50
50
|
disciplines: data.disciplines,
|
|
51
|
-
|
|
51
|
+
levels: data.levels,
|
|
52
52
|
tracks: data.tracks,
|
|
53
53
|
skills: data.skills,
|
|
54
54
|
behaviours: data.behaviours,
|
|
55
|
-
|
|
55
|
+
filterByLevel: false, // Show all levels but group by tier
|
|
56
56
|
topN: 20,
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -73,7 +73,7 @@ export function renderAssessmentResults() {
|
|
|
73
73
|
),
|
|
74
74
|
|
|
75
75
|
// Summary stats
|
|
76
|
-
createSummaryStats(assessmentState, data,
|
|
76
|
+
createSummaryStats(assessmentState, data, estimatedLevel),
|
|
77
77
|
|
|
78
78
|
// Top matches grouped by tier
|
|
79
79
|
createMatchesSection(matches, matchesByTier, selfAssessment, data),
|
|
@@ -139,28 +139,28 @@ function renderNoAssessment() {
|
|
|
139
139
|
* Create summary statistics section
|
|
140
140
|
* @param {Object} assessmentState - Current assessment state
|
|
141
141
|
* @param {Object} data - App data
|
|
142
|
-
* @param {{
|
|
142
|
+
* @param {{level: Object, confidence: number}} estimatedLevel - Estimated best-fit level
|
|
143
143
|
* @returns {HTMLElement}
|
|
144
144
|
*/
|
|
145
|
-
function createSummaryStats(assessmentState, data,
|
|
145
|
+
function createSummaryStats(assessmentState, data, estimatedLevel) {
|
|
146
146
|
const skillCount = Object.keys(assessmentState.skills).length;
|
|
147
147
|
const behaviourCount = Object.keys(assessmentState.behaviours).length;
|
|
148
148
|
|
|
149
149
|
// Calculate average levels
|
|
150
|
-
const
|
|
150
|
+
const avgSkillProficiency = calculateAverageLevel(
|
|
151
151
|
Object.values(assessmentState.skills),
|
|
152
152
|
["awareness", "foundational", "working", "practitioner", "expert"],
|
|
153
153
|
);
|
|
154
154
|
|
|
155
|
-
// Get
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
// Get level name based on track (default to professional)
|
|
156
|
+
const levelName =
|
|
157
|
+
estimatedLevel.level.professionalTitle ||
|
|
158
|
+
estimatedLevel.level.name ||
|
|
159
|
+
estimatedLevel.level.id;
|
|
160
160
|
const confidenceLabel =
|
|
161
|
-
|
|
161
|
+
estimatedLevel.confidence >= 0.7
|
|
162
162
|
? "High"
|
|
163
|
-
:
|
|
163
|
+
: estimatedLevel.confidence >= 0.4
|
|
164
164
|
? "Medium"
|
|
165
165
|
: "Low";
|
|
166
166
|
|
|
@@ -179,8 +179,12 @@ function createSummaryStats(assessmentState, data, estimatedGrade) {
|
|
|
179
179
|
`of ${data.behaviours.length} Behaviours`,
|
|
180
180
|
"🧠",
|
|
181
181
|
),
|
|
182
|
-
createStatBox(
|
|
183
|
-
|
|
182
|
+
createStatBox(
|
|
183
|
+
formatLevel(avgSkillProficiency),
|
|
184
|
+
"Avg Skill Proficiency",
|
|
185
|
+
"💡",
|
|
186
|
+
),
|
|
187
|
+
createStatBox(levelName, `Estimated Level (${confidenceLabel})`, "🎯"),
|
|
184
188
|
),
|
|
185
189
|
);
|
|
186
190
|
}
|
|
@@ -380,8 +384,8 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
380
384
|
a(
|
|
381
385
|
{
|
|
382
386
|
href: job.track
|
|
383
|
-
? `#/job/${job.discipline.id}/${job.
|
|
384
|
-
: `#/job/${job.discipline.id}/${job.
|
|
387
|
+
? `#/job/${job.discipline.id}/${job.level.id}/${job.track.id}`
|
|
388
|
+
: `#/job/${job.discipline.id}/${job.level.id}`,
|
|
385
389
|
},
|
|
386
390
|
job.title,
|
|
387
391
|
),
|
|
@@ -389,7 +393,7 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
389
393
|
div(
|
|
390
394
|
{ className: "match-badges" },
|
|
391
395
|
createBadge(job.discipline.name, "default"),
|
|
392
|
-
createBadge(job.
|
|
396
|
+
createBadge(job.level.name, "secondary"),
|
|
393
397
|
job.track && createBadge(job.track.name, "broad"),
|
|
394
398
|
),
|
|
395
399
|
),
|
|
@@ -440,8 +444,8 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
440
444
|
a(
|
|
441
445
|
{
|
|
442
446
|
href: job.track
|
|
443
|
-
? `#/job/${job.discipline.id}/${job.
|
|
444
|
-
: `#/job/${job.discipline.id}/${job.
|
|
447
|
+
? `#/job/${job.discipline.id}/${job.level.id}/${job.track.id}`
|
|
448
|
+
: `#/job/${job.discipline.id}/${job.level.id}`,
|
|
445
449
|
className: "btn btn-secondary btn-sm",
|
|
446
450
|
},
|
|
447
451
|
"View Job Details",
|
|
@@ -449,8 +453,8 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
449
453
|
a(
|
|
450
454
|
{
|
|
451
455
|
href: job.track
|
|
452
|
-
? `#/interview/${job.discipline.id}/${job.
|
|
453
|
-
: `#/interview/${job.discipline.id}/${job.
|
|
456
|
+
? `#/interview/${job.discipline.id}/${job.level.id}/${job.track.id}`
|
|
457
|
+
: `#/interview/${job.discipline.id}/${job.level.id}`,
|
|
454
458
|
className: "btn btn-secondary btn-sm",
|
|
455
459
|
},
|
|
456
460
|
"Interview Prep",
|
package/src/pages/behaviour.js
CHANGED
|
@@ -9,7 +9,7 @@ import { renderNotFound } from "../components/error-page.js";
|
|
|
9
9
|
import { prepareBehavioursList } from "../formatters/behaviour/shared.js";
|
|
10
10
|
import { behaviourToDOM } from "../formatters/behaviour/dom.js";
|
|
11
11
|
import { behaviourToCardConfig } from "../lib/card-mappers.js";
|
|
12
|
-
import { getConceptEmoji } from "@forwardimpact/
|
|
12
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Render behaviours list page
|
package/src/pages/discipline.js
CHANGED
|
@@ -10,7 +10,7 @@ import { renderNotFound } from "../components/error-page.js";
|
|
|
10
10
|
import { prepareDisciplinesList } from "../formatters/discipline/shared.js";
|
|
11
11
|
import { disciplineToDOM } from "../formatters/discipline/dom.js";
|
|
12
12
|
import { disciplineToCardConfig } from "../lib/card-mappers.js";
|
|
13
|
-
import { getConceptEmoji } from "@forwardimpact/
|
|
13
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Format discipline group name for display
|
package/src/pages/driver.js
CHANGED
|
@@ -9,7 +9,7 @@ import { renderNotFound } from "../components/error-page.js";
|
|
|
9
9
|
import { prepareDriversList } from "../formatters/driver/shared.js";
|
|
10
10
|
import { driverToDOM } from "../formatters/driver/dom.js";
|
|
11
11
|
import { driverToCardConfig } from "../lib/card-mappers.js";
|
|
12
|
-
import { getConceptEmoji } from "@forwardimpact/
|
|
12
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Render drivers list page
|
|
@@ -17,7 +17,7 @@ export function renderInterviewPrep() {
|
|
|
17
17
|
createBuilder({
|
|
18
18
|
title: "Interview Prep",
|
|
19
19
|
description:
|
|
20
|
-
"Select a discipline, track, and
|
|
20
|
+
"Select a discipline, track, and level to generate tailored interview questions " +
|
|
21
21
|
"based on the role's skill requirements and expected behaviours.",
|
|
22
22
|
formTitle: "Select Role",
|
|
23
23
|
emptyPreviewText: "Select all three components to preview the interview.",
|
|
@@ -26,21 +26,21 @@ export function renderInterviewPrep() {
|
|
|
26
26
|
prepareInterviewBuilderPreview({
|
|
27
27
|
...selection,
|
|
28
28
|
behaviourCount: data.behaviours.length,
|
|
29
|
-
|
|
29
|
+
levels: data.levels,
|
|
30
30
|
}),
|
|
31
31
|
detailPath: (sel) =>
|
|
32
32
|
sel.track
|
|
33
|
-
? `/interview/${sel.discipline}/${sel.
|
|
34
|
-
: `/interview/${sel.discipline}/${sel.
|
|
33
|
+
? `/interview/${sel.discipline}/${sel.level}/${sel.track}`
|
|
34
|
+
: `/interview/${sel.discipline}/${sel.level}`,
|
|
35
35
|
renderPreview: createStandardPreview,
|
|
36
36
|
helpItems: [
|
|
37
37
|
{
|
|
38
38
|
label: "Role Selection",
|
|
39
|
-
text: "Choose a discipline, track, and
|
|
39
|
+
text: "Choose a discipline, track, and level to define the target role for the interview.",
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
label: "Skill Questions",
|
|
43
|
-
text: "Questions are generated based on the required skill
|
|
43
|
+
text: "Questions are generated based on the required skill proficiencies for the role.",
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
label: "Behaviour Questions",
|
package/src/pages/interview.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createBadge } from "../components/card.js";
|
|
|
21
21
|
import { createBackLink } from "../components/nav.js";
|
|
22
22
|
import { createDetailSection } from "../components/detail.js";
|
|
23
23
|
import { renderError } from "../components/error-page.js";
|
|
24
|
-
import { getConceptEmoji } from "@forwardimpact/
|
|
24
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
25
25
|
import {
|
|
26
26
|
prepareAllInterviews,
|
|
27
27
|
INTERVIEW_TYPES,
|
|
@@ -32,18 +32,18 @@ import {
|
|
|
32
32
|
* @param {Object} params - Route params
|
|
33
33
|
*/
|
|
34
34
|
export function renderInterviewDetail(params) {
|
|
35
|
-
const { discipline: disciplineId,
|
|
35
|
+
const { discipline: disciplineId, level: levelId, track: trackId } = params;
|
|
36
36
|
const { data } = getState();
|
|
37
37
|
|
|
38
38
|
// Find the components
|
|
39
39
|
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
40
|
-
const
|
|
40
|
+
const level = data.levels.find((g) => g.id === levelId);
|
|
41
41
|
const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
42
42
|
|
|
43
|
-
if (!discipline || !
|
|
43
|
+
if (!discipline || !level) {
|
|
44
44
|
renderError({
|
|
45
45
|
title: "Interview Not Found",
|
|
46
|
-
message: "Invalid combination. Discipline or
|
|
46
|
+
message: "Invalid combination. Discipline or level not found.",
|
|
47
47
|
backPath: "/interview-prep",
|
|
48
48
|
backText: "← Back to Interview Prep",
|
|
49
49
|
});
|
|
@@ -64,7 +64,7 @@ export function renderInterviewDetail(params) {
|
|
|
64
64
|
// Use formatter shared module to get all interview types
|
|
65
65
|
const interviewsView = prepareAllInterviews({
|
|
66
66
|
discipline,
|
|
67
|
-
|
|
67
|
+
level,
|
|
68
68
|
track,
|
|
69
69
|
skills: data.skills,
|
|
70
70
|
behaviours: data.behaviours,
|
|
@@ -74,7 +74,7 @@ export function renderInterviewDetail(params) {
|
|
|
74
74
|
if (!interviewsView) {
|
|
75
75
|
renderError({
|
|
76
76
|
title: "Invalid Combination",
|
|
77
|
-
message: "This discipline, track, and
|
|
77
|
+
message: "This discipline, track, and level combination is not valid.",
|
|
78
78
|
backPath: "/interview-prep",
|
|
79
79
|
backText: "← Back to Interview Prep",
|
|
80
80
|
});
|
|
@@ -100,8 +100,8 @@ export function renderInterviewDetail(params) {
|
|
|
100
100
|
),
|
|
101
101
|
" × ",
|
|
102
102
|
a(
|
|
103
|
-
{ href: `#/
|
|
104
|
-
interviewsView.
|
|
103
|
+
{ href: `#/level/${interviewsView.levelId}` },
|
|
104
|
+
interviewsView.levelId,
|
|
105
105
|
),
|
|
106
106
|
" × ",
|
|
107
107
|
a(
|
package/src/pages/job-builder.js
CHANGED
|
@@ -17,7 +17,7 @@ export function renderJobBuilder() {
|
|
|
17
17
|
createBuilder({
|
|
18
18
|
title: "Job Builder",
|
|
19
19
|
description:
|
|
20
|
-
"Combine a discipline, track, and
|
|
20
|
+
"Combine a discipline, track, and level to generate a complete job definition " +
|
|
21
21
|
"with skill matrix and behaviour profile.",
|
|
22
22
|
formTitle: "Select Components",
|
|
23
23
|
emptyPreviewText:
|
|
@@ -27,12 +27,12 @@ export function renderJobBuilder() {
|
|
|
27
27
|
prepareJobBuilderPreview({
|
|
28
28
|
...selection,
|
|
29
29
|
behaviourCount: data.behaviours.length,
|
|
30
|
-
|
|
30
|
+
levels: data.levels,
|
|
31
31
|
}),
|
|
32
32
|
detailPath: (sel) =>
|
|
33
33
|
sel.track
|
|
34
|
-
? `/job/${sel.discipline}/${sel.
|
|
35
|
-
: `/job/${sel.discipline}/${sel.
|
|
34
|
+
? `/job/${sel.discipline}/${sel.level}/${sel.track}`
|
|
35
|
+
: `/job/${sel.discipline}/${sel.level}`,
|
|
36
36
|
renderPreview: createStandardPreview,
|
|
37
37
|
helpItems: [
|
|
38
38
|
{
|
|
@@ -40,8 +40,8 @@ export function renderJobBuilder() {
|
|
|
40
40
|
text: "Defines the T-shaped skill profile with primary, secondary, and broad skills.",
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
|
-
label: "
|
|
44
|
-
text: "Sets base skill
|
|
43
|
+
label: "Level",
|
|
44
|
+
text: "Sets base skill proficiencies and behaviour maturity expectations for career level.",
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
label: "Track",
|
package/src/pages/job.js
CHANGED
|
@@ -28,18 +28,18 @@ async function getJobTemplate() {
|
|
|
28
28
|
* @param {Object} params - Route params
|
|
29
29
|
*/
|
|
30
30
|
export async function renderJobDetail(params) {
|
|
31
|
-
const { discipline: disciplineId,
|
|
31
|
+
const { discipline: disciplineId, level: levelId, track: trackId } = params;
|
|
32
32
|
const { data } = getState();
|
|
33
33
|
|
|
34
34
|
// Find the components
|
|
35
35
|
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
36
|
-
const
|
|
36
|
+
const level = data.levels.find((g) => g.id === levelId);
|
|
37
37
|
const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
38
38
|
|
|
39
|
-
if (!discipline || !
|
|
39
|
+
if (!discipline || !level) {
|
|
40
40
|
renderError({
|
|
41
41
|
title: "Job Not Found",
|
|
42
|
-
message: "Invalid job combination. Discipline or
|
|
42
|
+
message: "Invalid job combination. Discipline or level not found.",
|
|
43
43
|
backPath: "/job-builder",
|
|
44
44
|
backText: "← Back to Job Builder",
|
|
45
45
|
});
|
|
@@ -60,7 +60,7 @@ export async function renderJobDetail(params) {
|
|
|
60
60
|
// Use formatter shared module to get job detail view
|
|
61
61
|
const jobView = prepareJobDetail({
|
|
62
62
|
discipline,
|
|
63
|
-
|
|
63
|
+
level,
|
|
64
64
|
track,
|
|
65
65
|
skills: data.skills,
|
|
66
66
|
behaviours: data.behaviours,
|
|
@@ -72,7 +72,7 @@ export async function renderJobDetail(params) {
|
|
|
72
72
|
if (!jobView) {
|
|
73
73
|
renderError({
|
|
74
74
|
title: "Invalid Combination",
|
|
75
|
-
message: "This discipline, track, and
|
|
75
|
+
message: "This discipline, track, and level combination is not valid.",
|
|
76
76
|
backPath: "/job-builder",
|
|
77
77
|
backText: "← Back to Job Builder",
|
|
78
78
|
});
|
|
@@ -89,6 +89,6 @@ export async function renderJobDetail(params) {
|
|
|
89
89
|
|
|
90
90
|
// Load template and format
|
|
91
91
|
const jobTemplate = await getJobTemplate();
|
|
92
|
-
const page = jobToDOM(jobView, { discipline,
|
|
92
|
+
const page = jobToDOM(jobView, { discipline, level, track, jobTemplate });
|
|
93
93
|
render(page);
|
|
94
94
|
}
|
package/src/pages/landing.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createStatCard } from "../components/card.js";
|
|
|
8
8
|
import {
|
|
9
9
|
groupSkillsByCapability,
|
|
10
10
|
getConceptEmoji,
|
|
11
|
-
} from "@forwardimpact/
|
|
11
|
+
} from "@forwardimpact/map/levels";
|
|
12
12
|
import { getStageEmoji } from "../formatters/stage/shared.js";
|
|
13
13
|
import { aggregateTools } from "../formatters/tool/shared.js";
|
|
14
14
|
import { createCommandPrompt } from "../components/command-prompt.js";
|
|
@@ -105,9 +105,9 @@ export function renderLanding() {
|
|
|
105
105
|
href: "/discipline",
|
|
106
106
|
}),
|
|
107
107
|
createStatCard({
|
|
108
|
-
value: data.
|
|
109
|
-
label: "
|
|
110
|
-
href: "/
|
|
108
|
+
value: data.levels.length,
|
|
109
|
+
label: "Levels",
|
|
110
|
+
href: "/level",
|
|
111
111
|
}),
|
|
112
112
|
createStatCard({
|
|
113
113
|
value: data.tracks.length,
|
|
@@ -169,9 +169,9 @@ export function renderLanding() {
|
|
|
169
169
|
"/discipline",
|
|
170
170
|
),
|
|
171
171
|
createQuickLinkCard(
|
|
172
|
-
`${getConceptEmoji(framework, "
|
|
173
|
-
`${data.
|
|
174
|
-
"/
|
|
172
|
+
`${getConceptEmoji(framework, "level")} ${framework.entityDefinitions.level.title}`,
|
|
173
|
+
`${data.levels.length} ${framework.entityDefinitions.level.title.toLowerCase()} — ${framework.entityDefinitions.level.description.trim().split("\n")[0]}`,
|
|
174
|
+
"/level",
|
|
175
175
|
),
|
|
176
176
|
createQuickLinkCard(
|
|
177
177
|
`${getConceptEmoji(framework, "track")} ${framework.entityDefinitions.track.title}`,
|
|
@@ -220,7 +220,7 @@ export function renderLanding() {
|
|
|
220
220
|
h2({}, "🔨 Build a Job Definition"),
|
|
221
221
|
p(
|
|
222
222
|
{},
|
|
223
|
-
"Combine a discipline, track, and
|
|
223
|
+
"Combine a discipline, track, and level to generate a complete job definition " +
|
|
224
224
|
"with skill matrices and behaviour profiles.",
|
|
225
225
|
),
|
|
226
226
|
div(
|
|
@@ -256,7 +256,7 @@ export function renderLanding() {
|
|
|
256
256
|
h2({}, "📈 Plan Your Career"),
|
|
257
257
|
p(
|
|
258
258
|
{},
|
|
259
|
-
"Visualize your progression to the next
|
|
259
|
+
"Visualize your progression to the next level and compare expectations " +
|
|
260
260
|
"across different tracks to plan your career development.",
|
|
261
261
|
),
|
|
262
262
|
div(
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levels 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 { prepareLevelsList } from "../formatters/level/shared.js";
|
|
10
|
+
import { levelToDOM } from "../formatters/level/dom.js";
|
|
11
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render levels list page
|
|
15
|
+
*/
|
|
16
|
+
export function renderLevelsList() {
|
|
17
|
+
const { data } = getState();
|
|
18
|
+
const { framework } = data;
|
|
19
|
+
const levelEmoji = getConceptEmoji(framework, "level");
|
|
20
|
+
|
|
21
|
+
// Transform data for list view
|
|
22
|
+
const { items } = prepareLevelsList(data.levels);
|
|
23
|
+
|
|
24
|
+
const page = div(
|
|
25
|
+
{ className: "levels-page" },
|
|
26
|
+
// Header
|
|
27
|
+
div(
|
|
28
|
+
{ className: "page-header" },
|
|
29
|
+
h1(
|
|
30
|
+
{ className: "page-title" },
|
|
31
|
+
`${levelEmoji} ${framework.entityDefinitions.level.title}`,
|
|
32
|
+
),
|
|
33
|
+
p(
|
|
34
|
+
{ className: "page-description" },
|
|
35
|
+
framework.entityDefinitions.level.description.trim(),
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
|
|
39
|
+
// Levels timeline
|
|
40
|
+
div(
|
|
41
|
+
{ className: "levels-timeline" },
|
|
42
|
+
...items.map((level) => createLevelTimelineItem(level)),
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
render(page);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a level timeline item
|
|
51
|
+
* @param {Object} level
|
|
52
|
+
* @returns {HTMLElement}
|
|
53
|
+
*/
|
|
54
|
+
function createLevelTimelineItem(level) {
|
|
55
|
+
const item = div(
|
|
56
|
+
{ className: "level-timeline-item" },
|
|
57
|
+
div({ className: "level-level-marker" }, String(level.ordinalRank)),
|
|
58
|
+
div(
|
|
59
|
+
{ className: "level-timeline-content card card-clickable" },
|
|
60
|
+
div(
|
|
61
|
+
{ className: "card-header" },
|
|
62
|
+
h3({ className: "card-title" }, level.displayName),
|
|
63
|
+
createBadge(level.id, "default"),
|
|
64
|
+
),
|
|
65
|
+
level.typicalExperienceRange
|
|
66
|
+
? p(
|
|
67
|
+
{ className: "text-muted", style: "margin: 0.25rem 0" },
|
|
68
|
+
`${level.typicalExperienceRange} experience`,
|
|
69
|
+
)
|
|
70
|
+
: null,
|
|
71
|
+
div(
|
|
72
|
+
{ className: "card-meta", style: "margin-top: 0.5rem" },
|
|
73
|
+
createBadge(
|
|
74
|
+
`Primary: ${formatLevel(level.baseSkillProficiencies?.primary)}`,
|
|
75
|
+
"primary",
|
|
76
|
+
),
|
|
77
|
+
createBadge(
|
|
78
|
+
`Secondary: ${formatLevel(level.baseSkillProficiencies?.secondary)}`,
|
|
79
|
+
"secondary",
|
|
80
|
+
),
|
|
81
|
+
createBadge(
|
|
82
|
+
`Broad: ${formatLevel(level.baseSkillProficiencies?.broad)}`,
|
|
83
|
+
"broad",
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
level.scope
|
|
87
|
+
? p(
|
|
88
|
+
{ className: "card-description", style: "margin-top: 0.75rem" },
|
|
89
|
+
`Scope: ${level.scope}`,
|
|
90
|
+
)
|
|
91
|
+
: null,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
item.querySelector(".card").addEventListener("click", () => {
|
|
96
|
+
window.location.hash = `/level/${level.id}`;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return item;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Render level detail page
|
|
104
|
+
* @param {Object} params - Route params
|
|
105
|
+
*/
|
|
106
|
+
export function renderLevelDetail(params) {
|
|
107
|
+
const { data } = getState();
|
|
108
|
+
const level = data.levels.find((g) => g.id === params.id);
|
|
109
|
+
|
|
110
|
+
if (!level) {
|
|
111
|
+
renderNotFound({
|
|
112
|
+
entityType: "Level",
|
|
113
|
+
entityId: params.id,
|
|
114
|
+
backPath: "/level",
|
|
115
|
+
backText: "← Back to Levels",
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Use DOM formatter - it handles transformation internally
|
|
121
|
+
render(levelToDOM(level, { framework: data.framework }));
|
|
122
|
+
}
|
|
@@ -18,7 +18,7 @@ export function renderCareerProgress() {
|
|
|
18
18
|
title: "Career Progress",
|
|
19
19
|
description:
|
|
20
20
|
"Select your current role to visualize career progression. See what skills and behaviours " +
|
|
21
|
-
"change as you advance to the next
|
|
21
|
+
"change as you advance to the next level, or compare expectations across different tracks.",
|
|
22
22
|
formTitle: "Select Your Current Role",
|
|
23
23
|
emptyPreviewText:
|
|
24
24
|
"Select all three components to preview progression paths.",
|
|
@@ -26,25 +26,25 @@ export function renderCareerProgress() {
|
|
|
26
26
|
previewPresenter: (selection) =>
|
|
27
27
|
prepareCareerProgressPreview({
|
|
28
28
|
...selection,
|
|
29
|
-
|
|
29
|
+
levels: data.levels,
|
|
30
30
|
tracks: data.tracks,
|
|
31
31
|
}),
|
|
32
32
|
detailPath: (sel) =>
|
|
33
33
|
sel.track
|
|
34
|
-
? `/progress/${sel.discipline}/${sel.
|
|
35
|
-
: `/progress/${sel.discipline}/${sel.
|
|
34
|
+
? `/progress/${sel.discipline}/${sel.level}/${sel.track}`
|
|
35
|
+
: `/progress/${sel.discipline}/${sel.level}`,
|
|
36
36
|
renderPreview: createProgressPreview,
|
|
37
37
|
labels: {
|
|
38
|
-
|
|
38
|
+
level: "Current Level",
|
|
39
39
|
},
|
|
40
40
|
helpItems: [
|
|
41
41
|
{
|
|
42
|
-
label: "📈
|
|
43
|
-
text: "See exactly which skills and behaviours need to grow to advance to the next
|
|
42
|
+
label: "📈 Level Progression",
|
|
43
|
+
text: "See exactly which skills and behaviours need to grow to advance to the next level rank.",
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
label: "🔀 Track Comparison",
|
|
47
|
-
text: "Compare how expectations differ across tracks at the same
|
|
47
|
+
text: "Compare how expectations differ across tracks at the same level - useful for exploring lateral moves.",
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
label: "🎯 Development Focus",
|