@forwardimpact/pathway 0.25.15 → 0.25.21

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.
Files changed (34) hide show
  1. package/bin/fit-pathway.js +62 -54
  2. package/package.json +1 -3
  3. package/src/commands/agent-io.js +120 -0
  4. package/src/commands/agent.js +266 -349
  5. package/src/commands/init.js +2 -2
  6. package/src/commands/job.js +237 -183
  7. package/src/components/comparison-radar.js +118 -103
  8. package/src/components/progression-table.js +244 -208
  9. package/src/formatters/index.js +0 -19
  10. package/src/formatters/interview/markdown.js +100 -88
  11. package/src/formatters/job/description.js +76 -75
  12. package/src/formatters/job/dom.js +113 -97
  13. package/src/formatters/level/dom.js +87 -102
  14. package/src/formatters/questions/markdown.js +37 -33
  15. package/src/formatters/questions/shared.js +142 -75
  16. package/src/formatters/skill/dom.js +102 -93
  17. package/src/lib/comparison-radar-chart.js +256 -0
  18. package/src/lib/radar-utils.js +199 -0
  19. package/src/lib/radar.js +25 -662
  20. package/src/pages/agent-builder-download.js +170 -0
  21. package/src/pages/agent-builder-preview.js +344 -0
  22. package/src/pages/agent-builder.js +6 -550
  23. package/src/pages/progress-comparison.js +110 -0
  24. package/src/pages/progress.js +11 -111
  25. package/src/pages/self-assessment-steps.js +494 -0
  26. package/src/pages/self-assessment.js +54 -504
  27. package/src/formatters/behaviour/microdata.js +0 -106
  28. package/src/formatters/discipline/microdata.js +0 -117
  29. package/src/formatters/driver/microdata.js +0 -91
  30. package/src/formatters/level/microdata.js +0 -141
  31. package/src/formatters/microdata-shared.js +0 -184
  32. package/src/formatters/skill/microdata.js +0 -151
  33. package/src/formatters/stage/microdata.js +0 -116
  34. package/src/formatters/track/microdata.js +0 -111
@@ -21,6 +21,213 @@ import { formatLevel } from "../lib/render.js";
21
21
  import { createLevelCell, createEmptyLevelCell } from "./detail.js";
22
22
  import { createBadge } from "./card.js";
23
23
 
24
+ /**
25
+ * Create a row for a gained (new) item
26
+ */
27
+ function createGainedRow(item, isSkill, maxLevels) {
28
+ const nameCell = td(
29
+ {},
30
+ a({ href: `#/${isSkill ? "skill" : "behaviour"}/${item.id}` }, item.name),
31
+ span({ className: "gained-badge" }, "NEW"),
32
+ );
33
+ const changeCell = td(
34
+ { className: "change-cell change-gained" },
35
+ span({ className: "change-indicator" }, "+"),
36
+ );
37
+ if (isSkill) {
38
+ return tr(
39
+ { className: "change-gained" },
40
+ nameCell,
41
+ td({}, createBadge(item.capability, item.capability)),
42
+ td({}, createBadge(formatLevel(item.type), item.type)),
43
+ createEmptyLevelCell(),
44
+ createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
45
+ changeCell,
46
+ );
47
+ }
48
+ return tr(
49
+ { className: "change-gained" },
50
+ nameCell,
51
+ createEmptyLevelCell(),
52
+ createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
53
+ changeCell,
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Create a row for a lost (removed) item
59
+ */
60
+ function createLostRow(item, isSkill, maxLevels) {
61
+ const nameCell = td(
62
+ {},
63
+ a({ href: `#/${isSkill ? "skill" : "behaviour"}/${item.id}` }, item.name),
64
+ span({ className: "lost-badge" }, "REMOVED"),
65
+ );
66
+ const changeCell = td(
67
+ { className: "change-cell change-lost" },
68
+ span({ className: "change-indicator" }, "−"),
69
+ );
70
+ if (isSkill) {
71
+ return tr(
72
+ { className: "change-lost" },
73
+ nameCell,
74
+ td({}, createBadge(item.capability, item.capability)),
75
+ td({}, createBadge(formatLevel(item.type), item.type)),
76
+ createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
77
+ createEmptyLevelCell(),
78
+ changeCell,
79
+ );
80
+ }
81
+ return tr(
82
+ { className: "change-lost" },
83
+ nameCell,
84
+ createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
85
+ createEmptyLevelCell(),
86
+ changeCell,
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Create a row for a normal change item
92
+ */
93
+ function createChangeRow(item, isSkill, maxLevels) {
94
+ const changeClass =
95
+ item.change > 0
96
+ ? "change-up"
97
+ : item.change < 0
98
+ ? "change-down"
99
+ : "change-same";
100
+ const changeIcon = item.change > 0 ? "↑" : item.change < 0 ? "↓" : "—";
101
+ const changeText =
102
+ item.change !== 0 ? `${changeIcon} ${Math.abs(item.change)}` : "—";
103
+
104
+ if (isSkill) {
105
+ return tr(
106
+ { className: changeClass },
107
+ td({}, a({ href: `#/skill/${item.id}` }, item.name)),
108
+ td({}, createBadge(item.capability, item.capability)),
109
+ td({}, createBadge(formatLevel(item.type), item.type)),
110
+ createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
111
+ createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
112
+ td(
113
+ { className: `change-cell ${changeClass}` },
114
+ span({ className: "change-indicator" }, changeText),
115
+ ),
116
+ );
117
+ }
118
+ return tr(
119
+ { className: changeClass },
120
+ td({}, a({ href: `#/behaviour/${item.id}` }, item.name)),
121
+ createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
122
+ createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
123
+ td(
124
+ { className: `change-cell ${changeClass}` },
125
+ span({ className: "change-indicator" }, changeText),
126
+ ),
127
+ );
128
+ }
129
+
130
+ /**
131
+ * Pluralize a type name
132
+ * @param {string} type
133
+ * @param {number} count
134
+ * @returns {string}
135
+ */
136
+ function pluralize(type, count) {
137
+ return count > 1 ? `${type}s` : type;
138
+ }
139
+
140
+ /**
141
+ * Create a progression group with header and table
142
+ * @param {Object} params
143
+ * @param {Array} params.items
144
+ * @param {string} params.icon
145
+ * @param {string} params.label
146
+ * @param {string} [params.headerClass]
147
+ * @param {string} params.tableClass
148
+ * @param {HTMLElement} params.headers
149
+ * @param {Function} params.createRow
150
+ * @returns {HTMLElement}
151
+ */
152
+ function createProgressionGroup({
153
+ items,
154
+ icon,
155
+ label,
156
+ headerClass,
157
+ tableClass,
158
+ headers,
159
+ createRow,
160
+ }) {
161
+ return div(
162
+ { className: "progression-group" },
163
+ div(
164
+ { className: `progression-group-header ${headerClass || ""}`.trim() },
165
+ span({ className: "group-icon" }, icon),
166
+ span({}, label),
167
+ ),
168
+ div(
169
+ { className: "table-container" },
170
+ table(
171
+ { className: tableClass },
172
+ thead({}, headers),
173
+ tbody({}, ...items.map(createRow)),
174
+ ),
175
+ ),
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Create a collapsible group for unchanged items
181
+ * @param {Object} params
182
+ * @param {Array} params.items
183
+ * @param {string} params.type
184
+ * @param {boolean} params.isSkill
185
+ * @param {string} params.tableClass
186
+ * @param {Function} params.createRow
187
+ * @returns {HTMLElement}
188
+ */
189
+ function createCollapsibleGroup({
190
+ items,
191
+ type,
192
+ isSkill,
193
+ tableClass,
194
+ createRow,
195
+ }) {
196
+ const noChangeHeaders = tr(
197
+ {},
198
+ th({}, isSkill ? "Skill" : "Behaviour"),
199
+ ...(isSkill ? [th({}, "Capability"), th({}, "Type")] : []),
200
+ th({}, "Current"),
201
+ th({}, "Target"),
202
+ th({}, "Change"),
203
+ );
204
+
205
+ const detailsId = `no-changes-${type}-${Date.now()}`;
206
+ return div(
207
+ { className: "progression-group no-change-group" },
208
+ createCollapsibleHeader(
209
+ detailsId,
210
+ `${items.length} ${pluralize(type, items.length)} unchanged`,
211
+ "✓",
212
+ ),
213
+ div(
214
+ {
215
+ className: "collapsible-content",
216
+ id: detailsId,
217
+ style: "display: none",
218
+ },
219
+ div(
220
+ { className: "table-container" },
221
+ table(
222
+ { className: tableClass },
223
+ thead({}, noChangeHeaders),
224
+ tbody({}, ...items.map(createRow)),
225
+ ),
226
+ ),
227
+ ),
228
+ );
229
+ }
230
+
24
231
  /**
25
232
  * Create a progression table showing changes
26
233
  * @param {SkillChangeItem[]|BehaviourChangeItem[]} changes - Array of change objects
@@ -44,116 +251,13 @@ export function createProgressionTable(changes, type = "skill") {
44
251
  const noChanges = changes.filter((c) => c.change === 0);
45
252
 
46
253
  const createRow = (item) => {
47
- // Handle gained skills (new in target role)
48
254
  if (item.isGained) {
49
- if (isSkill) {
50
- return tr(
51
- { className: "change-gained" },
52
- td(
53
- {},
54
- a({ href: `#/skill/${item.id}` }, item.name),
55
- span({ className: "gained-badge" }, "NEW"),
56
- ),
57
- td({}, createBadge(item.capability, item.capability)),
58
- td({}, createBadge(formatLevel(item.type), item.type)),
59
- createEmptyLevelCell(),
60
- createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
61
- td(
62
- { className: "change-cell change-gained" },
63
- span({ className: "change-indicator" }, "+"),
64
- ),
65
- );
66
- } else {
67
- return tr(
68
- { className: "change-gained" },
69
- td(
70
- {},
71
- a({ href: `#/behaviour/${item.id}` }, item.name),
72
- span({ className: "gained-badge" }, "NEW"),
73
- ),
74
- createEmptyLevelCell(),
75
- createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
76
- td(
77
- { className: "change-cell change-gained" },
78
- span({ className: "change-indicator" }, "+"),
79
- ),
80
- );
81
- }
255
+ return createGainedRow(item, isSkill, maxLevels);
82
256
  }
83
-
84
- // Handle lost skills (removed in target role)
85
257
  if (item.isLost) {
86
- if (isSkill) {
87
- return tr(
88
- { className: "change-lost" },
89
- td(
90
- {},
91
- a({ href: `#/skill/${item.id}` }, item.name),
92
- span({ className: "lost-badge" }, "REMOVED"),
93
- ),
94
- td({}, createBadge(item.capability, item.capability)),
95
- td({}, createBadge(formatLevel(item.type), item.type)),
96
- createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
97
- createEmptyLevelCell(),
98
- td(
99
- { className: "change-cell change-lost" },
100
- span({ className: "change-indicator" }, "−"),
101
- ),
102
- );
103
- } else {
104
- return tr(
105
- { className: "change-lost" },
106
- td(
107
- {},
108
- a({ href: `#/behaviour/${item.id}` }, item.name),
109
- span({ className: "lost-badge" }, "REMOVED"),
110
- ),
111
- createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
112
- createEmptyLevelCell(),
113
- td(
114
- { className: "change-cell change-lost" },
115
- span({ className: "change-indicator" }, "−"),
116
- ),
117
- );
118
- }
119
- }
120
-
121
- // Normal change (both current and target exist)
122
- const changeClass =
123
- item.change > 0
124
- ? "change-up"
125
- : item.change < 0
126
- ? "change-down"
127
- : "change-same";
128
- const changeIcon = item.change > 0 ? "↑" : item.change < 0 ? "↓" : "—";
129
- const changeText =
130
- item.change !== 0 ? `${changeIcon} ${Math.abs(item.change)}` : "—";
131
-
132
- if (isSkill) {
133
- return tr(
134
- { className: changeClass },
135
- td({}, a({ href: `#/skill/${item.id}` }, item.name)),
136
- td({}, createBadge(item.capability, item.capability)),
137
- td({}, createBadge(formatLevel(item.type), item.type)),
138
- createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
139
- createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
140
- td(
141
- { className: `change-cell ${changeClass}` },
142
- span({ className: "change-indicator" }, changeText),
143
- ),
144
- );
145
- } else {
146
- return tr(
147
- { className: changeClass },
148
- td({}, a({ href: `#/behaviour/${item.id}` }, item.name)),
149
- createLevelCell(item.currentIndex, maxLevels, item.currentLevel),
150
- createLevelCell(item.targetIndex, maxLevels, item.targetLevel),
151
- td(
152
- { className: `change-cell ${changeClass}` },
153
- span({ className: "change-indicator" }, changeText),
154
- ),
155
- );
258
+ return createLostRow(item, isSkill, maxLevels);
156
259
  }
260
+ return createChangeRow(item, isSkill, maxLevels);
157
261
  };
158
262
 
159
263
  const skillHeaders = tr(
@@ -165,127 +269,59 @@ export function createProgressionTable(changes, type = "skill") {
165
269
  th({}, "Change"),
166
270
  );
167
271
 
272
+ const tableClass = `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`;
168
273
  const content = [];
169
274
 
170
- // Show new skills first (in target role but not current)
171
275
  if (gained.length > 0) {
172
276
  content.push(
173
- div(
174
- { className: "progression-group" },
175
- div(
176
- { className: "progression-group-header gained-header" },
177
- span({ className: "group-icon" }, "➕"),
178
- span(
179
- {},
180
- `${gained.length} new ${type}${gained.length > 1 ? "s" : ""}`,
181
- ),
182
- ),
183
- div(
184
- { className: "table-container" },
185
- table(
186
- {
187
- className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
188
- },
189
- thead({}, skillHeaders),
190
- tbody({}, ...gained.map(createRow)),
191
- ),
192
- ),
193
- ),
277
+ createProgressionGroup({
278
+ items: gained,
279
+ icon: "➕",
280
+ label: `${gained.length} new ${pluralize(type, gained.length)}`,
281
+ headerClass: "gained-header",
282
+ tableClass,
283
+ headers: skillHeaders,
284
+ createRow,
285
+ }),
194
286
  );
195
287
  }
196
288
 
197
- // Show level changes
198
289
  if (changesRequired.length > 0) {
199
290
  content.push(
200
- div(
201
- { className: "progression-group" },
202
- div(
203
- { className: "progression-group-header" },
204
- span({ className: "group-icon" }, "📈"),
205
- span(
206
- {},
207
- `${changesRequired.length} ${type}${changesRequired.length > 1 ? "s" : ""} need growth`,
208
- ),
209
- ),
210
- div(
211
- { className: "table-container" },
212
- table(
213
- {
214
- className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
215
- },
216
- thead({}, skillHeaders),
217
- tbody({}, ...changesRequired.map(createRow)),
218
- ),
219
- ),
220
- ),
291
+ createProgressionGroup({
292
+ items: changesRequired,
293
+ icon: "📈",
294
+ label: `${changesRequired.length} ${pluralize(type, changesRequired.length)} need growth`,
295
+ tableClass,
296
+ headers: skillHeaders,
297
+ createRow,
298
+ }),
221
299
  );
222
300
  }
223
301
 
224
- // Show removed skills (in current role but not target)
225
302
  if (lost.length > 0) {
226
303
  content.push(
227
- div(
228
- { className: "progression-group" },
229
- div(
230
- { className: "progression-group-header lost-header" },
231
- span({ className: "group-icon" }, "➖"),
232
- span(
233
- {},
234
- `${lost.length} ${type}${lost.length > 1 ? "s" : ""} removed`,
235
- ),
236
- ),
237
- div(
238
- { className: "table-container" },
239
- table(
240
- {
241
- className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
242
- },
243
- thead({}, skillHeaders),
244
- tbody({}, ...lost.map(createRow)),
245
- ),
246
- ),
247
- ),
304
+ createProgressionGroup({
305
+ items: lost,
306
+ icon: "➖",
307
+ label: `${lost.length} ${pluralize(type, lost.length)} removed`,
308
+ headerClass: "lost-header",
309
+ tableClass,
310
+ headers: skillHeaders,
311
+ createRow,
312
+ }),
248
313
  );
249
314
  }
250
315
 
251
- // Show no changes (collapsed by default)
252
316
  if (noChanges.length > 0) {
253
- const noChangeHeaders = tr(
254
- {},
255
- th({}, isSkill ? "Skill" : "Behaviour"),
256
- ...(isSkill ? [th({}, "Capability"), th({}, "Type")] : []),
257
- th({}, "Current"),
258
- th({}, "Target"),
259
- th({}, "Change"),
260
- );
261
-
262
- const detailsId = `no-changes-${type}-${Date.now()}`;
263
317
  content.push(
264
- div(
265
- { className: "progression-group no-change-group" },
266
- createCollapsibleHeader(
267
- detailsId,
268
- `${noChanges.length} ${type}${noChanges.length > 1 ? "s" : ""} unchanged`,
269
- "✓",
270
- ),
271
- div(
272
- {
273
- className: "collapsible-content",
274
- id: detailsId,
275
- style: "display: none",
276
- },
277
- div(
278
- { className: "table-container" },
279
- table(
280
- {
281
- className: `table progression-table ${isSkill ? "skill-table" : "behaviour-table"}`,
282
- },
283
- thead({}, noChangeHeaders),
284
- tbody({}, ...noChanges.map(createRow)),
285
- ),
286
- ),
287
- ),
288
- ),
318
+ createCollapsibleGroup({
319
+ items: noChanges,
320
+ type,
321
+ isSkill,
322
+ tableClass,
323
+ createRow,
324
+ }),
289
325
  );
290
326
  }
291
327
 
@@ -7,7 +7,6 @@
7
7
 
8
8
  // Shared utilities
9
9
  export * from "./shared.js";
10
- export * from "./microdata-shared.js";
11
10
 
12
11
  // Job formatters
13
12
  export { jobToMarkdown } from "./job/markdown.js";
@@ -23,15 +22,10 @@ export { progressToDOM } from "./progress/dom.js";
23
22
 
24
23
  // Driver formatters
25
24
  export { driverToDOM } from "./driver/dom.js";
26
- export {
27
- driverListToMicrodata,
28
- driverToMicrodata,
29
- } from "./driver/microdata.js";
30
25
 
31
26
  // Skill formatters
32
27
  export { skillListToMarkdown, skillToMarkdown } from "./skill/markdown.js";
33
28
  export { skillToDOM } from "./skill/dom.js";
34
- export { skillListToMicrodata, skillToMicrodata } from "./skill/microdata.js";
35
29
 
36
30
  // Behaviour formatters
37
31
  export {
@@ -39,10 +33,6 @@ export {
39
33
  behaviourToMarkdown,
40
34
  } from "./behaviour/markdown.js";
41
35
  export { behaviourToDOM } from "./behaviour/dom.js";
42
- export {
43
- behaviourListToMicrodata,
44
- behaviourToMicrodata,
45
- } from "./behaviour/microdata.js";
46
36
 
47
37
  // Discipline formatters
48
38
  export {
@@ -50,23 +40,14 @@ export {
50
40
  disciplineToMarkdown,
51
41
  } from "./discipline/markdown.js";
52
42
  export { disciplineToDOM } from "./discipline/dom.js";
53
- export {
54
- disciplineListToMicrodata,
55
- disciplineToMicrodata,
56
- } from "./discipline/microdata.js";
57
43
 
58
44
  // Level formatters
59
45
  export { levelListToMarkdown, levelToMarkdown } from "./level/markdown.js";
60
46
  export { levelToDOM } from "./level/dom.js";
61
- export { levelListToMicrodata, levelToMicrodata } from "./level/microdata.js";
62
47
 
63
48
  // Track formatters
64
49
  export { trackListToMarkdown, trackToMarkdown } from "./track/markdown.js";
65
50
  export { trackToDOM } from "./track/dom.js";
66
- export { trackListToMicrodata, trackToMicrodata } from "./track/microdata.js";
67
-
68
- // Stage formatters
69
- export { stageListToMicrodata, stageToMicrodata } from "./stage/microdata.js";
70
51
 
71
52
  // JSON-LD formatters
72
53
  export {