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