@forwardimpact/model 0.5.0 → 0.7.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.
- package/README.md +2 -2
- package/package.json +17 -15
- package/{lib → src}/agent.js +14 -12
- package/{lib → src}/derivation.js +6 -9
- package/{lib → src}/index.js +59 -17
- package/{lib → src}/interview.js +450 -21
- package/{lib → src}/job-cache.js +7 -7
- package/{lib → src}/matching.js +38 -28
- package/{lib → src}/modifiers.js +4 -4
- package/src/policies/composed.js +111 -0
- package/src/policies/filters.js +104 -0
- package/src/policies/index.js +128 -0
- package/src/policies/orderings.js +300 -0
- package/src/policies/predicates.js +177 -0
- package/src/policies/thresholds.js +160 -0
- package/src/profile.js +182 -0
- package/{lib → src}/progression.js +8 -13
- package/{lib → src}/toolkit.js +3 -3
- package/lib/profile.js +0 -262
- /package/{lib → src}/checklist.js +0 -0
- /package/{lib → src}/job.js +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orderings and Comparator Functions
|
|
3
|
+
*
|
|
4
|
+
* Canonical orderings for entity types and comparator functions for sorting.
|
|
5
|
+
*
|
|
6
|
+
* Naming conventions:
|
|
7
|
+
* - ORDER_* - canonical ordering arrays
|
|
8
|
+
* - compareBy* - comparator functions for Array.sort()
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getSkillLevelIndex,
|
|
13
|
+
getBehaviourMaturityIndex,
|
|
14
|
+
getCapabilityOrder,
|
|
15
|
+
} from "@forwardimpact/schema/levels";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Canonical Orderings
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Skill type ordering (T-shaped profile: core → broad)
|
|
23
|
+
* Primary skills first, then secondary, broad, and track-added skills.
|
|
24
|
+
*/
|
|
25
|
+
export const ORDER_SKILL_TYPE = ["primary", "secondary", "broad", "track"];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Engineering lifecycle stages in execution order
|
|
29
|
+
*/
|
|
30
|
+
export const ORDER_STAGE = ["specify", "plan", "code", "review", "deploy"];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Agent stage ordering (subset used for agent generation)
|
|
34
|
+
*/
|
|
35
|
+
export const ORDER_AGENT_STAGE = ["plan", "code", "review"];
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Skill Comparators
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compare skills by level descending (higher level first)
|
|
43
|
+
* @param {Object} a - First skill entry
|
|
44
|
+
* @param {Object} b - Second skill entry
|
|
45
|
+
* @returns {number} Comparison result
|
|
46
|
+
*/
|
|
47
|
+
export function compareByLevelDesc(a, b) {
|
|
48
|
+
return getSkillLevelIndex(b.level) - getSkillLevelIndex(a.level);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Compare skills by level ascending (lower level first)
|
|
53
|
+
* @param {Object} a - First skill entry
|
|
54
|
+
* @param {Object} b - Second skill entry
|
|
55
|
+
* @returns {number} Comparison result
|
|
56
|
+
*/
|
|
57
|
+
export function compareByLevelAsc(a, b) {
|
|
58
|
+
return getSkillLevelIndex(a.level) - getSkillLevelIndex(b.level);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Compare skills by type (primary first)
|
|
63
|
+
* @param {Object} a - First skill entry
|
|
64
|
+
* @param {Object} b - Second skill entry
|
|
65
|
+
* @returns {number} Comparison result
|
|
66
|
+
*/
|
|
67
|
+
export function compareByType(a, b) {
|
|
68
|
+
return ORDER_SKILL_TYPE.indexOf(a.type) - ORDER_SKILL_TYPE.indexOf(b.type);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compare skills by name alphabetically
|
|
73
|
+
* @param {Object} a - First skill entry
|
|
74
|
+
* @param {Object} b - Second skill entry
|
|
75
|
+
* @returns {number} Comparison result
|
|
76
|
+
*/
|
|
77
|
+
export function compareByName(a, b) {
|
|
78
|
+
const nameA = a.skillName || a.name;
|
|
79
|
+
const nameB = b.skillName || b.name;
|
|
80
|
+
return nameA.localeCompare(nameB);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compare skills by level (desc), then type (asc), then name (asc)
|
|
85
|
+
*
|
|
86
|
+
* Standard priority ordering for skill display:
|
|
87
|
+
* - Higher levels first
|
|
88
|
+
* - Within same level, primary before secondary before broad
|
|
89
|
+
* - Within same type, alphabetical by name
|
|
90
|
+
*
|
|
91
|
+
* @param {Object} a - First skill entry
|
|
92
|
+
* @param {Object} b - Second skill entry
|
|
93
|
+
* @returns {number} Comparison result
|
|
94
|
+
*/
|
|
95
|
+
export function compareBySkillPriority(a, b) {
|
|
96
|
+
// Level descending (higher level first)
|
|
97
|
+
const levelDiff = getSkillLevelIndex(b.level) - getSkillLevelIndex(a.level);
|
|
98
|
+
if (levelDiff !== 0) return levelDiff;
|
|
99
|
+
|
|
100
|
+
// Type ascending (primary first)
|
|
101
|
+
const typeA = ORDER_SKILL_TYPE.indexOf(a.type);
|
|
102
|
+
const typeB = ORDER_SKILL_TYPE.indexOf(b.type);
|
|
103
|
+
if (typeA !== typeB) return typeA - typeB;
|
|
104
|
+
|
|
105
|
+
// Name ascending (alphabetical)
|
|
106
|
+
const nameA = a.skillName || a.name;
|
|
107
|
+
const nameB = b.skillName || b.name;
|
|
108
|
+
return nameA.localeCompare(nameB);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Compare skills by type (asc), then name (asc)
|
|
113
|
+
*
|
|
114
|
+
* Standard ordering for job skill matrix display:
|
|
115
|
+
* - Primary skills first, then secondary, then broad, then track
|
|
116
|
+
* - Within same type, alphabetical by name
|
|
117
|
+
*
|
|
118
|
+
* @param {Object} a - First skill entry
|
|
119
|
+
* @param {Object} b - Second skill entry
|
|
120
|
+
* @returns {number} Comparison result
|
|
121
|
+
*/
|
|
122
|
+
export function compareByTypeAndName(a, b) {
|
|
123
|
+
const typeCompare = compareByType(a, b);
|
|
124
|
+
if (typeCompare !== 0) return typeCompare;
|
|
125
|
+
return compareByName(a, b);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// Behaviour Comparators
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compare behaviours by maturity descending (higher maturity first)
|
|
134
|
+
* @param {Object} a - First behaviour entry
|
|
135
|
+
* @param {Object} b - Second behaviour entry
|
|
136
|
+
* @returns {number} Comparison result
|
|
137
|
+
*/
|
|
138
|
+
export function compareByMaturityDesc(a, b) {
|
|
139
|
+
return (
|
|
140
|
+
getBehaviourMaturityIndex(b.maturity) -
|
|
141
|
+
getBehaviourMaturityIndex(a.maturity)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Compare behaviours by maturity ascending (lower maturity first)
|
|
147
|
+
* @param {Object} a - First behaviour entry
|
|
148
|
+
* @param {Object} b - Second behaviour entry
|
|
149
|
+
* @returns {number} Comparison result
|
|
150
|
+
*/
|
|
151
|
+
export function compareByMaturityAsc(a, b) {
|
|
152
|
+
return (
|
|
153
|
+
getBehaviourMaturityIndex(a.maturity) -
|
|
154
|
+
getBehaviourMaturityIndex(b.maturity)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Compare behaviours by name alphabetically
|
|
160
|
+
* @param {Object} a - First behaviour entry
|
|
161
|
+
* @param {Object} b - Second behaviour entry
|
|
162
|
+
* @returns {number} Comparison result
|
|
163
|
+
*/
|
|
164
|
+
export function compareByBehaviourName(a, b) {
|
|
165
|
+
const nameA = a.behaviourName || a.name;
|
|
166
|
+
const nameB = b.behaviourName || b.name;
|
|
167
|
+
return nameA.localeCompare(nameB);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Compare behaviours by maturity (desc), then name (asc)
|
|
172
|
+
*
|
|
173
|
+
* Standard priority ordering for behaviour display:
|
|
174
|
+
* - Higher maturity first
|
|
175
|
+
* - Within same maturity, alphabetical by name
|
|
176
|
+
*
|
|
177
|
+
* @param {Object} a - First behaviour entry
|
|
178
|
+
* @param {Object} b - Second behaviour entry
|
|
179
|
+
* @returns {number} Comparison result
|
|
180
|
+
*/
|
|
181
|
+
export function compareByBehaviourPriority(a, b) {
|
|
182
|
+
const maturityDiff =
|
|
183
|
+
getBehaviourMaturityIndex(b.maturity) -
|
|
184
|
+
getBehaviourMaturityIndex(a.maturity);
|
|
185
|
+
if (maturityDiff !== 0) return maturityDiff;
|
|
186
|
+
return compareByBehaviourName(a, b);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// =============================================================================
|
|
190
|
+
// Capability Comparators
|
|
191
|
+
// =============================================================================
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a comparator for sorting by capability ordinal rank
|
|
195
|
+
*
|
|
196
|
+
* The returned comparator uses ordinalRank from loaded capability data,
|
|
197
|
+
* making the ordering data-driven rather than hardcoded.
|
|
198
|
+
*
|
|
199
|
+
* @param {Object[]} capabilities - Loaded capabilities array
|
|
200
|
+
* @returns {(a: Object, b: Object) => number} Comparator function
|
|
201
|
+
*/
|
|
202
|
+
export function compareByCapability(capabilities) {
|
|
203
|
+
const order = getCapabilityOrder(capabilities);
|
|
204
|
+
return (a, b) => {
|
|
205
|
+
const capA = a.capability || "";
|
|
206
|
+
const capB = b.capability || "";
|
|
207
|
+
return order.indexOf(capA) - order.indexOf(capB);
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Sort skills by capability (display order), then by name
|
|
213
|
+
*
|
|
214
|
+
* @param {Object[]} skills - Array of skills to sort
|
|
215
|
+
* @param {Object[]} capabilities - Loaded capabilities array
|
|
216
|
+
* @returns {Object[]} Sorted array (new array, does not mutate input)
|
|
217
|
+
*/
|
|
218
|
+
export function sortSkillsByCapability(skills, capabilities) {
|
|
219
|
+
const capabilityComparator = compareByCapability(capabilities);
|
|
220
|
+
return [...skills].sort((a, b) => {
|
|
221
|
+
const capCompare = capabilityComparator(a, b);
|
|
222
|
+
if (capCompare !== 0) return capCompare;
|
|
223
|
+
const nameA = a.skillName || a.name;
|
|
224
|
+
const nameB = b.skillName || b.name;
|
|
225
|
+
return nameA.localeCompare(nameB);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// Generic Comparator Factory
|
|
231
|
+
// =============================================================================
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Create comparator from an ordering array
|
|
235
|
+
*
|
|
236
|
+
* @param {string[]} order - Canonical order
|
|
237
|
+
* @param {(item: Object) => string} accessor - Extract value to compare
|
|
238
|
+
* @returns {(a: Object, b: Object) => number}
|
|
239
|
+
*/
|
|
240
|
+
export function compareByOrder(order, accessor) {
|
|
241
|
+
return (a, b) => order.indexOf(accessor(a)) - order.indexOf(accessor(b));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Chain multiple comparators together
|
|
246
|
+
*
|
|
247
|
+
* Returns first non-zero result, or 0 if all comparators return 0.
|
|
248
|
+
*
|
|
249
|
+
* @param {...Function} comparators - Comparator functions
|
|
250
|
+
* @returns {(a: Object, b: Object) => number}
|
|
251
|
+
*/
|
|
252
|
+
export function chainComparators(...comparators) {
|
|
253
|
+
return (a, b) => {
|
|
254
|
+
for (const comparator of comparators) {
|
|
255
|
+
const result = comparator(a, b);
|
|
256
|
+
if (result !== 0) return result;
|
|
257
|
+
}
|
|
258
|
+
return 0;
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// =============================================================================
|
|
263
|
+
// Skill Change Comparators (for progression)
|
|
264
|
+
// =============================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Compare skill changes by change magnitude (largest first), then type, then name
|
|
268
|
+
*
|
|
269
|
+
* Used for career progression analysis where biggest changes are most important.
|
|
270
|
+
*
|
|
271
|
+
* @param {Object} a - First skill change
|
|
272
|
+
* @param {Object} b - Second skill change
|
|
273
|
+
* @returns {number} Comparison result
|
|
274
|
+
*/
|
|
275
|
+
export function compareBySkillChange(a, b) {
|
|
276
|
+
// Change descending (largest improvement first)
|
|
277
|
+
if (b.change !== a.change) return b.change - a.change;
|
|
278
|
+
|
|
279
|
+
// Type ascending (primary first)
|
|
280
|
+
const typeA = ORDER_SKILL_TYPE.indexOf(a.type);
|
|
281
|
+
const typeB = ORDER_SKILL_TYPE.indexOf(b.type);
|
|
282
|
+
if (typeA !== typeB) return typeA - typeB;
|
|
283
|
+
|
|
284
|
+
// Name ascending
|
|
285
|
+
return a.name.localeCompare(b.name);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Compare behaviour changes by change magnitude (largest first), then name
|
|
290
|
+
*
|
|
291
|
+
* Used for career progression analysis.
|
|
292
|
+
*
|
|
293
|
+
* @param {Object} a - First behaviour change
|
|
294
|
+
* @param {Object} b - Second behaviour change
|
|
295
|
+
* @returns {number} Comparison result
|
|
296
|
+
*/
|
|
297
|
+
export function compareByBehaviourChange(a, b) {
|
|
298
|
+
if (b.change !== a.change) return b.change - a.change;
|
|
299
|
+
return a.name.localeCompare(b.name);
|
|
300
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry-Level Predicate Functions
|
|
3
|
+
*
|
|
4
|
+
* Pure predicates that operate on single skill/behaviour matrix entries.
|
|
5
|
+
* Each predicate takes one entry and returns boolean.
|
|
6
|
+
*
|
|
7
|
+
* Naming conventions:
|
|
8
|
+
* - is* - checks a boolean condition
|
|
9
|
+
* - has* - checks presence of a value
|
|
10
|
+
* - allOf/anyOf/not - combinators
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getSkillLevelIndex } from "@forwardimpact/schema/levels";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Identity Predicates
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Always returns true (identity predicate for optional filtering) */
|
|
20
|
+
export const isAny = () => true;
|
|
21
|
+
|
|
22
|
+
/** Always returns false (null predicate) */
|
|
23
|
+
export const isNone = () => false;
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Human-Only Predicates
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns true if skill is marked as human-only
|
|
31
|
+
* @param {Object} entry - Skill matrix entry
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export const isHumanOnly = (entry) => entry.isHumanOnly === true;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns true if skill is NOT human-only (agent-eligible)
|
|
38
|
+
* @param {Object} entry - Skill matrix entry
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export const isAgentEligible = (entry) => !entry.isHumanOnly;
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Skill Type Predicates
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns true if skill type is primary
|
|
49
|
+
* @param {Object} entry - Skill matrix entry
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export const isPrimary = (entry) => entry.type === "primary";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns true if skill type is secondary
|
|
56
|
+
* @param {Object} entry - Skill matrix entry
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
export const isSecondary = (entry) => entry.type === "secondary";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns true if skill type is broad
|
|
63
|
+
* @param {Object} entry - Skill matrix entry
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export const isBroad = (entry) => entry.type === "broad";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns true if skill type is track
|
|
70
|
+
* @param {Object} entry - Skill matrix entry
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
export const isTrack = (entry) => entry.type === "track";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns true if skill is primary or secondary (core skills)
|
|
77
|
+
* @param {Object} entry - Skill matrix entry
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
export const isCore = (entry) =>
|
|
81
|
+
entry.type === "primary" || entry.type === "secondary";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns true if skill is broad or track (supporting skills)
|
|
85
|
+
* @param {Object} entry - Skill matrix entry
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
export const isSupporting = (entry) =>
|
|
89
|
+
entry.type === "broad" || entry.type === "track";
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Skill Level Predicates
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create predicate for skills at or above a minimum level
|
|
97
|
+
* @param {string} minLevel - Minimum skill level
|
|
98
|
+
* @returns {(entry: Object) => boolean}
|
|
99
|
+
*/
|
|
100
|
+
export function hasMinLevel(minLevel) {
|
|
101
|
+
const minIndex = getSkillLevelIndex(minLevel);
|
|
102
|
+
return (entry) => getSkillLevelIndex(entry.level) >= minIndex;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create predicate for skills at exactly a specific level
|
|
107
|
+
* @param {string} level - Exact skill level to match
|
|
108
|
+
* @returns {(entry: Object) => boolean}
|
|
109
|
+
*/
|
|
110
|
+
export function hasLevel(level) {
|
|
111
|
+
const targetIndex = getSkillLevelIndex(level);
|
|
112
|
+
return (entry) => getSkillLevelIndex(entry.level) === targetIndex;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create predicate for skills below a level threshold
|
|
117
|
+
* @param {string} maxLevel - Level that must NOT be reached
|
|
118
|
+
* @returns {(entry: Object) => boolean}
|
|
119
|
+
*/
|
|
120
|
+
export function hasBelowLevel(maxLevel) {
|
|
121
|
+
const maxIndex = getSkillLevelIndex(maxLevel);
|
|
122
|
+
return (entry) => getSkillLevelIndex(entry.level) < maxIndex;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// Capability Predicates
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create predicate for skills in a specific capability
|
|
131
|
+
* @param {string} capability - Capability to match
|
|
132
|
+
* @returns {(entry: Object) => boolean}
|
|
133
|
+
*/
|
|
134
|
+
export function isInCapability(capability) {
|
|
135
|
+
return (entry) => entry.capability === capability;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create predicate for skills in any of the specified capabilities
|
|
140
|
+
* @param {string[]} capabilities - Capabilities to match
|
|
141
|
+
* @returns {(entry: Object) => boolean}
|
|
142
|
+
*/
|
|
143
|
+
export function isInAnyCapability(capabilities) {
|
|
144
|
+
const set = new Set(capabilities);
|
|
145
|
+
return (entry) => set.has(entry.capability);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// =============================================================================
|
|
149
|
+
// Combinators
|
|
150
|
+
// =============================================================================
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Compose predicates with AND logic (all must pass)
|
|
154
|
+
* @param {...Function} predicates - Predicates to combine
|
|
155
|
+
* @returns {(entry: Object) => boolean}
|
|
156
|
+
*/
|
|
157
|
+
export function allOf(...predicates) {
|
|
158
|
+
return (entry) => predicates.every((p) => p(entry));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Compose predicates with OR logic (any must pass)
|
|
163
|
+
* @param {...Function} predicates - Predicates to combine
|
|
164
|
+
* @returns {(entry: Object) => boolean}
|
|
165
|
+
*/
|
|
166
|
+
export function anyOf(...predicates) {
|
|
167
|
+
return (entry) => predicates.some((p) => p(entry));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Negate a predicate
|
|
172
|
+
* @param {Function} predicate - Predicate to negate
|
|
173
|
+
* @returns {(entry: Object) => boolean}
|
|
174
|
+
*/
|
|
175
|
+
export function not(predicate) {
|
|
176
|
+
return (entry) => !predicate(entry);
|
|
177
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
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;
|