@forwardimpact/model 0.5.0 → 0.7.1

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.
@@ -11,13 +11,13 @@ import { deriveJob } from "./derivation.js";
11
11
  const cache = new Map();
12
12
 
13
13
  /**
14
- * Create a consistent cache key from job parameters
14
+ * Build a consistent cache key from job parameters
15
15
  * @param {string} disciplineId
16
16
  * @param {string} gradeId
17
17
  * @param {string} [trackId] - Optional track ID
18
18
  * @returns {string}
19
19
  */
20
- export function makeJobKey(disciplineId, gradeId, trackId = null) {
20
+ export function buildJobKey(disciplineId, gradeId, trackId = null) {
21
21
  if (trackId) {
22
22
  return `${disciplineId}_${gradeId}_${trackId}`;
23
23
  }
@@ -43,7 +43,7 @@ export function getOrCreateJob({
43
43
  behaviours,
44
44
  capabilities,
45
45
  }) {
46
- const key = makeJobKey(discipline.id, grade.id, track?.id);
46
+ const key = buildJobKey(discipline.id, grade.id, track?.id);
47
47
 
48
48
  if (!cache.has(key)) {
49
49
  const job = deriveJob({
@@ -66,7 +66,7 @@ export function getOrCreateJob({
66
66
  /**
67
67
  * Clear all cached jobs
68
68
  */
69
- export function clearJobCache() {
69
+ export function clearCache() {
70
70
  cache.clear();
71
71
  }
72
72
 
@@ -76,14 +76,14 @@ export function clearJobCache() {
76
76
  * @param {string} gradeId
77
77
  * @param {string} [trackId] - Optional track ID
78
78
  */
79
- export function invalidateJob(disciplineId, gradeId, trackId = null) {
80
- cache.delete(makeJobKey(disciplineId, gradeId, trackId));
79
+ export function invalidateCachedJob(disciplineId, gradeId, trackId = null) {
80
+ cache.delete(buildJobKey(disciplineId, gradeId, trackId));
81
81
  }
82
82
 
83
83
  /**
84
84
  * Get the number of cached jobs (for testing/debugging)
85
85
  * @returns {number}
86
86
  */
87
- export function getCacheSize() {
87
+ export function getCachedJobCount() {
88
88
  return cache.size;
89
89
  }
@@ -16,6 +16,25 @@ import {
16
16
  isSeniorGrade,
17
17
  } from "./derivation.js";
18
18
 
19
+ import {
20
+ THRESHOLD_MATCH_STRONG,
21
+ THRESHOLD_MATCH_GOOD,
22
+ THRESHOLD_MATCH_STRETCH,
23
+ SCORE_GAP,
24
+ WEIGHT_DEV_TYPE_PRIMARY,
25
+ WEIGHT_DEV_TYPE_SECONDARY,
26
+ WEIGHT_DEV_TYPE_BROAD,
27
+ WEIGHT_DEV_AI_BOOST,
28
+ WEIGHT_ASSESSMENT_SKILL_DEFAULT,
29
+ WEIGHT_ASSESSMENT_BEHAVIOUR_DEFAULT,
30
+ WEIGHT_SENIOR_BASE,
31
+ WEIGHT_SENIOR_EXPECTATIONS,
32
+ LIMIT_PRIORITY_GAPS,
33
+ WEIGHT_SAME_TRACK_BONUS,
34
+ RANGE_GRADE_OFFSET,
35
+ RANGE_READY_GRADE_OFFSET,
36
+ } from "./policies/thresholds.js";
37
+
19
38
  // ============================================================================
20
39
  // Match Tier Types and Constants
21
40
  // ============================================================================
@@ -34,25 +53,26 @@ export const MatchTier = {
34
53
 
35
54
  /**
36
55
  * Match tier configuration with thresholds and display properties
56
+ * Uses threshold constants from policies/thresholds.js
37
57
  * @type {Object<number, {label: string, color: string, minScore: number, description: string}>}
38
58
  */
39
- export const MATCH_TIER_CONFIG = {
59
+ export const CONFIG_MATCH_TIER = {
40
60
  [MatchTier.STRONG]: {
41
61
  label: "Strong Match",
42
62
  color: "green",
43
- minScore: 0.85,
63
+ minScore: THRESHOLD_MATCH_STRONG,
44
64
  description: "Ready for this role now",
45
65
  },
46
66
  [MatchTier.GOOD]: {
47
67
  label: "Good Match",
48
68
  color: "blue",
49
- minScore: 0.7,
69
+ minScore: THRESHOLD_MATCH_GOOD,
50
70
  description: "Ready within 6-12 months of focused growth",
51
71
  },
52
72
  [MatchTier.STRETCH]: {
53
73
  label: "Stretch Role",
54
74
  color: "amber",
55
- minScore: 0.55,
75
+ minScore: THRESHOLD_MATCH_STRETCH,
56
76
  description: "Ambitious but achievable with dedicated development",
57
77
  },
58
78
  [MatchTier.ASPIRATIONAL]: {
@@ -76,19 +96,19 @@ export const MATCH_TIER_CONFIG = {
76
96
  * @param {number} score - Match score from 0 to 1
77
97
  * @returns {MatchTierInfo} Tier classification
78
98
  */
79
- export function classifyMatchTier(score) {
80
- if (score >= MATCH_TIER_CONFIG[MatchTier.STRONG].minScore) {
81
- return { tier: MatchTier.STRONG, ...MATCH_TIER_CONFIG[MatchTier.STRONG] };
99
+ export function classifyMatch(score) {
100
+ if (score >= CONFIG_MATCH_TIER[MatchTier.STRONG].minScore) {
101
+ return { tier: MatchTier.STRONG, ...CONFIG_MATCH_TIER[MatchTier.STRONG] };
82
102
  }
83
- if (score >= MATCH_TIER_CONFIG[MatchTier.GOOD].minScore) {
84
- return { tier: MatchTier.GOOD, ...MATCH_TIER_CONFIG[MatchTier.GOOD] };
103
+ if (score >= CONFIG_MATCH_TIER[MatchTier.GOOD].minScore) {
104
+ return { tier: MatchTier.GOOD, ...CONFIG_MATCH_TIER[MatchTier.GOOD] };
85
105
  }
86
- if (score >= MATCH_TIER_CONFIG[MatchTier.STRETCH].minScore) {
87
- return { tier: MatchTier.STRETCH, ...MATCH_TIER_CONFIG[MatchTier.STRETCH] };
106
+ if (score >= CONFIG_MATCH_TIER[MatchTier.STRETCH].minScore) {
107
+ return { tier: MatchTier.STRETCH, ...CONFIG_MATCH_TIER[MatchTier.STRETCH] };
88
108
  }
89
109
  return {
90
110
  tier: MatchTier.ASPIRATIONAL,
91
- ...MATCH_TIER_CONFIG[MatchTier.ASPIRATIONAL],
111
+ ...CONFIG_MATCH_TIER[MatchTier.ASPIRATIONAL],
92
112
  };
93
113
  }
94
114
 
@@ -98,16 +118,10 @@ export function classifyMatchTier(score) {
98
118
 
99
119
  /**
100
120
  * Score values for different gap sizes
101
- * Uses a smooth decay that reflects real-world readiness
121
+ * Re-exported from policies/thresholds.js for backward compatibility
102
122
  * @type {Object<number, number>}
103
123
  */
104
- export const GAP_SCORES = {
105
- 0: 1.0, // Meets or exceeds
106
- 1: 0.7, // Minor development needed
107
- 2: 0.4, // Significant but achievable gap
108
- 3: 0.15, // Major development required
109
- 4: 0.05, // Aspirational only
110
- };
124
+ export const GAP_SCORES = SCORE_GAP;
111
125
 
112
126
  /**
113
127
  * Calculate gap score with smooth decay
@@ -115,11 +129,11 @@ export const GAP_SCORES = {
115
129
  * @returns {number} Score from 0 to 1
116
130
  */
117
131
  export function calculateGapScore(gap) {
118
- if (gap <= 0) return GAP_SCORES[0]; // Meets or exceeds
119
- if (gap === 1) return GAP_SCORES[1];
120
- if (gap === 2) return GAP_SCORES[2];
121
- if (gap === 3) return GAP_SCORES[3];
122
- return GAP_SCORES[4]; // 4+ levels below
132
+ if (gap <= 0) return SCORE_GAP[0]; // Meets or exceeds
133
+ if (gap === 1) return SCORE_GAP[1];
134
+ if (gap === 2) return SCORE_GAP[2];
135
+ if (gap === 3) return SCORE_GAP[3];
136
+ return SCORE_GAP[4]; // 4+ levels below
123
137
  }
124
138
 
125
139
  /**
@@ -279,8 +293,12 @@ function calculateExpectationsScore(selfExpectations, jobExpectations) {
279
293
  */
280
294
  export function calculateJobMatch(selfAssessment, job) {
281
295
  // Get weights from track or use defaults (track may be null for trackless jobs)
282
- const skillWeight = job.track?.assessmentWeights?.skillWeight ?? 0.5;
283
- const behaviourWeight = job.track?.assessmentWeights?.behaviourWeight ?? 0.5;
296
+ const skillWeight =
297
+ job.track?.assessmentWeights?.skillWeight ??
298
+ WEIGHT_ASSESSMENT_SKILL_DEFAULT;
299
+ const behaviourWeight =
300
+ job.track?.assessmentWeights?.behaviourWeight ??
301
+ WEIGHT_ASSESSMENT_BEHAVIOUR_DEFAULT;
284
302
 
285
303
  // Calculate skill score
286
304
  const skillResult = calculateSkillScore(
@@ -306,7 +324,9 @@ export function calculateJobMatch(selfAssessment, job) {
306
324
  job.expectations,
307
325
  );
308
326
  // Add up to 10% bonus for expectations match
309
- overallScore = overallScore * 0.9 + expectationsScore * 0.1;
327
+ overallScore =
328
+ overallScore * WEIGHT_SENIOR_BASE +
329
+ expectationsScore * WEIGHT_SENIOR_EXPECTATIONS;
310
330
  }
311
331
 
312
332
  // Combine all gaps
@@ -316,10 +336,10 @@ export function calculateJobMatch(selfAssessment, job) {
316
336
  allGaps.sort((a, b) => b.gap - a.gap);
317
337
 
318
338
  // Classify match into tier
319
- const tier = classifyMatchTier(overallScore);
339
+ const tier = classifyMatch(overallScore);
320
340
 
321
- // Identify top priority gaps (top 3 by gap size)
322
- const priorityGaps = allGaps.slice(0, 3);
341
+ // Identify top priority gaps
342
+ const priorityGaps = allGaps.slice(0, LIMIT_PRIORITY_GAPS);
323
343
 
324
344
  const result = {
325
345
  overallScore,
@@ -538,11 +558,11 @@ export function findRealisticMatches({
538
558
  skills,
539
559
  });
540
560
 
541
- // Determine grade range (±1 level)
561
+ // Determine grade range (±RANGE_GRADE_OFFSET levels)
542
562
  const bestFitLevel = estimatedGrade.grade.ordinalRank;
543
563
  const gradeRange = {
544
- min: bestFitLevel - 1,
545
- max: bestFitLevel + 1,
564
+ min: bestFitLevel - RANGE_GRADE_OFFSET,
565
+ max: bestFitLevel + RANGE_GRADE_OFFSET,
546
566
  };
547
567
 
548
568
  // Find all matches
@@ -602,11 +622,11 @@ export function findRealisticMatches({
602
622
  }
603
623
 
604
624
  // Filter each tier to only show grades within reasonable range of highest match
605
- // For Strong/Good matches: show up to 2 levels below highest match
625
+ // For Strong/Good matches: show up to RANGE_READY_GRADE_OFFSET levels below highest match
606
626
  // For Stretch/Aspirational: show only at or above highest match (growth opportunities)
607
627
  if (highestMatchedLevel > 0) {
608
- const minLevelForReady = highestMatchedLevel - 2; // Show some consolidation options
609
- const minLevelForStretch = highestMatchedLevel; // Stretch roles should be at or above current
628
+ const minLevelForReady = highestMatchedLevel - RANGE_READY_GRADE_OFFSET;
629
+ const minLevelForStretch = highestMatchedLevel;
610
630
 
611
631
  matchesByTier[1] = matchesByTier[1].filter(
612
632
  (m) => m.job.grade.ordinalRank >= minLevelForReady,
@@ -667,8 +687,12 @@ export function deriveDevelopmentPath({ selfAssessment, targetJob }) {
667
687
  // - AI skills get a boost for "AI-era focus"
668
688
  const gapSize = targetIndex - selfIndex;
669
689
  const typeMultiplier =
670
- jobSkill.type === "primary" ? 3 : jobSkill.type === "secondary" ? 2 : 1;
671
- const aiBoost = jobSkill.capability === "ai" ? 1.5 : 1;
690
+ jobSkill.type === "primary"
691
+ ? WEIGHT_DEV_TYPE_PRIMARY
692
+ : jobSkill.type === "secondary"
693
+ ? WEIGHT_DEV_TYPE_SECONDARY
694
+ : WEIGHT_DEV_TYPE_BROAD;
695
+ const aiBoost = jobSkill.capability === "ai" ? WEIGHT_DEV_AI_BOOST : 1;
672
696
  const priority = gapSize * typeMultiplier * aiBoost;
673
697
 
674
698
  items.push({
@@ -789,8 +813,8 @@ export function findNextStepJob({
789
813
 
790
814
  if (job) {
791
815
  const analysis = calculateJobMatch(selfAssessment, job);
792
- // Boost score for same track
793
- const trackBonus = track.id === currentJob.track.id ? 0.1 : 0;
816
+ const trackBonus =
817
+ track.id === currentJob.track.id ? WEIGHT_SAME_TRACK_BONUS : 0;
794
818
  candidates.push({
795
819
  job,
796
820
  analysis,
@@ -6,13 +6,13 @@
6
6
  * (e.g., "delivery: 1", "scale: -1") - individual skill modifiers are not allowed.
7
7
  */
8
8
 
9
- import { CAPABILITY_ORDER } from "@forwardimpact/schema/levels";
9
+ import { Capability } from "@forwardimpact/schema/levels";
10
10
 
11
11
  /**
12
12
  * Valid skill capability names for modifier expansion
13
13
  * @type {Set<string>}
14
14
  */
15
- const VALID_CAPABILITIES = new Set(CAPABILITY_ORDER);
15
+ const VALID_CAPABILITIES = new Set(Object.values(Capability));
16
16
 
17
17
  /**
18
18
  * Check if a key is a skill capability
@@ -66,7 +66,7 @@ export function buildCapabilityToSkillsMap(skills) {
66
66
  * @param {import('./levels.js').Skill[]} skills - Array of all skills (for capability lookup)
67
67
  * @returns {Object<string, number>} Expanded skill modifiers with individual skill IDs
68
68
  */
69
- export function expandSkillModifiers(skillModifiers, skills) {
69
+ export function expandModifiersToSkills(skillModifiers, skills) {
70
70
  if (!skillModifiers) {
71
71
  return {};
72
72
  }
@@ -113,7 +113,7 @@ export function extractCapabilityModifiers(skillModifiers) {
113
113
  * @param {Object<string, number>} skillModifiers - The skill modifiers
114
114
  * @returns {Object<string, number>} Only the individual skill modifiers
115
115
  */
116
- export function extractIndividualModifiers(skillModifiers) {
116
+ export function extractSkillModifiers(skillModifiers) {
117
117
  if (!skillModifiers) {
118
118
  return {};
119
119
  }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Composed Policy Definitions
3
+ *
4
+ * Named policy compositions for specific use cases.
5
+ * Each POLICY_* export defines a complete filtering/sorting strategy.
6
+ *
7
+ * These are the high-level policies used by consuming code.
8
+ */
9
+
10
+ import { isAgentEligible } from "./predicates.js";
11
+ import { filterHighestLevel, composeFilters } from "./filters.js";
12
+ import {
13
+ compareByLevelDesc,
14
+ compareByMaturityDesc,
15
+ compareByTypeAndName,
16
+ compareBySkillPriority,
17
+ } from "./orderings.js";
18
+ import { LIMIT_AGENT_PROFILE_SKILLS } from "./thresholds.js";
19
+
20
+ // =============================================================================
21
+ // Agent Skill Policies
22
+ // =============================================================================
23
+
24
+ /**
25
+ * Filter for agent-eligible skills at highest derived level
26
+ *
27
+ * Agents receive skills after:
28
+ * 1. Excluding human-only skills (isAgentEligible)
29
+ * 2. Keeping only skills at the highest derived level
30
+ *
31
+ * This ensures agents focus on their peak competencies and
32
+ * respects track modifiers (a broad skill boosted to the same
33
+ * level as primary skills will be included).
34
+ */
35
+ export const filterAgentSkills = composeFilters(
36
+ isAgentEligible,
37
+ filterHighestLevel,
38
+ );
39
+
40
+ // =============================================================================
41
+ // Toolkit Extraction Policy
42
+ // =============================================================================
43
+
44
+ /**
45
+ * Filter for toolkit extraction
46
+ *
47
+ * Tools are extracted only from highest-level skills,
48
+ * keeping the toolkit focused on core competencies.
49
+ */
50
+ export const filterToolkitSkills = composeFilters(filterHighestLevel);
51
+
52
+ // =============================================================================
53
+ // Sorting Policies
54
+ // =============================================================================
55
+
56
+ /**
57
+ * Sort skills for agent profiles (level descending)
58
+ * @param {Array} skills - Skill matrix entries
59
+ * @returns {Array} Sorted skills (new array)
60
+ */
61
+ export function sortAgentSkills(skills) {
62
+ return [...skills].sort(compareByLevelDesc);
63
+ }
64
+
65
+ /**
66
+ * Sort behaviours for agent profiles (maturity descending)
67
+ * @param {Array} behaviours - Behaviour profile entries
68
+ * @returns {Array} Sorted behaviours (new array)
69
+ */
70
+ export function sortAgentBehaviours(behaviours) {
71
+ return [...behaviours].sort(compareByMaturityDesc);
72
+ }
73
+
74
+ /**
75
+ * Sort skills for job display (type ascending, then name)
76
+ * @param {Array} skills - Skill matrix entries
77
+ * @returns {Array} Sorted skills (new array)
78
+ */
79
+ export function sortJobSkills(skills) {
80
+ return [...skills].sort(compareByTypeAndName);
81
+ }
82
+
83
+ // =============================================================================
84
+ // Agent Profile Focus Policy
85
+ // =============================================================================
86
+
87
+ /**
88
+ * Select the focused subset of agent skills for profile body
89
+ *
90
+ * Agent profiles include a limited skill index to avoid context bloat.
91
+ * Skills are ranked by priority (level desc, type asc, name asc) and
92
+ * the top N are returned, where N = LIMIT_AGENT_PROFILE_SKILLS.
93
+ *
94
+ * All skills are still exported as SKILL.md files and listed via --skills.
95
+ *
96
+ * @param {Array} skills - Agent-eligible skills (already filtered and sorted)
97
+ * @returns {Array} Top N skills by priority
98
+ */
99
+ export function focusAgentSkills(skills) {
100
+ return [...skills]
101
+ .sort(compareBySkillPriority)
102
+ .slice(0, LIMIT_AGENT_PROFILE_SKILLS);
103
+ }
104
+
105
+ // =============================================================================
106
+ // Combined Filter + Sort Policies
107
+ // =============================================================================
108
+
109
+ /**
110
+ * Prepare skills for agent profile generation
111
+ *
112
+ * Complete pipeline:
113
+ * 1. Filter to agent-eligible skills
114
+ * 2. Keep only highest-level skills
115
+ * 3. Sort by level descending
116
+ *
117
+ * @param {Array} skillMatrix - Full skill matrix
118
+ * @returns {Array} Filtered and sorted skills
119
+ */
120
+ export function prepareAgentSkillMatrix(skillMatrix) {
121
+ const filtered = filterAgentSkills(skillMatrix);
122
+ return sortAgentSkills(filtered);
123
+ }
124
+
125
+ /**
126
+ * Prepare behaviours for agent profile generation
127
+ *
128
+ * Sorts by maturity descending (highest first).
129
+ *
130
+ * @param {Array} behaviourProfile - Full behaviour profile
131
+ * @returns {Array} Sorted behaviours
132
+ */
133
+ export function prepareAgentBehaviourProfile(behaviourProfile) {
134
+ return sortAgentBehaviours(behaviourProfile);
135
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Matrix-Level Filter Functions
3
+ *
4
+ * Filters that operate on entire arrays of skill/behaviour entries.
5
+ * Unlike predicates (single entry → boolean), these transform arrays.
6
+ *
7
+ * Naming convention: filter* for functions that reduce/transform arrays.
8
+ */
9
+
10
+ import { getSkillLevelIndex } from "@forwardimpact/schema/levels";
11
+
12
+ // =============================================================================
13
+ // Level-Based Filters
14
+ // =============================================================================
15
+
16
+ /**
17
+ * Filter matrix to keep only skills at the highest derived level
18
+ *
19
+ * After track modifiers are applied, some skills will be at higher levels
20
+ * than others. This filter keeps only the skills at the maximum level.
21
+ *
22
+ * @param {Array} matrix - Skill matrix entries with derived levels
23
+ * @returns {Array} Filtered matrix with only max-level skills
24
+ */
25
+ export function filterHighestLevel(matrix) {
26
+ if (matrix.length === 0) return [];
27
+
28
+ const maxIndex = Math.max(
29
+ ...matrix.map((entry) => getSkillLevelIndex(entry.level)),
30
+ );
31
+
32
+ return matrix.filter((entry) => getSkillLevelIndex(entry.level) === maxIndex);
33
+ }
34
+
35
+ /**
36
+ * Filter matrix to exclude skills at awareness level
37
+ *
38
+ * Skills at awareness level are typically too basic for certain outputs
39
+ * like responsibilities derivation.
40
+ *
41
+ * @param {Array} matrix - Skill matrix entries
42
+ * @returns {Array} Filtered matrix excluding awareness skills
43
+ */
44
+ export function filterAboveAwareness(matrix) {
45
+ return matrix.filter((entry) => entry.level !== "awareness");
46
+ }
47
+
48
+ // =============================================================================
49
+ // Predicate Application
50
+ // =============================================================================
51
+
52
+ /**
53
+ * Apply a predicate to filter a matrix
54
+ *
55
+ * Convenience wrapper around Array.filter() for consistency with
56
+ * the policy API.
57
+ *
58
+ * @param {Function} predicate - Predicate function (entry → boolean)
59
+ * @returns {(matrix: Array) => Array} Curried filter function
60
+ */
61
+ export function filterBy(predicate) {
62
+ return (matrix) => matrix.filter(predicate);
63
+ }
64
+
65
+ // =============================================================================
66
+ // Pipeline Application
67
+ // =============================================================================
68
+
69
+ /**
70
+ * Apply multiple filter operations in sequence
71
+ *
72
+ * Each operation can be either:
73
+ * - A predicate function (entry → boolean): used with Array.filter()
74
+ * - A matrix filter (array → array): applied directly
75
+ *
76
+ * Detection: if the function returns an array when given [{}], it's a matrix filter.
77
+ *
78
+ * @param {Array} matrix - Initial items
79
+ * @param {...Function} operations - Predicates or matrix filters
80
+ * @returns {Array} Transformed items
81
+ */
82
+ export function applyFilters(matrix, ...operations) {
83
+ return operations.reduce((acc, op) => {
84
+ // Detect matrix filter by checking return type
85
+ // Matrix filters always return arrays; predicates return booleans
86
+ const testResult = op([{}]);
87
+ if (Array.isArray(testResult)) {
88
+ return op(acc);
89
+ }
90
+ return acc.filter(op);
91
+ }, matrix);
92
+ }
93
+
94
+ /**
95
+ * Compose filter operations into a single filter function
96
+ *
97
+ * Useful for creating reusable composed policies.
98
+ *
99
+ * @param {...Function} operations - Predicates or matrix filters
100
+ * @returns {(matrix: Array) => Array} Composed filter function
101
+ */
102
+ export function composeFilters(...operations) {
103
+ return (matrix) => applyFilters(matrix, ...operations);
104
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Policy Module Index
3
+ *
4
+ * Re-exports all policy constants, predicates, filters, and orderings.
5
+ *
6
+ * Usage:
7
+ * import { THRESHOLD_MATCH_STRONG, isAgentEligible, filterHighestLevel }
8
+ * from "@forwardimpact/model/policies";
9
+ */
10
+
11
+ // Thresholds, scores, and weights
12
+ export {
13
+ // Match tier thresholds
14
+ THRESHOLD_MATCH_STRONG,
15
+ THRESHOLD_MATCH_GOOD,
16
+ THRESHOLD_MATCH_STRETCH,
17
+ THRESHOLD_MATCH_ASPIRATIONAL,
18
+ // Gap scores
19
+ SCORE_GAP_MEETS,
20
+ SCORE_GAP_MINOR,
21
+ SCORE_GAP_SIGNIFICANT,
22
+ SCORE_GAP_MAJOR,
23
+ SCORE_GAP_ASPIRATIONAL,
24
+ SCORE_GAP,
25
+ // Skill type weights
26
+ WEIGHT_SKILL_TYPE_PRIMARY,
27
+ WEIGHT_SKILL_TYPE_SECONDARY,
28
+ WEIGHT_SKILL_TYPE_BROAD,
29
+ WEIGHT_SKILL_TYPE_TRACK,
30
+ WEIGHT_SKILL_TYPE,
31
+ // Capability boosts
32
+ WEIGHT_CAPABILITY_AI,
33
+ WEIGHT_CAPABILITY_DELIVERY,
34
+ WEIGHT_CAPABILITY_BOOST,
35
+ // Behaviour weights
36
+ WEIGHT_BEHAVIOUR_BASE,
37
+ WEIGHT_BEHAVIOUR_MATURITY,
38
+ // Interview ratios
39
+ RATIO_SKILL_BEHAVIOUR,
40
+ // Level multipliers
41
+ WEIGHT_SKILL_LEVEL,
42
+ WEIGHT_BELOW_LEVEL_PENALTY,
43
+ // Development path weights
44
+ WEIGHT_DEV_TYPE_PRIMARY,
45
+ WEIGHT_DEV_TYPE_SECONDARY,
46
+ WEIGHT_DEV_TYPE_BROAD,
47
+ WEIGHT_DEV_AI_BOOST,
48
+ // Agent profile limits
49
+ LIMIT_AGENT_PROFILE_SKILLS,
50
+ LIMIT_AGENT_WORKING_STYLES,
51
+ // Interview time defaults
52
+ DEFAULT_INTERVIEW_QUESTION_MINUTES,
53
+ DEFAULT_DECOMPOSITION_QUESTION_MINUTES,
54
+ DEFAULT_SIMULATION_QUESTION_MINUTES,
55
+ TOLERANCE_INTERVIEW_BUDGET_MINUTES,
56
+ // Decomposition capability weights
57
+ WEIGHT_CAPABILITY_DECOMP_DELIVERY,
58
+ WEIGHT_CAPABILITY_DECOMP_SCALE,
59
+ WEIGHT_CAPABILITY_DECOMP_RELIABILITY,
60
+ WEIGHT_FOCUS_BOOST,
61
+ // Senior grade threshold
62
+ THRESHOLD_SENIOR_GRADE,
63
+ // Assessment weights
64
+ WEIGHT_ASSESSMENT_SKILL_DEFAULT,
65
+ WEIGHT_ASSESSMENT_BEHAVIOUR_DEFAULT,
66
+ WEIGHT_SENIOR_BASE,
67
+ WEIGHT_SENIOR_EXPECTATIONS,
68
+ // Match result limits
69
+ LIMIT_PRIORITY_GAPS,
70
+ WEIGHT_SAME_TRACK_BONUS,
71
+ // Realistic match filtering
72
+ RANGE_GRADE_OFFSET,
73
+ RANGE_READY_GRADE_OFFSET,
74
+ // Driver coverage thresholds
75
+ THRESHOLD_DRIVER_SKILL_LEVEL,
76
+ THRESHOLD_DRIVER_BEHAVIOUR_MATURITY,
77
+ } from "./thresholds.js";
78
+
79
+ // Predicates
80
+ export {
81
+ // Identity
82
+ isAny,
83
+ isNone,
84
+ // Human-only
85
+ isHumanOnly,
86
+ isAgentEligible,
87
+ // Skill types
88
+ isPrimary,
89
+ isSecondary,
90
+ isBroad,
91
+ isTrack,
92
+ isCore,
93
+ isSupporting,
94
+ // Skill levels
95
+ hasMinLevel,
96
+ hasLevel,
97
+ hasBelowLevel,
98
+ // Capabilities
99
+ isInCapability,
100
+ isInAnyCapability,
101
+ // Combinators
102
+ allOf,
103
+ anyOf,
104
+ not,
105
+ } from "./predicates.js";
106
+
107
+ // Filters
108
+ export {
109
+ filterHighestLevel,
110
+ filterAboveAwareness,
111
+ filterBy,
112
+ applyFilters,
113
+ composeFilters,
114
+ } from "./filters.js";
115
+
116
+ // Orderings
117
+ export {
118
+ // Canonical orders
119
+ ORDER_SKILL_TYPE,
120
+ ORDER_STAGE,
121
+ ORDER_AGENT_STAGE,
122
+ CHECKLIST_STAGE_MAP,
123
+ // Skill comparators
124
+ compareByLevelDesc,
125
+ compareByLevelAsc,
126
+ compareByType,
127
+ compareByName,
128
+ compareBySkillPriority,
129
+ compareByTypeAndName,
130
+ // Capability comparators
131
+ compareByCapability,
132
+ sortSkillsByCapability,
133
+ // Behaviour comparators
134
+ compareByMaturityDesc,
135
+ compareByMaturityAsc,
136
+ compareByBehaviourName,
137
+ compareByBehaviourPriority,
138
+ // Generic comparators
139
+ compareByOrder,
140
+ chainComparators,
141
+ // Change comparators
142
+ compareBySkillChange,
143
+ compareByBehaviourChange,
144
+ } from "./orderings.js";
145
+
146
+ // Composed policies
147
+ export {
148
+ // Agent skill filtering
149
+ filterAgentSkills,
150
+ filterToolkitSkills,
151
+ // Agent profile focus
152
+ focusAgentSkills,
153
+ // Sorting
154
+ sortAgentSkills,
155
+ sortAgentBehaviours,
156
+ sortJobSkills,
157
+ // Combined filter + sort
158
+ prepareAgentSkillMatrix,
159
+ prepareAgentBehaviourProfile,
160
+ } from "./composed.js";