@forwardimpact/map 0.11.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.
Files changed (83) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +67 -0
  3. package/bin/fit-map.js +287 -0
  4. package/examples/behaviours/_index.yaml +8 -0
  5. package/examples/behaviours/outcome_ownership.yaml +43 -0
  6. package/examples/behaviours/polymathic_knowledge.yaml +41 -0
  7. package/examples/behaviours/precise_communication.yaml +39 -0
  8. package/examples/behaviours/relentless_curiosity.yaml +37 -0
  9. package/examples/behaviours/systems_thinking.yaml +40 -0
  10. package/examples/capabilities/_index.yaml +8 -0
  11. package/examples/capabilities/business.yaml +205 -0
  12. package/examples/capabilities/delivery.yaml +1001 -0
  13. package/examples/capabilities/people.yaml +68 -0
  14. package/examples/capabilities/reliability.yaml +349 -0
  15. package/examples/capabilities/scale.yaml +1672 -0
  16. package/examples/copilot-setup-steps.yaml +25 -0
  17. package/examples/devcontainer.yaml +21 -0
  18. package/examples/disciplines/_index.yaml +6 -0
  19. package/examples/disciplines/data_engineering.yaml +68 -0
  20. package/examples/disciplines/engineering_management.yaml +61 -0
  21. package/examples/disciplines/software_engineering.yaml +68 -0
  22. package/examples/drivers.yaml +202 -0
  23. package/examples/framework.yaml +73 -0
  24. package/examples/levels.yaml +115 -0
  25. package/examples/questions/behaviours/outcome_ownership.yaml +228 -0
  26. package/examples/questions/behaviours/polymathic_knowledge.yaml +275 -0
  27. package/examples/questions/behaviours/precise_communication.yaml +248 -0
  28. package/examples/questions/behaviours/relentless_curiosity.yaml +248 -0
  29. package/examples/questions/behaviours/systems_thinking.yaml +238 -0
  30. package/examples/questions/capabilities/business.yaml +107 -0
  31. package/examples/questions/capabilities/delivery.yaml +101 -0
  32. package/examples/questions/capabilities/people.yaml +106 -0
  33. package/examples/questions/capabilities/reliability.yaml +105 -0
  34. package/examples/questions/capabilities/scale.yaml +104 -0
  35. package/examples/questions/skills/architecture_design.yaml +115 -0
  36. package/examples/questions/skills/cloud_platforms.yaml +105 -0
  37. package/examples/questions/skills/code_quality.yaml +162 -0
  38. package/examples/questions/skills/data_modeling.yaml +107 -0
  39. package/examples/questions/skills/devops.yaml +111 -0
  40. package/examples/questions/skills/full_stack_development.yaml +118 -0
  41. package/examples/questions/skills/sre_practices.yaml +113 -0
  42. package/examples/questions/skills/stakeholder_management.yaml +116 -0
  43. package/examples/questions/skills/team_collaboration.yaml +106 -0
  44. package/examples/questions/skills/technical_writing.yaml +110 -0
  45. package/examples/self-assessments.yaml +64 -0
  46. package/examples/stages.yaml +191 -0
  47. package/examples/tracks/_index.yaml +5 -0
  48. package/examples/tracks/platform.yaml +47 -0
  49. package/examples/tracks/sre.yaml +46 -0
  50. package/examples/vscode-settings.yaml +21 -0
  51. package/package.json +49 -0
  52. package/schema/json/behaviour-questions.schema.json +95 -0
  53. package/schema/json/behaviour.schema.json +73 -0
  54. package/schema/json/capability-questions.schema.json +95 -0
  55. package/schema/json/capability.schema.json +229 -0
  56. package/schema/json/defs.schema.json +132 -0
  57. package/schema/json/discipline.schema.json +123 -0
  58. package/schema/json/drivers.schema.json +48 -0
  59. package/schema/json/framework.schema.json +68 -0
  60. package/schema/json/levels.schema.json +121 -0
  61. package/schema/json/self-assessments.schema.json +52 -0
  62. package/schema/json/skill-questions.schema.json +83 -0
  63. package/schema/json/stages.schema.json +88 -0
  64. package/schema/json/track.schema.json +95 -0
  65. package/schema/rdf/behaviour-questions.ttl +128 -0
  66. package/schema/rdf/behaviour.ttl +130 -0
  67. package/schema/rdf/capability.ttl +466 -0
  68. package/schema/rdf/defs.ttl +396 -0
  69. package/schema/rdf/discipline.ttl +313 -0
  70. package/schema/rdf/drivers.ttl +84 -0
  71. package/schema/rdf/framework.ttl +166 -0
  72. package/schema/rdf/levels.ttl +357 -0
  73. package/schema/rdf/self-assessments.ttl +147 -0
  74. package/schema/rdf/skill-questions.ttl +155 -0
  75. package/schema/rdf/stages.ttl +166 -0
  76. package/schema/rdf/track.ttl +225 -0
  77. package/src/index-generator.js +65 -0
  78. package/src/index.js +44 -0
  79. package/src/levels.js +553 -0
  80. package/src/loader.js +608 -0
  81. package/src/modifiers.js +23 -0
  82. package/src/schema-validation.js +438 -0
  83. package/src/validation.js +2136 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Directory Index Generator
3
+ *
4
+ * Generates _index.yaml files for browser-based directory discovery.
5
+ * These files list all entity files in a directory.
6
+ */
7
+
8
+ import { readdir, writeFile } from "fs/promises";
9
+ import { join, basename } from "path";
10
+ import { stringify as stringifyYaml } from "yaml";
11
+
12
+ /**
13
+ * Generate _index.yaml for a directory
14
+ * @param {string} dir - Directory path
15
+ * @returns {Promise<string[]>} List of file IDs included
16
+ */
17
+ export async function generateDirIndex(dir) {
18
+ const files = await readdir(dir);
19
+ const yamlFiles = files.filter(
20
+ (f) => f.endsWith(".yaml") && !f.startsWith("_"),
21
+ );
22
+
23
+ const fileIds = yamlFiles.map((f) => basename(f, ".yaml")).sort();
24
+
25
+ const content = stringifyYaml(
26
+ {
27
+ // Auto-generated index for browser loading
28
+ // Do not edit manually - regenerate with: npx pathway --generate-index
29
+ files: fileIds,
30
+ },
31
+ { lineWidth: 0 },
32
+ );
33
+
34
+ // Add header comment
35
+ const output = `# Auto-generated index for browser loading
36
+ # Do not edit manually - regenerate with: npx pathway --generate-index
37
+ ${content}`;
38
+
39
+ await writeFile(join(dir, "_index.yaml"), output, "utf-8");
40
+
41
+ return fileIds;
42
+ }
43
+
44
+ /**
45
+ * Generate all index files for the data directory
46
+ * @param {string} dataDir - Path to the data directory
47
+ * @returns {Promise<Object>} Summary of generated indexes
48
+ */
49
+ export async function generateAllIndexes(dataDir) {
50
+ const directories = ["behaviours", "disciplines", "tracks", "capabilities"];
51
+
52
+ const results = {};
53
+
54
+ for (const dir of directories) {
55
+ const fullPath = join(dataDir, dir);
56
+ try {
57
+ const files = await generateDirIndex(fullPath);
58
+ results[dir] = files;
59
+ } catch (err) {
60
+ results[dir] = { error: err.message };
61
+ }
62
+ }
63
+
64
+ return results;
65
+ }
package/src/index.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @forwardimpact/map
3
+ *
4
+ * Data model definitions, validation, and loading for Engineering Pathway.
5
+ */
6
+
7
+ // Data loading
8
+ export {
9
+ loadAllData,
10
+ loadYamlFile,
11
+ createDataLoader,
12
+ loadFrameworkConfig,
13
+ loadQuestionFolder,
14
+ loadQuestionBankFromFolder,
15
+ loadSelfAssessments,
16
+ loadExampleData,
17
+ loadAndValidate,
18
+ loadAgentData,
19
+ loadSkillsWithAgentData,
20
+ } from "./loader.js";
21
+
22
+ // Referential integrity validation
23
+ export {
24
+ validateAllData,
25
+ validateQuestionBank,
26
+ validateSelfAssessment,
27
+ validateAgentData,
28
+ } from "./validation.js";
29
+
30
+ // Schema-based validation
31
+ export {
32
+ validateDataDirectory,
33
+ validateReferentialIntegrity,
34
+ runSchemaValidation,
35
+ } from "./schema-validation.js";
36
+
37
+ // Index generation
38
+ export { generateAllIndexes, generateDirIndex } from "./index-generator.js";
39
+
40
+ // Type constants and helpers
41
+ export * from "./levels.js";
42
+
43
+ // Capability validation helper
44
+ export { isCapability } from "./modifiers.js";
package/src/levels.js ADDED
@@ -0,0 +1,553 @@
1
+ /**
2
+ * Engineering Pathway Type Definitions
3
+ *
4
+ * This module defines all data structures used in the engineering pathway.
5
+ */
6
+
7
+ /**
8
+ * Skill proficiencies from lowest to highest proficiency
9
+ * @readonly
10
+ * @enum {string}
11
+ */
12
+ export const SkillProficiency = {
13
+ AWARENESS: "awareness",
14
+ FOUNDATIONAL: "foundational",
15
+ WORKING: "working",
16
+ PRACTITIONER: "practitioner",
17
+ EXPERT: "expert",
18
+ };
19
+
20
+ /**
21
+ * Ordered array of skill proficiencies for comparison/clamping
22
+ * @type {string[]}
23
+ */
24
+ export const SKILL_PROFICIENCY_ORDER = [
25
+ SkillProficiency.AWARENESS,
26
+ SkillProficiency.FOUNDATIONAL,
27
+ SkillProficiency.WORKING,
28
+ SkillProficiency.PRACTITIONER,
29
+ SkillProficiency.EXPERT,
30
+ ];
31
+
32
+ /**
33
+ * Behaviour maturity levels from lowest to highest
34
+ * @readonly
35
+ * @enum {string}
36
+ */
37
+ export const BehaviourMaturity = {
38
+ EMERGING: "emerging",
39
+ DEVELOPING: "developing",
40
+ PRACTICING: "practicing",
41
+ ROLE_MODELING: "role_modeling",
42
+ EXEMPLIFYING: "exemplifying",
43
+ };
44
+
45
+ /**
46
+ * Ordered array of behaviour maturity levels for comparison/clamping
47
+ * @type {string[]}
48
+ */
49
+ export const BEHAVIOUR_MATURITY_ORDER = [
50
+ BehaviourMaturity.EMERGING,
51
+ BehaviourMaturity.DEVELOPING,
52
+ BehaviourMaturity.PRACTICING,
53
+ BehaviourMaturity.ROLE_MODELING,
54
+ BehaviourMaturity.EXEMPLIFYING,
55
+ ];
56
+
57
+ // ============================================================================
58
+ // Data-driven Stage Functions
59
+ // ============================================================================
60
+ // Stage ordering is derived from loaded stage data, not hardcoded.
61
+ // Use getStageOrder(stages) to get stage IDs in lifecycle order.
62
+
63
+ /**
64
+ * Get ordered stage IDs from loaded stage data
65
+ *
66
+ * Stages are defined in stages.yaml and their array order IS the
67
+ * canonical lifecycle order. This function extracts IDs preserving
68
+ * that order, similar to getCapabilityOrder for capabilities.
69
+ *
70
+ * @param {Object[]} stages - Loaded stages array from stages.yaml
71
+ * @returns {string[]} Stage IDs in lifecycle order
72
+ */
73
+ export function getStageOrder(stages) {
74
+ return stages.map((s) => s.id);
75
+ }
76
+
77
+ /**
78
+ * Skill capabilities (what capability area)
79
+ * @readonly
80
+ * @enum {string}
81
+ */
82
+ export const Capability = {
83
+ DELIVERY: "delivery",
84
+ SCALE: "scale",
85
+ RELIABILITY: "reliability",
86
+ DATA: "data",
87
+ AI: "ai",
88
+ ML: "ml",
89
+ PROCESS: "process",
90
+ BUSINESS: "business",
91
+ PEOPLE: "people",
92
+ DOCUMENTATION: "documentation",
93
+ PRODUCT: "product",
94
+ };
95
+
96
+ // ============================================================================
97
+ // Data-driven Capability Functions
98
+ // ============================================================================
99
+ // These functions work with loaded capability data for responsibility derivation
100
+
101
+ /**
102
+ * Get capability metadata from loaded capability data
103
+ * @param {Object[]} capabilities - Loaded capabilities array
104
+ * @param {string} capabilityId - The capability ID to look up
105
+ * @returns {Object|undefined} The capability object or undefined
106
+ */
107
+ export function getCapabilityById(capabilities, capabilityId) {
108
+ return capabilities.find((c) => c.id === capabilityId);
109
+ }
110
+
111
+ /**
112
+ * Get ordered capability IDs from loaded capability data
113
+ * @param {Object[]} capabilities - Loaded capabilities array
114
+ * @returns {string[]} Capability IDs in display order
115
+ */
116
+ export function getCapabilityOrder(capabilities) {
117
+ return [...capabilities]
118
+ .sort((a, b) => (a.ordinalRank || 0) - (b.ordinalRank || 0))
119
+ .map((c) => c.id);
120
+ }
121
+
122
+ /**
123
+ * Group skills by capability in display order
124
+ * @param {import('./levels.js').Skill[]} skills - Array of skills to group
125
+ * @param {Object[]} capabilities - Loaded capabilities array for ordering
126
+ * @returns {Object<string, import('./levels.js').Skill[]>} Object with capabilities as keys (in display order)
127
+ */
128
+ export function groupSkillsByCapability(skills, capabilities) {
129
+ const capabilityOrder = getCapabilityOrder(capabilities);
130
+ const result = {};
131
+
132
+ // Initialize all capabilities in display order (ensures consistent key order)
133
+ for (const capability of capabilityOrder) {
134
+ result[capability] = [];
135
+ }
136
+
137
+ // Populate with skills
138
+ for (const skill of skills) {
139
+ if (result[skill.capability]) {
140
+ result[skill.capability].push(skill);
141
+ }
142
+ }
143
+
144
+ // Remove empty capabilities and sort skills within each capability by name
145
+ for (const capability of Object.keys(result)) {
146
+ if (result[capability].length === 0) {
147
+ delete result[capability];
148
+ } else {
149
+ result[capability].sort((a, b) => a.name.localeCompare(b.name));
150
+ }
151
+ }
152
+
153
+ return result;
154
+ }
155
+
156
+ /**
157
+ * Get emoji for a capability from loaded capability data
158
+ * @param {Object[]} capabilities - Loaded capabilities array
159
+ * @param {string} capabilityId - The capability ID
160
+ * @returns {string} The emoji or default "💡"
161
+ */
162
+ export function getCapabilityEmoji(capabilities, capabilityId) {
163
+ const capability = getCapabilityById(capabilities, capabilityId);
164
+ return capability?.emojiIcon || "💡";
165
+ }
166
+
167
+ /**
168
+ * Get responsibility statement for a capability at a specific skill proficiency
169
+ *
170
+ * Uses professionalResponsibilities for professional disciplines and
171
+ * managementResponsibilities for management disciplines.
172
+ *
173
+ * @param {Object[]} capabilities - Loaded capabilities array
174
+ * @param {string} capabilityId - The capability ID
175
+ * @param {string} level - The skill proficiency (awareness, foundational, working, practitioner, expert)
176
+ * @param {Object} [discipline] - Optional discipline to determine which responsibilities to use
177
+ * @param {boolean} [discipline.isManagement] - Whether this is a management discipline
178
+ * @returns {string|undefined} The responsibility statement or undefined
179
+ */
180
+ export function getCapabilityResponsibility(
181
+ capabilities,
182
+ capabilityId,
183
+ level,
184
+ discipline,
185
+ ) {
186
+ const capability = getCapabilityById(capabilities, capabilityId);
187
+ const responsibilityKey = discipline?.isManagement
188
+ ? "managementResponsibilities"
189
+ : "professionalResponsibilities";
190
+ return capability?.[responsibilityKey]?.[level];
191
+ }
192
+
193
+ /**
194
+ * Skill type within a discipline
195
+ * @readonly
196
+ * @enum {string}
197
+ */
198
+ export const SkillType = {
199
+ PRIMARY: "primary",
200
+ SECONDARY: "secondary",
201
+ BROAD: "broad",
202
+ TRACK: "track",
203
+ };
204
+
205
+ /**
206
+ * @typedef {Object} LevelDescription
207
+ * @property {string} level - The level identifier
208
+ * @property {string} description - Description of what this level means
209
+ */
210
+
211
+ /**
212
+ * @typedef {Object} Skill
213
+ * @property {string} id - Unique identifier
214
+ * @property {string} name - Display name
215
+ * @property {string} capability - One of Capability values
216
+ * @property {string} description - General description of the skill
217
+ * @property {Object<string, string>} proficiencyDescriptions - Description for each skill proficiency
218
+ */
219
+
220
+ /**
221
+ * @typedef {Object} Behaviour
222
+ * @property {string} id - Unique identifier
223
+ * @property {string} name - Display name
224
+ * @property {string} description - General description of the behaviour
225
+ * @property {Object<string, string>} maturityDescriptions - Description for each maturity level
226
+ */
227
+
228
+ /**
229
+ * @typedef {Object} Driver
230
+ * @property {string} id - Unique identifier
231
+ * @property {string} name - Display name
232
+ * @property {string} description - Description of the organizational outcome
233
+ * @property {string[]} contributingSkills - Array of skill IDs that support this driver
234
+ * @property {string[]} contributingBehaviours - Array of behaviour IDs that support this driver
235
+ */
236
+
237
+ /**
238
+ * @typedef {Object} Discipline
239
+ * @property {string} id - Unique identifier
240
+ * @property {string} specialization - Display name for the field (e.g., "Software Engineering")
241
+ * @property {string} roleTitle - Display name for a person in this role (e.g., "Software Engineer")
242
+ * @property {string} [name] - Legacy display name (deprecated, use specialization/roleTitle)
243
+ * @property {string} description - Description of the discipline
244
+ * @property {Array<string|null>} validTracks - Valid track configurations. null = allow trackless (generalist), string = track ID
245
+ * @property {string[]} coreSkills - Skill IDs requiring deep expertise (Practitioner/Expert)
246
+ * @property {string[]} supportingSkills - Skill IDs requiring solid competence (Working/Practitioner)
247
+ * @property {string[]} broadSkills - Skill IDs requiring awareness (Awareness/Foundational)
248
+ * @property {Object<string, number>} behaviourModifiers - Map of behaviour ID to modifier (+1, 0, -1)
249
+ */
250
+
251
+ /**
252
+ * @typedef {Object} AssessmentWeights
253
+ * @property {number} skillWeight - Weight for skill matching (0.0-1.0)
254
+ * @property {number} behaviourWeight - Weight for behaviour matching (0.0-1.0)
255
+ */
256
+
257
+ /**
258
+ * @typedef {Object} Track
259
+ * @property {string} id - Unique identifier
260
+ * @property {string} name - Display name
261
+ * @property {string} description - Description of the track focus
262
+ * @property {Object<string, number>} skillModifiers - Map of capability/skill ID to level modifier (positive or negative integer)
263
+ * @property {Object<string, number>} behaviourModifiers - Map of behaviour ID to maturity modifier (positive or negative integer)
264
+ * @property {AssessmentWeights} [assessmentWeights] - Optional custom weights for job matching
265
+ * @property {string} [minLevel] - Optional minimum level ID this track is valid for
266
+ */
267
+
268
+ /**
269
+ * @typedef {Object} LevelSkillProficiencies
270
+ * @property {string} primary - Base skill proficiency for primary skills
271
+ * @property {string} secondary - Base skill proficiency for secondary skills
272
+ * @property {string} broad - Base skill proficiency for broad skills
273
+ */
274
+
275
+ /**
276
+ * @typedef {Object} LevelExpectations
277
+ * @property {string} impactScope - Expected scope of work/impact
278
+ * @property {string} autonomyExpectation - Expected level of autonomy
279
+ * @property {string} influenceScope - Expected sphere of influence
280
+ * @property {string} complexityHandled - Expected complexity of work handled
281
+ */
282
+
283
+ /**
284
+ * @typedef {Object} BreadthCriteria
285
+ * @property {number} [practitioner] - Minimum number of skills at Practitioner level
286
+ * @property {number} [expert] - Minimum number of skills at Expert level
287
+ */
288
+
289
+ /**
290
+ * @typedef {Object} Level
291
+ * @property {string} id - Unique identifier
292
+ * @property {string} professionalTitle - Display name for professional/IC track (e.g., "Level I", "Staff")
293
+ * @property {string} managementTitle - Display name for management track (e.g., "Associate", "Director")
294
+ * @property {string} [name] - Legacy display name (deprecated, use professionalTitle/managementTitle)
295
+ * @property {string} [typicalExperienceRange] - Typical years of experience range (e.g., "0-2", "20+")
296
+ * @property {number} ordinalRank - Numeric level for ordering (higher = more senior)
297
+ * @property {LevelSkillProficiencies} baseSkillProficiencies - Base skill proficiencies by skill type
298
+ * @property {string} baseBehaviourMaturity - Base behaviour maturity level
299
+ * @property {LevelExpectations} expectations - Role expectations
300
+ * @property {BreadthCriteria} [breadthCriteria] - For senior levels, breadth requirements
301
+ */
302
+
303
+ /**
304
+ * @typedef {Object} SkillMatrixEntry
305
+ * @property {string} skillId - The skill ID
306
+ * @property {string} skillName - The skill name
307
+ * @property {string} capability - The skill capability
308
+ * @property {string} type - The skill type (primary/secondary/broad)
309
+ * @property {string} proficiency - The derived skill proficiency
310
+ * @property {string} proficiencyDescription - Description for this proficiency
311
+ */
312
+
313
+ /**
314
+ * @typedef {Object} BehaviourProfileEntry
315
+ * @property {string} behaviourId - The behaviour ID
316
+ * @property {string} behaviourName - The behaviour name
317
+ * @property {string} maturity - The derived maturity level
318
+ * @property {string} maturityDescription - Description for this maturity level
319
+
320
+ */
321
+
322
+ /**
323
+ * @typedef {Object} JobDefinition
324
+ * @property {string} id - Generated job ID (discipline_level_track)
325
+ * @property {string} title - Generated job title
326
+ * @property {Discipline} discipline - Reference to the discipline
327
+ * @property {Level} level - Reference to the level
328
+ * @property {Track} track - Reference to the track
329
+ * @property {SkillMatrixEntry[]} skillMatrix - Complete derived skill matrix
330
+ * @property {BehaviourProfileEntry[]} behaviourProfile - Complete derived behaviour profile
331
+ * @property {LevelExpectations} expectations - Level-level expectations
332
+ */
333
+
334
+ /**
335
+ * @typedef {Object} Question
336
+ * @property {string} id - Unique identifier
337
+ * @property {string} text - The question text
338
+ * @property {string} type - Question type (technical, situational, behavioural)
339
+ * @property {string[]} [followUps] - Optional follow-up questions
340
+ * @property {string[]} [lookingFor] - What good answers should include
341
+ * @property {number} [expectedDurationMinutes] - Estimated time to ask and answer
342
+ */
343
+
344
+ /**
345
+ * @typedef {Object<string, Question[]>} LevelQuestions - Questions organized by level
346
+ */
347
+
348
+ /**
349
+ * @typedef {Object} RoleTypeQuestions
350
+ * @property {LevelQuestions} [professionalQuestions] - Questions for professional/IC roles
351
+ * @property {LevelQuestions} [managementQuestions] - Questions for management roles
352
+ */
353
+
354
+ /**
355
+ * @typedef {Object} QuestionBank
356
+ * @property {Object<string, RoleTypeQuestions>} skillProficiencies - Questions by skill ID, then by role type (professional/management), then by level
357
+ * @property {Object<string, RoleTypeQuestions>} behaviourMaturities - Questions by behaviour ID, then by role type, then by maturity
358
+ * @property {Object<string, RoleTypeQuestions>} [capabilityLevels] - Questions by capability ID, then by role type, then by level
359
+ */
360
+
361
+ /**
362
+ * @typedef {Object} SelfAssessment
363
+ * @property {string} [id] - Optional identifier
364
+ * @property {Object<string, string>} skills - Map of skill ID to self-assessed level
365
+ * @property {Object<string, string>} behaviours - Map of behaviour ID to self-assessed maturity
366
+ * @property {Object} [expectations] - Optional self-assessment of scope/autonomy/influence
367
+ * @property {string} [expectations.scope] - Self-assessed scope
368
+ * @property {string} [expectations.autonomy] - Self-assessed autonomy
369
+ * @property {string} [expectations.influence] - Self-assessed influence
370
+ */
371
+
372
+ /**
373
+ * @typedef {Object} MatchGap
374
+ * @property {string} id - Skill or behaviour ID
375
+ * @property {string} name - Skill or behaviour name
376
+ * @property {string} type - 'skill' or 'behaviour'
377
+ * @property {string} current - Current level
378
+ * @property {string} required - Required level
379
+ * @property {number} gap - Numeric gap (positive means below requirement)
380
+ */
381
+
382
+ /**
383
+ * @typedef {Object} MatchAnalysis
384
+ * @property {number} overallScore - Combined weighted score (0-1)
385
+ * @property {number} skillScore - Skill match score (0-1)
386
+ * @property {number} behaviourScore - Behaviour match score (0-1)
387
+ * @property {MatchingWeights} weightsUsed - The weights used in calculation
388
+ * @property {MatchGap[]} gaps - Array of gaps where requirements not met
389
+ * @property {MatchTierInfo} tier - Match tier classification
390
+ * @property {MatchGap[]} priorityGaps - Top 3 gaps by severity for focused development
391
+ * @property {number} [expectationsScore] - For senior roles, expectations match score
392
+ */
393
+
394
+ /**
395
+ * @typedef {Object} JobMatch
396
+ * @property {JobDefinition} job - The matched job
397
+ * @property {MatchAnalysis} analysis - Match analysis details
398
+ */
399
+
400
+ /**
401
+ * @typedef {Object} DevelopmentItem
402
+ * @property {string} id - Skill or behaviour ID
403
+ * @property {string} name - Skill or behaviour name
404
+ * @property {string} type - 'skill' or 'behaviour'
405
+ * @property {string} currentLevel - Current level
406
+ * @property {string} targetLevel - Target level for the job
407
+ * @property {number} priority - Priority score (higher = more important)
408
+ * @property {string} rationale - Why this development is important
409
+ */
410
+
411
+ /**
412
+ * @typedef {Object} DevelopmentPath
413
+ * @property {JobDefinition} targetJob - The target job
414
+ * @property {DevelopmentItem[]} items - Prioritized development items
415
+ * @property {number} estimatedReadiness - Current readiness score (0-1)
416
+ */
417
+
418
+ /**
419
+ * @typedef {Object} DriverCoverage
420
+ * @property {string} driverId - The driver ID
421
+ * @property {string} driverName - The driver name
422
+ * @property {number} skillCoverage - Percentage of linked skills at Working+ (0-1)
423
+ * @property {number} behaviourCoverage - Percentage of linked behaviours at Practicing+ (0-1)
424
+ * @property {number} overallScore - Weighted average of skill and behaviour coverage
425
+ * @property {string[]} coveredSkills - Skills that meet the threshold
426
+ * @property {string[]} coveredBehaviours - Behaviours that meet the threshold
427
+ * @property {string[]} missingSkills - Skills below threshold
428
+ * @property {string[]} missingBehaviours - Behaviours below threshold
429
+ */
430
+
431
+ /**
432
+ * @typedef {Object} InterviewQuestion
433
+ * @property {Question} question - The question details
434
+ * @property {string} targetId - The skill or behaviour ID this assesses
435
+ * @property {string} targetName - The skill or behaviour name
436
+ * @property {string} targetType - 'skill' or 'behaviour'
437
+ * @property {string} targetLevel - The level this question assesses
438
+ * @property {number} priority - Priority in the interview (higher = ask first)
439
+ */
440
+
441
+ /**
442
+ * @typedef {Object} InterviewGuide
443
+ * @property {JobDefinition} job - The job being interviewed for
444
+ * @property {InterviewQuestion[]} questions - Ordered list of questions
445
+ * @property {number} estimatedMinutes - Total estimated time
446
+ * @property {Object} coverage - Coverage summary
447
+ * @property {string[]} coverage.skills - Skills covered
448
+ * @property {string[]} coverage.behaviours - Behaviours covered
449
+ */
450
+
451
+ /**
452
+ * @typedef {Object} ValidationError
453
+ * @property {string} type - Error type identifier
454
+ * @property {string} message - Human-readable error message
455
+ * @property {string} [path] - Path to the invalid data
456
+ * @property {*} [value] - The invalid value
457
+ */
458
+
459
+ /**
460
+ * @typedef {Object} ValidationWarning
461
+ * @property {string} type - Warning type identifier
462
+ * @property {string} message - Human-readable warning message
463
+ * @property {string} [path] - Path to the concerning data
464
+ */
465
+
466
+ /**
467
+ * @typedef {Object} ValidationResult
468
+ * @property {boolean} valid - Whether validation passed
469
+ * @property {ValidationError[]} errors - Array of validation errors
470
+ * @property {ValidationWarning[]} warnings - Array of validation warnings
471
+ */
472
+
473
+ /**
474
+ * @typedef {Object} JobValidationRules
475
+ * @property {Array<{discipline: string, level?: string, track?: string}>} [invalidCombinations] - Invalid combinations
476
+ * @property {Object<string, string[]>} [validTracksByDiscipline] - Valid tracks per discipline
477
+ */
478
+
479
+ /**
480
+ * Helper function to get skill proficiency index
481
+ * @param {string} level - The skill proficiency
482
+ * @returns {number} The index (0-4), or -1 if invalid
483
+ */
484
+ export function getSkillProficiencyIndex(level) {
485
+ return SKILL_PROFICIENCY_ORDER.indexOf(level);
486
+ }
487
+
488
+ /**
489
+ * Helper function to get behaviour maturity index
490
+ * @param {string} maturity - The maturity level
491
+ * @returns {number} The index (0-3), or -1 if invalid
492
+ */
493
+ export function getBehaviourMaturityIndex(maturity) {
494
+ return BEHAVIOUR_MATURITY_ORDER.indexOf(maturity);
495
+ }
496
+
497
+ /**
498
+ * Clamp a skill proficiency index to valid range
499
+ * @param {number} index - The index to clamp
500
+ * @returns {string} The clamped skill proficiency
501
+ */
502
+ export function clampSkillProficiency(index) {
503
+ const clampedIndex = Math.max(
504
+ 0,
505
+ Math.min(SKILL_PROFICIENCY_ORDER.length - 1, index),
506
+ );
507
+ return SKILL_PROFICIENCY_ORDER[clampedIndex];
508
+ }
509
+
510
+ /**
511
+ * Clamp a behaviour maturity index to valid range
512
+ * @param {number} index - The index to clamp
513
+ * @returns {string} The clamped maturity level
514
+ */
515
+ export function clampBehaviourMaturity(index) {
516
+ const clampedIndex = Math.max(
517
+ 0,
518
+ Math.min(BEHAVIOUR_MATURITY_ORDER.length - 1, index),
519
+ );
520
+ return BEHAVIOUR_MATURITY_ORDER[clampedIndex];
521
+ }
522
+
523
+ /**
524
+ * Check if a skill proficiency meets or exceeds a requirement
525
+ * @param {string} actual - The actual skill proficiency
526
+ * @param {string} required - The required skill proficiency
527
+ * @returns {boolean} True if actual meets or exceeds required
528
+ */
529
+ export function skillProficiencyMeetsRequirement(actual, required) {
530
+ return getSkillProficiencyIndex(actual) >= getSkillProficiencyIndex(required);
531
+ }
532
+
533
+ /**
534
+ * Check if a behaviour maturity meets or exceeds a requirement
535
+ * @param {string} actual - The actual maturity level
536
+ * @param {string} required - The required maturity level
537
+ * @returns {boolean} True if actual meets or exceeds required
538
+ */
539
+ export function behaviourMaturityMeetsRequirement(actual, required) {
540
+ return (
541
+ getBehaviourMaturityIndex(actual) >= getBehaviourMaturityIndex(required)
542
+ );
543
+ }
544
+
545
+ /**
546
+ * Get emoji for a concept from framework data
547
+ * @param {Object} framework - Framework object loaded from framework.yaml
548
+ * @param {string} concept - The concept type: 'driver', 'skill', 'behaviour', 'discipline', 'level', or 'track'
549
+ * @returns {string} The emoji for the concept or default "💡"
550
+ */
551
+ export function getConceptEmoji(framework, concept) {
552
+ return framework?.entityDefinitions?.[concept]?.emojiIcon || "💡";
553
+ }