@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.
- package/README.md +2 -2
- package/package.json +17 -15
- package/{lib → src}/agent.js +25 -19
- package/{lib → src}/checklist.js +2 -10
- package/{lib → src}/derivation.js +17 -15
- package/{lib → src}/index.js +88 -17
- package/src/interview.js +1001 -0
- package/{lib → src}/job-cache.js +7 -7
- package/{lib → src}/matching.js +65 -41
- package/{lib → src}/modifiers.js +4 -4
- package/src/policies/composed.js +135 -0
- package/src/policies/filters.js +104 -0
- package/src/policies/index.js +160 -0
- package/src/policies/orderings.js +312 -0
- package/src/policies/predicates.js +177 -0
- package/src/policies/thresholds.js +317 -0
- package/src/profile.js +145 -0
- package/{lib → src}/progression.js +8 -13
- package/{lib → src}/toolkit.js +3 -3
- package/lib/interview.js +0 -539
- package/lib/profile.js +0 -262
- /package/{lib → src}/job.js +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Thresholds, Scores, and Weights
|
|
3
|
+
*
|
|
4
|
+
* Named constants for filter thresholds, scoring, and priority weights.
|
|
5
|
+
* Grep for THRESHOLD_, SCORE_, WEIGHT_ to find all policy values.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Match Tier Thresholds
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Match tier score thresholds
|
|
14
|
+
*
|
|
15
|
+
* These thresholds determine how candidate match scores are classified.
|
|
16
|
+
* Adjust these to change how lenient/strict match tiers are.
|
|
17
|
+
*
|
|
18
|
+
* @see matching.js:classifyMatch
|
|
19
|
+
*/
|
|
20
|
+
export const THRESHOLD_MATCH_STRONG = 0.85;
|
|
21
|
+
export const THRESHOLD_MATCH_GOOD = 0.7;
|
|
22
|
+
export const THRESHOLD_MATCH_STRETCH = 0.55;
|
|
23
|
+
export const THRESHOLD_MATCH_ASPIRATIONAL = 0;
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Gap Score Decay
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gap score decay values
|
|
31
|
+
*
|
|
32
|
+
* When a candidate has a gap between their level and the job requirement,
|
|
33
|
+
* these scores penalize larger gaps more heavily.
|
|
34
|
+
*
|
|
35
|
+
* @see matching.js:calculateGapScore
|
|
36
|
+
*/
|
|
37
|
+
export const SCORE_GAP_MEETS = 1.0;
|
|
38
|
+
export const SCORE_GAP_MINOR = 0.7;
|
|
39
|
+
export const SCORE_GAP_SIGNIFICANT = 0.4;
|
|
40
|
+
export const SCORE_GAP_MAJOR = 0.15;
|
|
41
|
+
export const SCORE_GAP_ASPIRATIONAL = 0.05;
|
|
42
|
+
|
|
43
|
+
/** Gap scores indexed by gap size (0-4+) */
|
|
44
|
+
export const SCORE_GAP = {
|
|
45
|
+
0: SCORE_GAP_MEETS,
|
|
46
|
+
1: SCORE_GAP_MINOR,
|
|
47
|
+
2: SCORE_GAP_SIGNIFICANT,
|
|
48
|
+
3: SCORE_GAP_MAJOR,
|
|
49
|
+
4: SCORE_GAP_ASPIRATIONAL,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Skill Priority Weights
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Skill priority weights by type
|
|
58
|
+
*
|
|
59
|
+
* Primary skills are core competencies and get highest priority.
|
|
60
|
+
* Used for interview question selection and development prioritization.
|
|
61
|
+
*
|
|
62
|
+
* @see interview.js:calculateSkillPriority
|
|
63
|
+
*/
|
|
64
|
+
export const WEIGHT_SKILL_TYPE_PRIMARY = 30;
|
|
65
|
+
export const WEIGHT_SKILL_TYPE_SECONDARY = 20;
|
|
66
|
+
export const WEIGHT_SKILL_TYPE_BROAD = 10;
|
|
67
|
+
export const WEIGHT_SKILL_TYPE_TRACK = 5;
|
|
68
|
+
|
|
69
|
+
/** Skill type weights as object for lookup */
|
|
70
|
+
export const WEIGHT_SKILL_TYPE = {
|
|
71
|
+
primary: WEIGHT_SKILL_TYPE_PRIMARY,
|
|
72
|
+
secondary: WEIGHT_SKILL_TYPE_SECONDARY,
|
|
73
|
+
broad: WEIGHT_SKILL_TYPE_BROAD,
|
|
74
|
+
track: WEIGHT_SKILL_TYPE_TRACK,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Capability Priority Boosts
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Capability priority boosts
|
|
83
|
+
*
|
|
84
|
+
* Certain capabilities get additional priority in the AI-era engineering model.
|
|
85
|
+
*
|
|
86
|
+
* @see interview.js:calculateSkillPriority
|
|
87
|
+
*/
|
|
88
|
+
export const WEIGHT_CAPABILITY_AI = 15;
|
|
89
|
+
export const WEIGHT_CAPABILITY_DELIVERY = 5;
|
|
90
|
+
|
|
91
|
+
/** Capability boosts as object for lookup */
|
|
92
|
+
export const WEIGHT_CAPABILITY_BOOST = {
|
|
93
|
+
ai: WEIGHT_CAPABILITY_AI,
|
|
94
|
+
delivery: WEIGHT_CAPABILITY_DELIVERY,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Behaviour Priority Weights
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Base behaviour priority weight
|
|
103
|
+
*
|
|
104
|
+
* @see interview.js:calculateBehaviourPriority
|
|
105
|
+
*/
|
|
106
|
+
export const WEIGHT_BEHAVIOUR_BASE = 15;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Behaviour maturity multiplier
|
|
110
|
+
*
|
|
111
|
+
* Each maturity level adds this amount to the priority.
|
|
112
|
+
*/
|
|
113
|
+
export const WEIGHT_BEHAVIOUR_MATURITY = 3;
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// Interview Ratios
|
|
117
|
+
// =============================================================================
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Default ratio of interview time allocated to skills vs behaviours
|
|
121
|
+
*
|
|
122
|
+
* 0.6 = 60% skills, 40% behaviours
|
|
123
|
+
*/
|
|
124
|
+
export const RATIO_SKILL_BEHAVIOUR = 0.6;
|
|
125
|
+
|
|
126
|
+
// =============================================================================
|
|
127
|
+
// Skill Level Multipliers
|
|
128
|
+
// =============================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Skill level multiplier for priority calculation
|
|
132
|
+
*
|
|
133
|
+
* Higher skill levels get proportionally more priority.
|
|
134
|
+
*/
|
|
135
|
+
export const WEIGHT_SKILL_LEVEL = 2;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Priority penalty for below-level questions
|
|
139
|
+
*/
|
|
140
|
+
export const WEIGHT_BELOW_LEVEL_PENALTY = -5;
|
|
141
|
+
|
|
142
|
+
// =============================================================================
|
|
143
|
+
// Development Path Weights
|
|
144
|
+
// =============================================================================
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Type multipliers for development path prioritization
|
|
148
|
+
*
|
|
149
|
+
* Primary skills are more critical to develop first.
|
|
150
|
+
*/
|
|
151
|
+
export const WEIGHT_DEV_TYPE_PRIMARY = 3;
|
|
152
|
+
export const WEIGHT_DEV_TYPE_SECONDARY = 2;
|
|
153
|
+
export const WEIGHT_DEV_TYPE_BROAD = 1;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* AI capability boost for development paths
|
|
157
|
+
*
|
|
158
|
+
* AI skills get extra emphasis in development planning.
|
|
159
|
+
*/
|
|
160
|
+
export const WEIGHT_DEV_AI_BOOST = 1.5;
|
|
161
|
+
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Agent Profile Limits
|
|
164
|
+
// =============================================================================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Maximum number of skills shown in agent profile body
|
|
168
|
+
*
|
|
169
|
+
* Limits the skill index table and before-handoff checklist to keep
|
|
170
|
+
* agent context focused. All skills are still exported as SKILL.md files
|
|
171
|
+
* and listed via --skills.
|
|
172
|
+
*
|
|
173
|
+
* @see agent.js:buildStageProfileBodyData
|
|
174
|
+
*/
|
|
175
|
+
export const LIMIT_AGENT_PROFILE_SKILLS = 5;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Maximum number of working style entries from top behaviours
|
|
179
|
+
* in agent profiles.
|
|
180
|
+
*
|
|
181
|
+
* @see agent.js:buildWorkingStyleFromBehaviours
|
|
182
|
+
*/
|
|
183
|
+
export const LIMIT_AGENT_WORKING_STYLES = 3;
|
|
184
|
+
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// Interview Time Defaults
|
|
187
|
+
// =============================================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Default expected duration for a standard interview question (minutes)
|
|
191
|
+
*/
|
|
192
|
+
export const DEFAULT_INTERVIEW_QUESTION_MINUTES = 5;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Default expected duration for a decomposition question (minutes)
|
|
196
|
+
*/
|
|
197
|
+
export const DEFAULT_DECOMPOSITION_QUESTION_MINUTES = 15;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Default expected duration for a stakeholder simulation question (minutes)
|
|
201
|
+
*/
|
|
202
|
+
export const DEFAULT_SIMULATION_QUESTION_MINUTES = 20;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Tolerance above target interview budget before stopping selection (minutes)
|
|
206
|
+
*
|
|
207
|
+
* Interview question selection allows exceeding the time budget by this amount
|
|
208
|
+
* to avoid under-filling interviews.
|
|
209
|
+
*/
|
|
210
|
+
export const TOLERANCE_INTERVIEW_BUDGET_MINUTES = 5;
|
|
211
|
+
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// Decomposition Capability Weights
|
|
214
|
+
// =============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Capability priority weights for decomposition interviews
|
|
218
|
+
*
|
|
219
|
+
* Delivery and scale capabilities are typically more important for
|
|
220
|
+
* system decomposition questions.
|
|
221
|
+
*
|
|
222
|
+
* @see interview.js:calculateCapabilityPriority
|
|
223
|
+
*/
|
|
224
|
+
export const WEIGHT_CAPABILITY_DECOMP_DELIVERY = 10;
|
|
225
|
+
export const WEIGHT_CAPABILITY_DECOMP_SCALE = 8;
|
|
226
|
+
export const WEIGHT_CAPABILITY_DECOMP_RELIABILITY = 6;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Priority boost applied to focus-area questions in focused interviews
|
|
230
|
+
*
|
|
231
|
+
* @see interview.js:deriveFocusedInterview
|
|
232
|
+
*/
|
|
233
|
+
export const WEIGHT_FOCUS_BOOST = 10;
|
|
234
|
+
|
|
235
|
+
// =============================================================================
|
|
236
|
+
// Senior Grade Threshold
|
|
237
|
+
// =============================================================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Minimum ordinalRank for a grade to be considered "senior" (Staff+)
|
|
241
|
+
*
|
|
242
|
+
* Used to determine when additional expectations scoring applies
|
|
243
|
+
* in job matching.
|
|
244
|
+
*
|
|
245
|
+
* @see derivation.js:isSeniorGrade
|
|
246
|
+
*/
|
|
247
|
+
export const THRESHOLD_SENIOR_GRADE = 5;
|
|
248
|
+
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// Assessment Weights
|
|
251
|
+
// =============================================================================
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Default skill weight when track does not specify assessment weights
|
|
255
|
+
*/
|
|
256
|
+
export const WEIGHT_ASSESSMENT_SKILL_DEFAULT = 0.5;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Default behaviour weight when track does not specify assessment weights
|
|
260
|
+
*/
|
|
261
|
+
export const WEIGHT_ASSESSMENT_BEHAVIOUR_DEFAULT = 0.5;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Base weight for overall score in senior role matching (non-expectations portion)
|
|
265
|
+
*/
|
|
266
|
+
export const WEIGHT_SENIOR_BASE = 0.9;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Weight for expectations score bonus in senior role matching
|
|
270
|
+
*/
|
|
271
|
+
export const WEIGHT_SENIOR_EXPECTATIONS = 0.1;
|
|
272
|
+
|
|
273
|
+
// =============================================================================
|
|
274
|
+
// Match Result Limits
|
|
275
|
+
// =============================================================================
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Number of top-priority gaps to surface in match analysis
|
|
279
|
+
*/
|
|
280
|
+
export const LIMIT_PRIORITY_GAPS = 3;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Score bonus for same-track candidates in next-step job matching
|
|
284
|
+
*/
|
|
285
|
+
export const WEIGHT_SAME_TRACK_BONUS = 0.1;
|
|
286
|
+
|
|
287
|
+
// =============================================================================
|
|
288
|
+
// Realistic Match Filtering
|
|
289
|
+
// =============================================================================
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Grade offset (±) from best-fit grade for realistic match filtering
|
|
293
|
+
*/
|
|
294
|
+
export const RANGE_GRADE_OFFSET = 1;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Grade offset below highest strong/good match for ready-tier filtering
|
|
298
|
+
*
|
|
299
|
+
* Strong and Good matches are shown up to this many levels below the
|
|
300
|
+
* highest matched grade. Stretch and Aspirational matches are only shown
|
|
301
|
+
* at or above the highest matched grade.
|
|
302
|
+
*/
|
|
303
|
+
export const RANGE_READY_GRADE_OFFSET = 2;
|
|
304
|
+
|
|
305
|
+
// =============================================================================
|
|
306
|
+
// Driver Coverage Thresholds
|
|
307
|
+
// =============================================================================
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Minimum skill level for a skill to count as "covered" in driver analysis
|
|
311
|
+
*/
|
|
312
|
+
export const THRESHOLD_DRIVER_SKILL_LEVEL = "working";
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Minimum behaviour maturity for a behaviour to count as "covered" in driver analysis
|
|
316
|
+
*/
|
|
317
|
+
export const THRESHOLD_DRIVER_BEHAVIOUR_MATURITY = "practicing";
|
package/src/profile.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Profile Derivation
|
|
3
|
+
*
|
|
4
|
+
* Shared functions for deriving skill and behaviour profiles for both
|
|
5
|
+
* human jobs and AI agents.
|
|
6
|
+
*
|
|
7
|
+
* - prepareBaseProfile() - core derivation (skills, behaviours, responsibilities)
|
|
8
|
+
* - prepareAgentProfile() - agent-specific derivation using composed policies
|
|
9
|
+
*
|
|
10
|
+
* @see policies/composed.js - Agent filtering and sorting policies
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
deriveSkillMatrix,
|
|
15
|
+
deriveBehaviourProfile,
|
|
16
|
+
deriveResponsibilities,
|
|
17
|
+
} from "./derivation.js";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
prepareAgentSkillMatrix,
|
|
21
|
+
prepareAgentBehaviourProfile,
|
|
22
|
+
} from "./policies/composed.js";
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Utility Functions
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build set of capabilities with positive track modifiers
|
|
30
|
+
* @param {Object} track - Track definition
|
|
31
|
+
* @returns {Set<string>} Set of capability IDs with positive modifiers
|
|
32
|
+
*/
|
|
33
|
+
export function getPositiveTrackCapabilities(track) {
|
|
34
|
+
return new Set(
|
|
35
|
+
Object.entries(track.skillModifiers || {})
|
|
36
|
+
.filter(([_, modifier]) => modifier > 0)
|
|
37
|
+
.map(([capability]) => capability),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Profile Derivation
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} BaseProfile
|
|
47
|
+
* @property {Array} skillMatrix - Derived skill matrix
|
|
48
|
+
* @property {Array} behaviourProfile - Derived behaviour profile
|
|
49
|
+
* @property {Array} derivedResponsibilities - Derived responsibilities (if capabilities provided)
|
|
50
|
+
* @property {Object} discipline - The discipline
|
|
51
|
+
* @property {Object} track - The track
|
|
52
|
+
* @property {Object} grade - The grade
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prepare a base profile with raw derivation
|
|
57
|
+
*
|
|
58
|
+
* Core derivation entry point shared by jobs and agents. Produces the
|
|
59
|
+
* raw skill matrix, behaviour profile, and responsibilities without
|
|
60
|
+
* any filtering or sorting. Consumers apply policies as needed:
|
|
61
|
+
*
|
|
62
|
+
* - Human jobs: use raw output directly (sorted by type in derivation)
|
|
63
|
+
* - AI agents: use prepareAgentProfile() which applies composed policies
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} params
|
|
66
|
+
* @param {Object} params.discipline - The discipline
|
|
67
|
+
* @param {Object} params.track - The track
|
|
68
|
+
* @param {Object} params.grade - The grade
|
|
69
|
+
* @param {Array} params.skills - All available skills
|
|
70
|
+
* @param {Array} params.behaviours - All available behaviours
|
|
71
|
+
* @param {Array} [params.capabilities] - Optional capabilities for responsibility derivation
|
|
72
|
+
* @returns {BaseProfile} The prepared profile
|
|
73
|
+
*/
|
|
74
|
+
export function prepareBaseProfile({
|
|
75
|
+
discipline,
|
|
76
|
+
track,
|
|
77
|
+
grade,
|
|
78
|
+
skills,
|
|
79
|
+
behaviours,
|
|
80
|
+
capabilities,
|
|
81
|
+
}) {
|
|
82
|
+
// Core derivation
|
|
83
|
+
const skillMatrix = deriveSkillMatrix({ discipline, grade, track, skills });
|
|
84
|
+
const behaviourProfile = deriveBehaviourProfile({
|
|
85
|
+
discipline,
|
|
86
|
+
grade,
|
|
87
|
+
track,
|
|
88
|
+
behaviours,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Derive responsibilities if capabilities provided
|
|
92
|
+
let derivedResponsibilities = [];
|
|
93
|
+
if (capabilities && capabilities.length > 0) {
|
|
94
|
+
derivedResponsibilities = deriveResponsibilities({
|
|
95
|
+
skillMatrix,
|
|
96
|
+
capabilities,
|
|
97
|
+
track,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
skillMatrix,
|
|
103
|
+
behaviourProfile,
|
|
104
|
+
derivedResponsibilities,
|
|
105
|
+
discipline,
|
|
106
|
+
track,
|
|
107
|
+
grade,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Prepare a profile optimized for agent generation
|
|
113
|
+
*
|
|
114
|
+
* Applies agent-specific policies from composed.js:
|
|
115
|
+
* - Excludes human-only skills
|
|
116
|
+
* - Keeps only skills at the highest derived level
|
|
117
|
+
* - Sorts skills by level descending
|
|
118
|
+
* - Sorts behaviours by maturity descending
|
|
119
|
+
*
|
|
120
|
+
* @param {Object} params - Same as prepareBaseProfile
|
|
121
|
+
* @returns {BaseProfile} The prepared profile with agent policies applied
|
|
122
|
+
*/
|
|
123
|
+
export function prepareAgentProfile({
|
|
124
|
+
discipline,
|
|
125
|
+
track,
|
|
126
|
+
grade,
|
|
127
|
+
skills,
|
|
128
|
+
behaviours,
|
|
129
|
+
capabilities,
|
|
130
|
+
}) {
|
|
131
|
+
const base = prepareBaseProfile({
|
|
132
|
+
discipline,
|
|
133
|
+
track,
|
|
134
|
+
grade,
|
|
135
|
+
skills,
|
|
136
|
+
behaviours,
|
|
137
|
+
capabilities,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...base,
|
|
142
|
+
skillMatrix: prepareAgentSkillMatrix(base.skillMatrix),
|
|
143
|
+
behaviourProfile: prepareAgentBehaviourProfile(base.behaviourProfile),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
getBehaviourMaturityIndex,
|
|
12
12
|
} from "@forwardimpact/schema/levels";
|
|
13
13
|
import { deriveJob, isValidJobCombination } from "./derivation.js";
|
|
14
|
+
import {
|
|
15
|
+
compareBySkillChange,
|
|
16
|
+
compareByBehaviourChange,
|
|
17
|
+
} from "./policies/orderings.js";
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* @typedef {Object} SkillChange
|
|
@@ -128,14 +132,8 @@ export function calculateSkillChanges(currentMatrix, targetMatrix) {
|
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
// Sort
|
|
132
|
-
|
|
133
|
-
changes.sort((a, b) => {
|
|
134
|
-
if (b.change !== a.change) return b.change - a.change;
|
|
135
|
-
if (typeOrder[a.type] !== typeOrder[b.type])
|
|
136
|
-
return typeOrder[a.type] - typeOrder[b.type];
|
|
137
|
-
return a.name.localeCompare(b.name);
|
|
138
|
-
});
|
|
135
|
+
// Sort using policy comparator
|
|
136
|
+
changes.sort(compareBySkillChange);
|
|
139
137
|
|
|
140
138
|
return changes;
|
|
141
139
|
}
|
|
@@ -172,11 +170,8 @@ export function calculateBehaviourChanges(currentProfile, targetProfile) {
|
|
|
172
170
|
}
|
|
173
171
|
}
|
|
174
172
|
|
|
175
|
-
// Sort
|
|
176
|
-
changes.sort(
|
|
177
|
-
if (b.change !== a.change) return b.change - a.change;
|
|
178
|
-
return a.name.localeCompare(b.name);
|
|
179
|
-
});
|
|
173
|
+
// Sort using policy comparator
|
|
174
|
+
changes.sort(compareByBehaviourChange);
|
|
180
175
|
|
|
181
176
|
return changes;
|
|
182
177
|
}
|
package/{lib → src}/toolkit.js
RENAMED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* level contribute tools, ensuring focused toolkits for both jobs and agents.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { filterToolkitSkills } from "./policies/composed.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @typedef {Object} ToolkitEntry
|
|
@@ -30,8 +30,8 @@ import { filterByHighestLevel } from "./profile.js";
|
|
|
30
30
|
* @returns {ToolkitEntry[]} De-duplicated toolkit sorted by name
|
|
31
31
|
*/
|
|
32
32
|
export function deriveToolkit({ skillMatrix, skills }) {
|
|
33
|
-
// Filter to highest level skills only
|
|
34
|
-
const sourceMatrix =
|
|
33
|
+
// Filter to highest level skills only using policy
|
|
34
|
+
const sourceMatrix = filterToolkitSkills(skillMatrix);
|
|
35
35
|
|
|
36
36
|
// Build skill lookup map for O(1) access
|
|
37
37
|
const skillMap = new Map(skills.map((s) => [s.id, s]));
|