@forwardimpact/pathway 0.21.0 → 0.23.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 +4 -3
- package/src/commands/agent.js +14 -10
- package/src/commands/behaviour.js +11 -1
- package/src/commands/build.js +11 -2
- package/src/commands/command-factory.js +4 -2
- package/src/commands/dev.js +9 -2
- package/src/commands/discipline.js +19 -2
- package/src/commands/driver.js +11 -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 +41 -28
- package/src/commands/level.js +76 -0
- package/src/commands/progress.js +20 -20
- package/src/commands/questions.js +3 -3
- package/src/commands/skill.js +11 -1
- package/src/commands/stage.js +11 -1
- package/src/commands/tool.js +4 -3
- package/src/commands/track.js +11 -1
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/card.js +8 -104
- package/src/components/comparison-radar.js +4 -4
- package/src/components/detail.js +18 -120
- package/src/components/error-page.js +8 -68
- package/src/components/grid.js +12 -106
- package/src/components/list.js +7 -116
- package/src/components/nav.js +7 -60
- package/src/components/radar-chart.js +3 -3
- package/src/components/skill-matrix.js +7 -7
- package/src/css/bundles/app.css +25 -21
- package/src/css/bundles/handout.css +33 -33
- package/src/css/bundles/slides.css +25 -25
- package/src/css/pages/landing.css +5 -5
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/shared.js +23 -23
- 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 +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 +51 -51
- 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 +3 -3
- package/src/formatters/track/shared.js +1 -1
- package/src/handout-main.js +12 -12
- package/src/handout.html +32 -13
- package/src/index.html +33 -14
- 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/error-boundary.js +3 -66
- package/src/lib/errors.js +7 -45
- package/src/lib/job-cache.js +12 -12
- package/src/lib/markdown.js +2 -109
- package/src/lib/reactive.js +7 -73
- package/src/lib/render.js +53 -201
- package/src/lib/router-core.js +2 -156
- package/src/lib/router-pages.js +2 -11
- package/src/lib/router-slides.js +2 -197
- package/src/lib/state.js +16 -65
- package/src/lib/utils.js +3 -10
- package/src/lib/yaml-loader.js +22 -80
- package/src/main.js +10 -10
- package/src/pages/agent-builder.js +12 -12
- package/src/pages/assessment-results.js +28 -24
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +8 -8
- package/src/pages/job-builder.js +7 -7
- package/src/pages/job.js +8 -8
- 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/pages/skill.js +1 -1
- package/src/slide-main.js +23 -23
- 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 +4 -4
- package/src/slides/level.js +32 -0
- package/src/slides/overview.js +10 -10
- package/src/slides/progress.js +13 -13
- package/src/slides.html +32 -13
- package/src/types.js +1 -1
- package/templates/job.template.md +2 -2
- package/src/commands/grade.js +0 -60
- package/src/css/base.css +0 -56
- package/src/css/components/badges.css +0 -232
- package/src/css/components/buttons.css +0 -101
- package/src/css/components/forms.css +0 -191
- package/src/css/components/layout.css +0 -218
- package/src/css/components/nav.css +0 -206
- package/src/css/components/progress.css +0 -166
- package/src/css/components/states.css +0 -82
- package/src/css/components/surfaces.css +0 -347
- package/src/css/components/tables.css +0 -362
- package/src/css/components/top-bar.css +0 -180
- package/src/css/components/typography.css +0 -121
- package/src/css/components/utilities.css +0 -41
- package/src/css/pages/detail.css +0 -119
- package/src/css/reset.css +0 -50
- package/src/css/tokens.css +0 -162
- package/src/css/views/handout.css +0 -30
- package/src/css/views/print.css +0 -634
- package/src/css/views/slide-animations.css +0 -113
- package/src/css/views/slide-base.css +0 -331
- package/src/css/views/slide-sections.css +0 -597
- package/src/css/views/slide-tables.css +0 -275
- package/src/formatters/grade/shared.js +0 -86
- package/src/pages/grade.js +0 -122
- package/src/slides/grade.js +0 -32
package/src/lib/state.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Application state management
|
|
3
|
+
*
|
|
4
|
+
* Uses generic store from @forwardimpact/libui/state
|
|
5
|
+
* with Pathway-specific state shape and accessors.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
* @typedef {Object} AppState
|
|
7
|
-
* @property {Object} data - Loaded data from YAML files
|
|
8
|
-
* @property {Object} ui - UI state
|
|
9
|
-
*/
|
|
8
|
+
import { createStore } from "@forwardimpact/libui/state";
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
const state = {
|
|
10
|
+
const store = createStore({
|
|
13
11
|
data: {
|
|
14
12
|
skills: [],
|
|
15
13
|
behaviours: [],
|
|
16
14
|
disciplines: [],
|
|
17
15
|
tracks: [],
|
|
18
|
-
|
|
16
|
+
levels: [],
|
|
19
17
|
drivers: [],
|
|
20
18
|
questions: {},
|
|
21
19
|
capabilities: [],
|
|
@@ -31,52 +29,22 @@ const state = {
|
|
|
31
29
|
behaviours: { search: "" },
|
|
32
30
|
disciplines: { search: "" },
|
|
33
31
|
tracks: { search: "" },
|
|
34
|
-
|
|
32
|
+
levels: { search: "" },
|
|
35
33
|
drivers: { search: "" },
|
|
36
34
|
},
|
|
37
35
|
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/** @type {Set<Function>} */
|
|
41
|
-
const listeners = new Set();
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get the current state
|
|
45
|
-
* @returns {AppState}
|
|
46
|
-
*/
|
|
47
|
-
export function getState() {
|
|
48
|
-
return state;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get a specific path from state
|
|
53
|
-
* @param {string} path - Dot-notation path (e.g., 'data.skills')
|
|
54
|
-
* @returns {*}
|
|
55
|
-
*/
|
|
56
|
-
export function getStatePath(path) {
|
|
57
|
-
return path.split(".").reduce((obj, key) => obj?.[key], state);
|
|
58
|
-
}
|
|
36
|
+
});
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
* Update state at a specific path
|
|
62
|
-
* @param {string} path - Dot-notation path
|
|
63
|
-
* @param {*} value - New value
|
|
64
|
-
*/
|
|
65
|
-
export function updateState(path, value) {
|
|
66
|
-
const keys = path.split(".");
|
|
67
|
-
const lastKey = keys.pop();
|
|
68
|
-
const target = keys.reduce((obj, key) => obj[key], state);
|
|
69
|
-
target[lastKey] = value;
|
|
70
|
-
notifyListeners();
|
|
71
|
-
}
|
|
38
|
+
export const { getState, getStatePath, updateState, subscribe } = store;
|
|
72
39
|
|
|
73
40
|
/**
|
|
74
41
|
* Merge data into state
|
|
75
42
|
* @param {Object} data - Data to merge
|
|
76
43
|
*/
|
|
77
44
|
export function setData(data) {
|
|
45
|
+
const state = getState();
|
|
78
46
|
Object.assign(state.data, data, { loaded: true, error: null });
|
|
79
|
-
|
|
47
|
+
updateState("data", state.data);
|
|
80
48
|
}
|
|
81
49
|
|
|
82
50
|
/**
|
|
@@ -84,25 +52,7 @@ export function setData(data) {
|
|
|
84
52
|
* @param {Error} error
|
|
85
53
|
*/
|
|
86
54
|
export function setError(error) {
|
|
87
|
-
|
|
88
|
-
notifyListeners();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Subscribe to state changes
|
|
93
|
-
* @param {Function} listener
|
|
94
|
-
* @returns {Function} Unsubscribe function
|
|
95
|
-
*/
|
|
96
|
-
export function subscribe(listener) {
|
|
97
|
-
listeners.add(listener);
|
|
98
|
-
return () => listeners.delete(listener);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Notify all listeners of state change
|
|
103
|
-
*/
|
|
104
|
-
function notifyListeners() {
|
|
105
|
-
listeners.forEach((listener) => listener(state));
|
|
55
|
+
updateState("data.error", error.message);
|
|
106
56
|
}
|
|
107
57
|
|
|
108
58
|
/**
|
|
@@ -112,9 +62,10 @@ function notifyListeners() {
|
|
|
112
62
|
* @param {*} value - Filter value
|
|
113
63
|
*/
|
|
114
64
|
export function setFilter(entity, filterKey, value) {
|
|
65
|
+
const state = getState();
|
|
115
66
|
if (state.ui.filters[entity]) {
|
|
116
67
|
state.ui.filters[entity][filterKey] = value;
|
|
117
|
-
|
|
68
|
+
updateState("ui.filters", state.ui.filters);
|
|
118
69
|
}
|
|
119
70
|
}
|
|
120
71
|
|
|
@@ -124,7 +75,7 @@ export function setFilter(entity, filterKey, value) {
|
|
|
124
75
|
* @returns {Object}
|
|
125
76
|
*/
|
|
126
77
|
export function getFilters(entity) {
|
|
127
|
-
return
|
|
78
|
+
return getState().ui.filters[entity] || {};
|
|
128
79
|
}
|
|
129
80
|
|
|
130
81
|
/**
|
|
@@ -140,7 +91,7 @@ export function getFilters(entity) {
|
|
|
140
91
|
* @returns {Branding}
|
|
141
92
|
*/
|
|
142
93
|
export function getBranding() {
|
|
143
|
-
const { framework } =
|
|
94
|
+
const { framework } = getState().data;
|
|
144
95
|
return {
|
|
145
96
|
title: framework.title || "Engineering Pathway",
|
|
146
97
|
tag: framework.tag || "#BenchTools",
|
package/src/lib/utils.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* General utility functions
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from @forwardimpact/libui/utils.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
* Get an array of items by their IDs
|
|
7
|
-
* @param {Array} items - Array of items with id property
|
|
8
|
-
* @param {string[]} ids - Array of IDs to find
|
|
9
|
-
* @returns {Array} - Found items, filtered to remove nulls
|
|
10
|
-
*/
|
|
11
|
-
export function getItemsByIds(items, ids) {
|
|
12
|
-
if (!ids) return [];
|
|
13
|
-
return ids.map((id) => items.find((item) => item.id === id)).filter(Boolean);
|
|
14
|
-
}
|
|
7
|
+
export { getItemsByIds } from "@forwardimpact/libui/utils";
|
package/src/lib/yaml-loader.js
CHANGED
|
@@ -1,56 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser-compatible YAML loading
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Generic utilities from @forwardimpact/libui/yaml-loader,
|
|
5
|
+
* plus Pathway-specific entity loaders.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
export {
|
|
9
|
+
loadYamlFile,
|
|
10
|
+
tryLoadYamlFile,
|
|
11
|
+
loadDirIndex,
|
|
12
|
+
} from "@forwardimpact/libui/yaml-loader";
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export async function loadYamlFile(path) {
|
|
16
|
-
const response = await fetch(path);
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
`Failed to load ${path}: ${response.status} ${response.statusText}`,
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
const text = await response.text();
|
|
23
|
-
return parseYaml(text);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Try to load a YAML file, return null if not found
|
|
28
|
-
* @param {string} path - Path to the YAML file
|
|
29
|
-
* @returns {Promise<*|null>}
|
|
30
|
-
*/
|
|
31
|
-
async function tryLoadYamlFile(path) {
|
|
32
|
-
const response = await fetch(path);
|
|
33
|
-
if (response.status === 404) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
if (!response.ok) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`Failed to load ${path}: ${response.status} ${response.statusText}`,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
const text = await response.text();
|
|
42
|
-
return parseYaml(text);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Load directory index (list of file IDs)
|
|
47
|
-
* @param {string} dir - Directory path
|
|
48
|
-
* @returns {Promise<string[]>} Array of file IDs
|
|
49
|
-
*/
|
|
50
|
-
async function loadDirIndex(dir) {
|
|
51
|
-
const index = await loadYamlFile(`${dir}/_index.yaml`);
|
|
52
|
-
return index.files || [];
|
|
53
|
-
}
|
|
14
|
+
import {
|
|
15
|
+
loadYamlFile,
|
|
16
|
+
tryLoadYamlFile,
|
|
17
|
+
loadDirIndex,
|
|
18
|
+
} from "@forwardimpact/libui/yaml-loader";
|
|
54
19
|
|
|
55
20
|
/**
|
|
56
21
|
* Load skills from capability files
|
|
@@ -86,7 +51,7 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
86
51
|
name,
|
|
87
52
|
capability: capabilityId, // Add capability from parent
|
|
88
53
|
description: human.description,
|
|
89
|
-
|
|
54
|
+
proficiencyDescriptions: human.proficiencyDescriptions,
|
|
90
55
|
// Include isHumanOnly flag for agent filtering (defaults to false)
|
|
91
56
|
...(isHumanOnly && { isHumanOnly }),
|
|
92
57
|
...(agent && { agent }),
|
|
@@ -115,23 +80,18 @@ async function loadDisciplinesFromDir(disciplinesDir) {
|
|
|
115
80
|
const disciplines = await Promise.all(
|
|
116
81
|
disciplineIds.map(async (id) => {
|
|
117
82
|
const content = await loadYamlFile(`${disciplinesDir}/${id}.yaml`);
|
|
118
|
-
// Shared content at top level, role summaries under human:
|
|
119
83
|
const {
|
|
120
84
|
specialization,
|
|
121
85
|
roleTitle,
|
|
122
|
-
// Track constraints
|
|
123
86
|
isProfessional,
|
|
124
87
|
isManagement,
|
|
125
88
|
validTracks,
|
|
126
|
-
|
|
127
|
-
// Shared content - now at root level
|
|
89
|
+
minLevel,
|
|
128
90
|
description,
|
|
129
|
-
// Structural properties (derivation inputs)
|
|
130
91
|
coreSkills,
|
|
131
92
|
supportingSkills,
|
|
132
93
|
broadSkills,
|
|
133
94
|
behaviourModifiers,
|
|
134
|
-
// Presentation sections
|
|
135
95
|
human,
|
|
136
96
|
agent,
|
|
137
97
|
} = content;
|
|
@@ -139,19 +99,15 @@ async function loadDisciplinesFromDir(disciplinesDir) {
|
|
|
139
99
|
id,
|
|
140
100
|
specialization,
|
|
141
101
|
roleTitle,
|
|
142
|
-
// Track constraints
|
|
143
102
|
isProfessional,
|
|
144
103
|
isManagement,
|
|
145
104
|
validTracks,
|
|
146
|
-
|
|
147
|
-
// Shared content at top level
|
|
105
|
+
minLevel,
|
|
148
106
|
description,
|
|
149
|
-
// Structural properties
|
|
150
107
|
coreSkills,
|
|
151
108
|
supportingSkills,
|
|
152
109
|
broadSkills,
|
|
153
110
|
behaviourModifiers,
|
|
154
|
-
// Human presentation content (role summaries only)
|
|
155
111
|
...human,
|
|
156
112
|
...(agent && { agent }),
|
|
157
113
|
};
|
|
@@ -171,31 +127,25 @@ async function loadTracksFromDir(tracksDir) {
|
|
|
171
127
|
const tracks = await Promise.all(
|
|
172
128
|
trackIds.map(async (id) => {
|
|
173
129
|
const content = await loadYamlFile(`${tracksDir}/${id}.yaml`);
|
|
174
|
-
// Shared content at top level (no human section for tracks anymore)
|
|
175
130
|
const {
|
|
176
131
|
name,
|
|
177
|
-
// Shared content - now at root level
|
|
178
132
|
description,
|
|
179
133
|
roleContext,
|
|
180
|
-
// Structural properties (derivation inputs)
|
|
181
134
|
skillModifiers,
|
|
182
135
|
behaviourModifiers,
|
|
183
136
|
matchingWeights,
|
|
184
|
-
|
|
185
|
-
// Agent section (no human section anymore for tracks)
|
|
137
|
+
minLevel,
|
|
186
138
|
agent,
|
|
187
139
|
} = content;
|
|
188
140
|
return {
|
|
189
141
|
id,
|
|
190
142
|
name,
|
|
191
|
-
// Shared content at top level
|
|
192
143
|
description,
|
|
193
144
|
roleContext,
|
|
194
|
-
// Structural properties
|
|
195
145
|
skillModifiers,
|
|
196
146
|
behaviourModifiers,
|
|
197
147
|
matchingWeights,
|
|
198
|
-
|
|
148
|
+
minLevel,
|
|
199
149
|
...(agent && { agent }),
|
|
200
150
|
};
|
|
201
151
|
}),
|
|
@@ -214,7 +164,6 @@ async function loadBehavioursFromDir(behavioursDir) {
|
|
|
214
164
|
const behaviours = await Promise.all(
|
|
215
165
|
behaviourIds.map(async (id) => {
|
|
216
166
|
const content = await loadYamlFile(`${behavioursDir}/${id}.yaml`);
|
|
217
|
-
// Flatten human properties to top level (behaviours use human: section in YAML)
|
|
218
167
|
const { name, human, agent } = content;
|
|
219
168
|
return {
|
|
220
169
|
id,
|
|
@@ -285,7 +234,7 @@ async function loadQuestionFolder(
|
|
|
285
234
|
);
|
|
286
235
|
|
|
287
236
|
return {
|
|
288
|
-
|
|
237
|
+
skillProficiencies: Object.fromEntries(skillEntries),
|
|
289
238
|
behaviourMaturities: Object.fromEntries(behaviourEntries),
|
|
290
239
|
capabilityLevels: Object.fromEntries(capabilityEntries),
|
|
291
240
|
};
|
|
@@ -297,25 +246,20 @@ async function loadQuestionFolder(
|
|
|
297
246
|
* @returns {Promise<Object>}
|
|
298
247
|
*/
|
|
299
248
|
export async function loadAllData(dataDir = "./data") {
|
|
300
|
-
// Load capabilities first (skills are embedded in capabilities)
|
|
301
249
|
const capabilities = await loadCapabilitiesFromDir(`${dataDir}/capabilities`);
|
|
302
|
-
|
|
303
|
-
// Extract skills from capabilities
|
|
304
250
|
const skills = await loadSkillsFromCapabilities(`${dataDir}/capabilities`);
|
|
305
251
|
|
|
306
|
-
|
|
307
|
-
const [drivers, behaviours, disciplines, tracks, grades, stages, framework] =
|
|
252
|
+
const [drivers, behaviours, disciplines, tracks, levels, stages, framework] =
|
|
308
253
|
await Promise.all([
|
|
309
254
|
loadYamlFile(`${dataDir}/drivers.yaml`),
|
|
310
255
|
loadBehavioursFromDir(`${dataDir}/behaviours`),
|
|
311
256
|
loadDisciplinesFromDir(`${dataDir}/disciplines`),
|
|
312
257
|
loadTracksFromDir(`${dataDir}/tracks`),
|
|
313
|
-
loadYamlFile(`${dataDir}/
|
|
258
|
+
loadYamlFile(`${dataDir}/levels.yaml`),
|
|
314
259
|
loadYamlFile(`${dataDir}/stages.yaml`),
|
|
315
260
|
loadYamlFile(`${dataDir}/framework.yaml`),
|
|
316
261
|
]);
|
|
317
262
|
|
|
318
|
-
// Load questions using skill/behaviour/capability IDs
|
|
319
263
|
const questions = await loadQuestionFolder(
|
|
320
264
|
`${dataDir}/questions`,
|
|
321
265
|
skills,
|
|
@@ -329,7 +273,7 @@ export async function loadAllData(dataDir = "./data") {
|
|
|
329
273
|
skills,
|
|
330
274
|
disciplines,
|
|
331
275
|
tracks,
|
|
332
|
-
|
|
276
|
+
levels,
|
|
333
277
|
questions,
|
|
334
278
|
capabilities,
|
|
335
279
|
stages,
|
|
@@ -339,9 +283,8 @@ export async function loadAllData(dataDir = "./data") {
|
|
|
339
283
|
|
|
340
284
|
/**
|
|
341
285
|
* Load agent-specific data for browser-based agent generation
|
|
342
|
-
* Uses co-located files where agent sections are embedded in entity files
|
|
343
286
|
* @param {string} [dataDir='./data'] - Path to data directory
|
|
344
|
-
* @returns {Promise<Object>}
|
|
287
|
+
* @returns {Promise<Object>}
|
|
345
288
|
*/
|
|
346
289
|
export async function loadAgentDataBrowser(dataDir = "./data") {
|
|
347
290
|
const [
|
|
@@ -360,7 +303,6 @@ export async function loadAgentDataBrowser(dataDir = "./data") {
|
|
|
360
303
|
tryLoadYamlFile(`${dataDir}/copilot-setup-steps.yaml`),
|
|
361
304
|
]);
|
|
362
305
|
|
|
363
|
-
// Extract agent sections from co-located files
|
|
364
306
|
return {
|
|
365
307
|
disciplines: disciplines
|
|
366
308
|
.filter((d) => d.agent)
|
package/src/main.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
renderDisciplineDetail,
|
|
26
26
|
} from "./pages/discipline.js";
|
|
27
27
|
import { renderTracksList, renderTrackDetail } from "./pages/track.js";
|
|
28
|
-
import {
|
|
28
|
+
import { renderLevelsList, renderLevelDetail } from "./pages/level.js";
|
|
29
29
|
import { renderDriversList, renderDriverDetail } from "./pages/driver.js";
|
|
30
30
|
import { renderStagesList, renderStageDetail } from "./pages/stage.js";
|
|
31
31
|
import { renderToolsList } from "./pages/tool.js";
|
|
@@ -99,9 +99,9 @@ function setupRoutes() {
|
|
|
99
99
|
router.on("/track", renderTracksList);
|
|
100
100
|
router.on("/track/:id", renderTrackDetail);
|
|
101
101
|
|
|
102
|
-
//
|
|
103
|
-
router.on("/
|
|
104
|
-
router.on("/
|
|
102
|
+
// Level
|
|
103
|
+
router.on("/level", renderLevelsList);
|
|
104
|
+
router.on("/level/:id", renderLevelDetail);
|
|
105
105
|
|
|
106
106
|
// Driver
|
|
107
107
|
router.on("/driver", renderDriversList);
|
|
@@ -116,18 +116,18 @@ function setupRoutes() {
|
|
|
116
116
|
|
|
117
117
|
// Job builder
|
|
118
118
|
router.on("/job-builder", renderJobBuilder);
|
|
119
|
-
router.on("/job/:discipline/:
|
|
120
|
-
router.on("/job/:discipline/:
|
|
119
|
+
router.on("/job/:discipline/:level/:track", renderJobDetail);
|
|
120
|
+
router.on("/job/:discipline/:level", renderJobDetail);
|
|
121
121
|
|
|
122
122
|
// Interview prep
|
|
123
123
|
router.on("/interview-prep", renderInterviewPrep);
|
|
124
|
-
router.on("/interview/:discipline/:
|
|
125
|
-
router.on("/interview/:discipline/:
|
|
124
|
+
router.on("/interview/:discipline/:level/:track", renderInterviewDetail);
|
|
125
|
+
router.on("/interview/:discipline/:level", renderInterviewDetail);
|
|
126
126
|
|
|
127
127
|
// Career progress
|
|
128
128
|
router.on("/career-progress", renderCareerProgress);
|
|
129
|
-
router.on("/progress/:discipline/:
|
|
130
|
-
router.on("/progress/:discipline/:
|
|
129
|
+
router.on("/progress/:discipline/:level/:track", renderProgressDetail);
|
|
130
|
+
router.on("/progress/:discipline/:level", renderProgressDetail);
|
|
131
131
|
|
|
132
132
|
// Self-assessment
|
|
133
133
|
router.on("/self-assessment", renderSelfAssessment);
|
|
@@ -26,10 +26,10 @@ import {
|
|
|
26
26
|
deriveStageAgent,
|
|
27
27
|
generateSkillMarkdown,
|
|
28
28
|
deriveAgentSkills,
|
|
29
|
-
|
|
29
|
+
deriveReferenceLevel,
|
|
30
30
|
deriveToolkit,
|
|
31
31
|
buildAgentIndex,
|
|
32
|
-
} from "@forwardimpact/
|
|
32
|
+
} from "@forwardimpact/libskill";
|
|
33
33
|
import {
|
|
34
34
|
createSelectWithValue,
|
|
35
35
|
createDisciplineSelect,
|
|
@@ -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
|
|
|
@@ -19,7 +19,7 @@ import { getState } from "../lib/state.js";
|
|
|
19
19
|
import { createBadge } from "../components/card.js";
|
|
20
20
|
import { formatLevel } from "../lib/render.js";
|
|
21
21
|
import { getAssessmentState, resetAssessment } from "./self-assessment.js";
|
|
22
|
-
import { findRealisticMatches } from "@forwardimpact/
|
|
22
|
+
import { findRealisticMatches } from "@forwardimpact/libskill/matching";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Render the assessment results page
|
|
@@ -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",
|