@forwardimpact/model 0.7.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/package.json +1 -1
- package/src/agent.js +11 -7
- package/src/checklist.js +2 -10
- package/src/derivation.js +11 -6
- package/src/index.js +29 -0
- package/src/interview.js +79 -46
- package/src/matching.js +27 -13
- package/src/policies/composed.js +24 -0
- package/src/policies/index.js +32 -0
- package/src/policies/orderings.js +12 -0
- package/src/policies/thresholds.js +157 -0
- package/src/profile.js +24 -61
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
filterAgentSkills,
|
|
25
25
|
sortAgentSkills,
|
|
26
26
|
sortAgentBehaviours,
|
|
27
|
+
focusAgentSkills,
|
|
27
28
|
} from "./policies/composed.js";
|
|
28
29
|
import { ORDER_AGENT_STAGE } from "./policies/orderings.js";
|
|
30
|
+
import { LIMIT_AGENT_WORKING_STYLES } from "./policies/thresholds.js";
|
|
29
31
|
import { SkillLevel } from "@forwardimpact/schema/levels";
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -194,7 +196,7 @@ function findAgentBehaviour(agentBehaviours, id) {
|
|
|
194
196
|
function buildWorkingStyleFromBehaviours(
|
|
195
197
|
derivedBehaviours,
|
|
196
198
|
agentBehaviours,
|
|
197
|
-
topN =
|
|
199
|
+
topN = LIMIT_AGENT_WORKING_STYLES,
|
|
198
200
|
) {
|
|
199
201
|
const entries = [];
|
|
200
202
|
|
|
@@ -518,7 +520,7 @@ function buildStageProfileBodyData({
|
|
|
518
520
|
? substituteTemplateVars(rawPriority, humanDiscipline)
|
|
519
521
|
: null;
|
|
520
522
|
|
|
521
|
-
// Build skill index from derived skills
|
|
523
|
+
// Build skill index from derived skills (already focused by deriveStageAgent)
|
|
522
524
|
const skillIndex = derivedSkills
|
|
523
525
|
.map((derived) => {
|
|
524
526
|
const skill = skills.find((s) => s.id === derived.skillId);
|
|
@@ -538,7 +540,6 @@ function buildStageProfileBodyData({
|
|
|
538
540
|
const workingStyles = buildWorkingStyleFromBehaviours(
|
|
539
541
|
derivedBehaviours,
|
|
540
542
|
agentBehaviours,
|
|
541
|
-
3,
|
|
542
543
|
);
|
|
543
544
|
|
|
544
545
|
// Constraints (stage + discipline + track)
|
|
@@ -602,13 +603,16 @@ export function deriveStageAgent({
|
|
|
602
603
|
stages,
|
|
603
604
|
}) {
|
|
604
605
|
// Derive skills and behaviours
|
|
605
|
-
const
|
|
606
|
+
const allSkills = deriveAgentSkills({
|
|
606
607
|
discipline,
|
|
607
608
|
track,
|
|
608
609
|
grade,
|
|
609
610
|
skills,
|
|
610
611
|
});
|
|
611
612
|
|
|
613
|
+
// Focus skills for profile body (limited set to reduce context bloat)
|
|
614
|
+
const focusedSkills = focusAgentSkills(allSkills);
|
|
615
|
+
|
|
612
616
|
const derivedBehaviours = deriveAgentBehaviours({
|
|
613
617
|
discipline,
|
|
614
618
|
track,
|
|
@@ -624,13 +628,13 @@ export function deriveStageAgent({
|
|
|
624
628
|
stages,
|
|
625
629
|
});
|
|
626
630
|
|
|
627
|
-
// Derive checklist
|
|
631
|
+
// Derive checklist from focused skills only
|
|
628
632
|
const checklistStage = getChecklistStage(stage.id);
|
|
629
633
|
let checklist = [];
|
|
630
634
|
if (checklistStage && capabilities) {
|
|
631
635
|
checklist = deriveChecklist({
|
|
632
636
|
stageId: checklistStage,
|
|
633
|
-
skillMatrix:
|
|
637
|
+
skillMatrix: focusedSkills,
|
|
634
638
|
skills,
|
|
635
639
|
capabilities,
|
|
636
640
|
});
|
|
@@ -640,7 +644,7 @@ export function deriveStageAgent({
|
|
|
640
644
|
stage,
|
|
641
645
|
discipline,
|
|
642
646
|
track,
|
|
643
|
-
derivedSkills,
|
|
647
|
+
derivedSkills: focusedSkills,
|
|
644
648
|
derivedBehaviours,
|
|
645
649
|
handoffs,
|
|
646
650
|
constraints: [
|
package/src/checklist.js
CHANGED
|
@@ -7,15 +7,7 @@
|
|
|
7
7
|
* Checklist = Stage × Skill Matrix × Skill Ready Criteria
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
* Map from stage ID to the stage whose ready criteria should be shown
|
|
12
|
-
* (i.e., what must be ready before leaving this stage)
|
|
13
|
-
*/
|
|
14
|
-
const STAGE_TO_HANDOFF = {
|
|
15
|
-
plan: "plan", // Show plan.ready before leaving plan
|
|
16
|
-
code: "code", // Show code.ready before leaving code
|
|
17
|
-
review: "review", // Show review.ready (completion criteria)
|
|
18
|
-
};
|
|
10
|
+
import { CHECKLIST_STAGE_MAP } from "./policies/orderings.js";
|
|
19
11
|
|
|
20
12
|
/**
|
|
21
13
|
* Derive checklist items for a specific stage
|
|
@@ -34,7 +26,7 @@ export function deriveChecklist({
|
|
|
34
26
|
skills,
|
|
35
27
|
capabilities,
|
|
36
28
|
}) {
|
|
37
|
-
const targetStage =
|
|
29
|
+
const targetStage = CHECKLIST_STAGE_MAP[stageId];
|
|
38
30
|
if (!targetStage) {
|
|
39
31
|
return [];
|
|
40
32
|
}
|
package/src/derivation.js
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
SkillType,
|
|
10
|
-
SkillLevel,
|
|
11
|
-
BehaviourMaturity,
|
|
12
10
|
SKILL_LEVEL_ORDER,
|
|
13
11
|
getSkillLevelIndex,
|
|
14
12
|
getBehaviourMaturityIndex,
|
|
@@ -19,6 +17,11 @@ import {
|
|
|
19
17
|
|
|
20
18
|
import { resolveSkillModifier } from "./modifiers.js";
|
|
21
19
|
import { ORDER_SKILL_TYPE } from "./policies/orderings.js";
|
|
20
|
+
import {
|
|
21
|
+
THRESHOLD_SENIOR_GRADE,
|
|
22
|
+
THRESHOLD_DRIVER_SKILL_LEVEL,
|
|
23
|
+
THRESHOLD_DRIVER_BEHAVIOUR_MATURITY,
|
|
24
|
+
} from "./policies/thresholds.js";
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
27
|
* Build a Map of skillId → skillType for a discipline
|
|
@@ -595,7 +598,10 @@ export function calculateDriverCoverage({ job, drivers }) {
|
|
|
595
598
|
|
|
596
599
|
for (const skillId of contributingSkills) {
|
|
597
600
|
const level = jobSkillLevels.get(skillId);
|
|
598
|
-
if (
|
|
601
|
+
if (
|
|
602
|
+
level &&
|
|
603
|
+
skillLevelMeetsRequirement(level, THRESHOLD_DRIVER_SKILL_LEVEL)
|
|
604
|
+
) {
|
|
599
605
|
coveredSkills.push(skillId);
|
|
600
606
|
} else {
|
|
601
607
|
missingSkills.push(skillId);
|
|
@@ -611,7 +617,7 @@ export function calculateDriverCoverage({ job, drivers }) {
|
|
|
611
617
|
const coveredBehaviours = [];
|
|
612
618
|
const missingBehaviours = [];
|
|
613
619
|
const practicingIndex = getBehaviourMaturityIndex(
|
|
614
|
-
|
|
620
|
+
THRESHOLD_DRIVER_BEHAVIOUR_MATURITY,
|
|
615
621
|
);
|
|
616
622
|
|
|
617
623
|
for (const behaviourId of contributingBehaviours) {
|
|
@@ -678,8 +684,7 @@ export function getGradeLevel(grade) {
|
|
|
678
684
|
* @returns {boolean} True if the grade is senior level
|
|
679
685
|
*/
|
|
680
686
|
export function isSeniorGrade(grade) {
|
|
681
|
-
|
|
682
|
-
return grade.ordinalRank >= 5;
|
|
687
|
+
return grade.ordinalRank >= THRESHOLD_SENIOR_GRADE;
|
|
683
688
|
}
|
|
684
689
|
|
|
685
690
|
/**
|
package/src/index.js
CHANGED
|
@@ -130,6 +130,33 @@ export {
|
|
|
130
130
|
SCORE_GAP,
|
|
131
131
|
WEIGHT_SKILL_TYPE,
|
|
132
132
|
WEIGHT_CAPABILITY_BOOST,
|
|
133
|
+
// Interview thresholds
|
|
134
|
+
DEFAULT_INTERVIEW_QUESTION_MINUTES,
|
|
135
|
+
DEFAULT_DECOMPOSITION_QUESTION_MINUTES,
|
|
136
|
+
DEFAULT_SIMULATION_QUESTION_MINUTES,
|
|
137
|
+
TOLERANCE_INTERVIEW_BUDGET_MINUTES,
|
|
138
|
+
WEIGHT_CAPABILITY_DECOMP_DELIVERY,
|
|
139
|
+
WEIGHT_CAPABILITY_DECOMP_SCALE,
|
|
140
|
+
WEIGHT_CAPABILITY_DECOMP_RELIABILITY,
|
|
141
|
+
WEIGHT_FOCUS_BOOST,
|
|
142
|
+
// Senior grade
|
|
143
|
+
THRESHOLD_SENIOR_GRADE,
|
|
144
|
+
// Assessment weights
|
|
145
|
+
WEIGHT_ASSESSMENT_SKILL_DEFAULT,
|
|
146
|
+
WEIGHT_ASSESSMENT_BEHAVIOUR_DEFAULT,
|
|
147
|
+
WEIGHT_SENIOR_BASE,
|
|
148
|
+
WEIGHT_SENIOR_EXPECTATIONS,
|
|
149
|
+
// Match limits
|
|
150
|
+
LIMIT_PRIORITY_GAPS,
|
|
151
|
+
WEIGHT_SAME_TRACK_BONUS,
|
|
152
|
+
RANGE_GRADE_OFFSET,
|
|
153
|
+
RANGE_READY_GRADE_OFFSET,
|
|
154
|
+
// Driver coverage
|
|
155
|
+
THRESHOLD_DRIVER_SKILL_LEVEL,
|
|
156
|
+
THRESHOLD_DRIVER_BEHAVIOUR_MATURITY,
|
|
157
|
+
// Agent limits
|
|
158
|
+
LIMIT_AGENT_PROFILE_SKILLS,
|
|
159
|
+
LIMIT_AGENT_WORKING_STYLES,
|
|
133
160
|
// Predicates
|
|
134
161
|
isHumanOnly,
|
|
135
162
|
isAgentEligible,
|
|
@@ -152,6 +179,7 @@ export {
|
|
|
152
179
|
ORDER_SKILL_TYPE,
|
|
153
180
|
ORDER_STAGE,
|
|
154
181
|
ORDER_AGENT_STAGE,
|
|
182
|
+
CHECKLIST_STAGE_MAP,
|
|
155
183
|
compareByLevelDesc,
|
|
156
184
|
compareByType,
|
|
157
185
|
compareBySkillPriority,
|
|
@@ -160,6 +188,7 @@ export {
|
|
|
160
188
|
// Composed policies
|
|
161
189
|
filterAgentSkills,
|
|
162
190
|
filterToolkitSkills,
|
|
191
|
+
focusAgentSkills,
|
|
163
192
|
sortAgentSkills,
|
|
164
193
|
sortAgentBehaviours,
|
|
165
194
|
prepareAgentSkillMatrix,
|
package/src/interview.js
CHANGED
|
@@ -20,22 +20,17 @@ import {
|
|
|
20
20
|
WEIGHT_SKILL_LEVEL,
|
|
21
21
|
WEIGHT_BELOW_LEVEL_PENALTY,
|
|
22
22
|
RATIO_SKILL_BEHAVIOUR,
|
|
23
|
+
DEFAULT_INTERVIEW_QUESTION_MINUTES,
|
|
24
|
+
DEFAULT_DECOMPOSITION_QUESTION_MINUTES,
|
|
25
|
+
DEFAULT_SIMULATION_QUESTION_MINUTES,
|
|
26
|
+
TOLERANCE_INTERVIEW_BUDGET_MINUTES,
|
|
27
|
+
WEIGHT_CAPABILITY_DECOMP_DELIVERY,
|
|
28
|
+
WEIGHT_CAPABILITY_DECOMP_SCALE,
|
|
29
|
+
WEIGHT_CAPABILITY_DECOMP_RELIABILITY,
|
|
30
|
+
WEIGHT_FOCUS_BOOST,
|
|
23
31
|
} from "./policies/thresholds.js";
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
* Default question time estimate if not specified
|
|
27
|
-
*/
|
|
28
|
-
const DEFAULT_QUESTION_MINUTES = 5;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Default decomposition question time estimate
|
|
32
|
-
*/
|
|
33
|
-
const DEFAULT_DECOMPOSITION_MINUTES = 15;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Default stakeholder simulation question time estimate
|
|
37
|
-
*/
|
|
38
|
-
const DEFAULT_SIMULATION_MINUTES = 20;
|
|
33
|
+
import { compareByMaturityDesc } from "./policies/orderings.js";
|
|
39
34
|
|
|
40
35
|
/**
|
|
41
36
|
* Get questions from the question bank for a specific skill and level
|
|
@@ -136,7 +131,7 @@ function calculateSkillPriority(skill, includeBelowLevel = false) {
|
|
|
136
131
|
priority += WEIGHT_CAPABILITY_BOOST.ai;
|
|
137
132
|
}
|
|
138
133
|
|
|
139
|
-
// Delivery skills
|
|
134
|
+
// Delivery skills get a core technical boost
|
|
140
135
|
if (skill.capability === Capability.DELIVERY) {
|
|
141
136
|
priority += WEIGHT_CAPABILITY_BOOST.delivery;
|
|
142
137
|
}
|
|
@@ -178,11 +173,11 @@ function calculateCapabilityPriority(capabilityId, levelIndex) {
|
|
|
178
173
|
|
|
179
174
|
// Delivery and scale capabilities are typically more important for decomposition
|
|
180
175
|
if (capabilityId === Capability.DELIVERY) {
|
|
181
|
-
priority +=
|
|
176
|
+
priority += WEIGHT_CAPABILITY_DECOMP_DELIVERY;
|
|
182
177
|
} else if (capabilityId === Capability.SCALE) {
|
|
183
|
-
priority +=
|
|
178
|
+
priority += WEIGHT_CAPABILITY_DECOMP_SCALE;
|
|
184
179
|
} else if (capabilityId === Capability.RELIABILITY) {
|
|
185
|
-
priority +=
|
|
180
|
+
priority += WEIGHT_CAPABILITY_DECOMP_RELIABILITY;
|
|
186
181
|
}
|
|
187
182
|
|
|
188
183
|
// Higher level = higher priority
|
|
@@ -339,8 +334,11 @@ export function deriveInterviewQuestions({ job, questionBank, options = {} }) {
|
|
|
339
334
|
for (const q of allSkillQuestions) {
|
|
340
335
|
if (selectedSkillIds.has(q.targetId)) continue; // Skip if we already have this skill
|
|
341
336
|
const questionTime =
|
|
342
|
-
q.question.expectedDurationMinutes ||
|
|
343
|
-
if (
|
|
337
|
+
q.question.expectedDurationMinutes || DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
338
|
+
if (
|
|
339
|
+
skillMinutes + questionTime <=
|
|
340
|
+
skillTimeBudget + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
341
|
+
) {
|
|
344
342
|
selectedQuestions.push(q);
|
|
345
343
|
selectedSkillIds.add(q.targetId);
|
|
346
344
|
coveredSkills.add(q.targetId);
|
|
@@ -352,8 +350,11 @@ export function deriveInterviewQuestions({ job, questionBank, options = {} }) {
|
|
|
352
350
|
for (const q of allSkillQuestions) {
|
|
353
351
|
if (selectedQuestions.includes(q)) continue; // Skip already selected
|
|
354
352
|
const questionTime =
|
|
355
|
-
q.question.expectedDurationMinutes ||
|
|
356
|
-
if (
|
|
353
|
+
q.question.expectedDurationMinutes || DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
354
|
+
if (
|
|
355
|
+
skillMinutes + questionTime <=
|
|
356
|
+
skillTimeBudget + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
357
|
+
) {
|
|
357
358
|
selectedQuestions.push(q);
|
|
358
359
|
coveredSkills.add(q.targetId);
|
|
359
360
|
skillMinutes += questionTime;
|
|
@@ -368,8 +369,11 @@ export function deriveInterviewQuestions({ job, questionBank, options = {} }) {
|
|
|
368
369
|
for (const q of allBehaviourQuestions) {
|
|
369
370
|
if (selectedBehaviourIds.has(q.targetId)) continue; // Skip if we already have this behaviour
|
|
370
371
|
const questionTime =
|
|
371
|
-
q.question.expectedDurationMinutes ||
|
|
372
|
-
if (
|
|
372
|
+
q.question.expectedDurationMinutes || DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
373
|
+
if (
|
|
374
|
+
behaviourMinutes + questionTime <=
|
|
375
|
+
behaviourTimeBudget + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
376
|
+
) {
|
|
373
377
|
selectedQuestions.push(q);
|
|
374
378
|
selectedBehaviourIds.add(q.targetId);
|
|
375
379
|
coveredBehaviours.add(q.targetId);
|
|
@@ -381,8 +385,11 @@ export function deriveInterviewQuestions({ job, questionBank, options = {} }) {
|
|
|
381
385
|
for (const q of allBehaviourQuestions) {
|
|
382
386
|
if (selectedQuestions.includes(q)) continue; // Skip already selected
|
|
383
387
|
const questionTime =
|
|
384
|
-
q.question.expectedDurationMinutes ||
|
|
385
|
-
if (
|
|
388
|
+
q.question.expectedDurationMinutes || DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
389
|
+
if (
|
|
390
|
+
behaviourMinutes + questionTime <=
|
|
391
|
+
behaviourTimeBudget + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
392
|
+
) {
|
|
386
393
|
selectedQuestions.push(q);
|
|
387
394
|
coveredBehaviours.add(q.targetId);
|
|
388
395
|
behaviourMinutes += questionTime;
|
|
@@ -395,7 +402,9 @@ export function deriveInterviewQuestions({ job, questionBank, options = {} }) {
|
|
|
395
402
|
// Calculate total time
|
|
396
403
|
const expectedDurationMinutes = selectedQuestions.reduce(
|
|
397
404
|
(sum, q) =>
|
|
398
|
-
sum +
|
|
405
|
+
sum +
|
|
406
|
+
(q.question.expectedDurationMinutes ||
|
|
407
|
+
DEFAULT_INTERVIEW_QUESTION_MINUTES),
|
|
399
408
|
0,
|
|
400
409
|
);
|
|
401
410
|
|
|
@@ -469,10 +478,14 @@ export function deriveShortInterview({
|
|
|
469
478
|
}
|
|
470
479
|
|
|
471
480
|
const questionTime =
|
|
472
|
-
nextQuestion.question.expectedDurationMinutes ||
|
|
481
|
+
nextQuestion.question.expectedDurationMinutes ||
|
|
482
|
+
DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
473
483
|
|
|
474
484
|
// Don't exceed budget by too much
|
|
475
|
-
if (
|
|
485
|
+
if (
|
|
486
|
+
totalMinutes + questionTime >
|
|
487
|
+
targetMinutes + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
488
|
+
) {
|
|
476
489
|
break;
|
|
477
490
|
}
|
|
478
491
|
|
|
@@ -549,7 +562,9 @@ export function deriveBehaviourQuestions({
|
|
|
549
562
|
// Calculate total time
|
|
550
563
|
const expectedDurationMinutes = interviewQuestions.reduce(
|
|
551
564
|
(sum, q) =>
|
|
552
|
-
sum +
|
|
565
|
+
sum +
|
|
566
|
+
(q.question.expectedDurationMinutes ||
|
|
567
|
+
DEFAULT_INTERVIEW_QUESTION_MINUTES),
|
|
553
568
|
0,
|
|
554
569
|
);
|
|
555
570
|
|
|
@@ -603,7 +618,7 @@ export function deriveFocusedInterview({
|
|
|
603
618
|
targetName: skill.skillName,
|
|
604
619
|
targetType: "skill",
|
|
605
620
|
targetLevel: skill.level,
|
|
606
|
-
priority: calculateSkillPriority(skill) +
|
|
621
|
+
priority: calculateSkillPriority(skill) + WEIGHT_FOCUS_BOOST,
|
|
607
622
|
});
|
|
608
623
|
coveredSkills.add(skill.skillId);
|
|
609
624
|
}
|
|
@@ -627,7 +642,7 @@ export function deriveFocusedInterview({
|
|
|
627
642
|
targetName: behaviour.behaviourName,
|
|
628
643
|
targetType: "behaviour",
|
|
629
644
|
targetLevel: behaviour.maturity,
|
|
630
|
-
priority: calculateBehaviourPriority(behaviour) +
|
|
645
|
+
priority: calculateBehaviourPriority(behaviour) + WEIGHT_FOCUS_BOOST,
|
|
631
646
|
});
|
|
632
647
|
coveredBehaviours.add(behaviour.behaviourId);
|
|
633
648
|
}
|
|
@@ -638,7 +653,9 @@ export function deriveFocusedInterview({
|
|
|
638
653
|
|
|
639
654
|
const expectedDurationMinutes = interviewQuestions.reduce(
|
|
640
655
|
(sum, q) =>
|
|
641
|
-
sum +
|
|
656
|
+
sum +
|
|
657
|
+
(q.question.expectedDurationMinutes ||
|
|
658
|
+
DEFAULT_INTERVIEW_QUESTION_MINUTES),
|
|
642
659
|
0,
|
|
643
660
|
);
|
|
644
661
|
|
|
@@ -734,8 +751,11 @@ export function deriveMissionFitInterview({
|
|
|
734
751
|
for (const q of allSkillQuestions) {
|
|
735
752
|
if (selectedSkillIds.has(q.targetId)) continue;
|
|
736
753
|
const questionTime =
|
|
737
|
-
q.question.expectedDurationMinutes ||
|
|
738
|
-
if (
|
|
754
|
+
q.question.expectedDurationMinutes || DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
755
|
+
if (
|
|
756
|
+
totalMinutes + questionTime <=
|
|
757
|
+
targetMinutes + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
758
|
+
) {
|
|
739
759
|
selectedQuestions.push(q);
|
|
740
760
|
selectedSkillIds.add(q.targetId);
|
|
741
761
|
coveredSkills.add(q.targetId);
|
|
@@ -747,8 +767,11 @@ export function deriveMissionFitInterview({
|
|
|
747
767
|
for (const q of allSkillQuestions) {
|
|
748
768
|
if (selectedQuestions.includes(q)) continue;
|
|
749
769
|
const questionTime =
|
|
750
|
-
q.question.expectedDurationMinutes ||
|
|
751
|
-
if (
|
|
770
|
+
q.question.expectedDurationMinutes || DEFAULT_INTERVIEW_QUESTION_MINUTES;
|
|
771
|
+
if (
|
|
772
|
+
totalMinutes + questionTime <=
|
|
773
|
+
targetMinutes + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
774
|
+
) {
|
|
752
775
|
selectedQuestions.push(q);
|
|
753
776
|
coveredSkills.add(q.targetId);
|
|
754
777
|
totalMinutes += questionTime;
|
|
@@ -854,8 +877,12 @@ export function deriveDecompositionInterview({
|
|
|
854
877
|
for (const q of allCapabilityQuestions) {
|
|
855
878
|
if (selectedCapabilityIds.has(q.targetId)) continue;
|
|
856
879
|
const questionTime =
|
|
857
|
-
q.question.expectedDurationMinutes ||
|
|
858
|
-
|
|
880
|
+
q.question.expectedDurationMinutes ||
|
|
881
|
+
DEFAULT_DECOMPOSITION_QUESTION_MINUTES;
|
|
882
|
+
if (
|
|
883
|
+
totalMinutes + questionTime <=
|
|
884
|
+
targetMinutes + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
885
|
+
) {
|
|
859
886
|
selectedQuestions.push(q);
|
|
860
887
|
selectedCapabilityIds.add(q.targetId);
|
|
861
888
|
coveredCapabilities.add(q.targetId);
|
|
@@ -867,8 +894,12 @@ export function deriveDecompositionInterview({
|
|
|
867
894
|
for (const q of allCapabilityQuestions) {
|
|
868
895
|
if (selectedQuestions.includes(q)) continue;
|
|
869
896
|
const questionTime =
|
|
870
|
-
q.question.expectedDurationMinutes ||
|
|
871
|
-
|
|
897
|
+
q.question.expectedDurationMinutes ||
|
|
898
|
+
DEFAULT_DECOMPOSITION_QUESTION_MINUTES;
|
|
899
|
+
if (
|
|
900
|
+
totalMinutes + questionTime <=
|
|
901
|
+
targetMinutes + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
902
|
+
) {
|
|
872
903
|
selectedQuestions.push(q);
|
|
873
904
|
coveredCapabilities.add(q.targetId);
|
|
874
905
|
totalMinutes += questionTime;
|
|
@@ -915,9 +946,7 @@ export function deriveStakeholderInterview({
|
|
|
915
946
|
|
|
916
947
|
// Sort behaviours by maturity (highest first) to prioritize the most emphasized
|
|
917
948
|
const sortedBehaviours = [...job.behaviourProfile].sort(
|
|
918
|
-
|
|
919
|
-
getBehaviourMaturityIndex(b.maturity) -
|
|
920
|
-
getBehaviourMaturityIndex(a.maturity),
|
|
949
|
+
compareByMaturityDesc,
|
|
921
950
|
);
|
|
922
951
|
|
|
923
952
|
// Select one question per behaviour, highest maturity first, within budget
|
|
@@ -940,8 +969,12 @@ export function deriveStakeholderInterview({
|
|
|
940
969
|
if (!question) continue;
|
|
941
970
|
|
|
942
971
|
const questionTime =
|
|
943
|
-
question.expectedDurationMinutes ||
|
|
944
|
-
if (
|
|
972
|
+
question.expectedDurationMinutes || DEFAULT_SIMULATION_QUESTION_MINUTES;
|
|
973
|
+
if (
|
|
974
|
+
totalMinutes + questionTime >
|
|
975
|
+
targetMinutes + TOLERANCE_INTERVIEW_BUDGET_MINUTES
|
|
976
|
+
)
|
|
977
|
+
break;
|
|
945
978
|
|
|
946
979
|
selectedQuestions.push({
|
|
947
980
|
question,
|
package/src/matching.js
CHANGED
|
@@ -25,6 +25,14 @@ import {
|
|
|
25
25
|
WEIGHT_DEV_TYPE_SECONDARY,
|
|
26
26
|
WEIGHT_DEV_TYPE_BROAD,
|
|
27
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,
|
|
28
36
|
} from "./policies/thresholds.js";
|
|
29
37
|
|
|
30
38
|
// ============================================================================
|
|
@@ -285,8 +293,12 @@ function calculateExpectationsScore(selfExpectations, jobExpectations) {
|
|
|
285
293
|
*/
|
|
286
294
|
export function calculateJobMatch(selfAssessment, job) {
|
|
287
295
|
// Get weights from track or use defaults (track may be null for trackless jobs)
|
|
288
|
-
const skillWeight =
|
|
289
|
-
|
|
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;
|
|
290
302
|
|
|
291
303
|
// Calculate skill score
|
|
292
304
|
const skillResult = calculateSkillScore(
|
|
@@ -312,7 +324,9 @@ export function calculateJobMatch(selfAssessment, job) {
|
|
|
312
324
|
job.expectations,
|
|
313
325
|
);
|
|
314
326
|
// Add up to 10% bonus for expectations match
|
|
315
|
-
overallScore =
|
|
327
|
+
overallScore =
|
|
328
|
+
overallScore * WEIGHT_SENIOR_BASE +
|
|
329
|
+
expectationsScore * WEIGHT_SENIOR_EXPECTATIONS;
|
|
316
330
|
}
|
|
317
331
|
|
|
318
332
|
// Combine all gaps
|
|
@@ -324,8 +338,8 @@ export function calculateJobMatch(selfAssessment, job) {
|
|
|
324
338
|
// Classify match into tier
|
|
325
339
|
const tier = classifyMatch(overallScore);
|
|
326
340
|
|
|
327
|
-
// Identify top priority gaps
|
|
328
|
-
const priorityGaps = allGaps.slice(0,
|
|
341
|
+
// Identify top priority gaps
|
|
342
|
+
const priorityGaps = allGaps.slice(0, LIMIT_PRIORITY_GAPS);
|
|
329
343
|
|
|
330
344
|
const result = {
|
|
331
345
|
overallScore,
|
|
@@ -544,11 +558,11 @@ export function findRealisticMatches({
|
|
|
544
558
|
skills,
|
|
545
559
|
});
|
|
546
560
|
|
|
547
|
-
// Determine grade range (±
|
|
561
|
+
// Determine grade range (±RANGE_GRADE_OFFSET levels)
|
|
548
562
|
const bestFitLevel = estimatedGrade.grade.ordinalRank;
|
|
549
563
|
const gradeRange = {
|
|
550
|
-
min: bestFitLevel -
|
|
551
|
-
max: bestFitLevel +
|
|
564
|
+
min: bestFitLevel - RANGE_GRADE_OFFSET,
|
|
565
|
+
max: bestFitLevel + RANGE_GRADE_OFFSET,
|
|
552
566
|
};
|
|
553
567
|
|
|
554
568
|
// Find all matches
|
|
@@ -608,11 +622,11 @@ export function findRealisticMatches({
|
|
|
608
622
|
}
|
|
609
623
|
|
|
610
624
|
// Filter each tier to only show grades within reasonable range of highest match
|
|
611
|
-
// For Strong/Good matches: show up to
|
|
625
|
+
// For Strong/Good matches: show up to RANGE_READY_GRADE_OFFSET levels below highest match
|
|
612
626
|
// For Stretch/Aspirational: show only at or above highest match (growth opportunities)
|
|
613
627
|
if (highestMatchedLevel > 0) {
|
|
614
|
-
const minLevelForReady = highestMatchedLevel -
|
|
615
|
-
const minLevelForStretch = highestMatchedLevel;
|
|
628
|
+
const minLevelForReady = highestMatchedLevel - RANGE_READY_GRADE_OFFSET;
|
|
629
|
+
const minLevelForStretch = highestMatchedLevel;
|
|
616
630
|
|
|
617
631
|
matchesByTier[1] = matchesByTier[1].filter(
|
|
618
632
|
(m) => m.job.grade.ordinalRank >= minLevelForReady,
|
|
@@ -799,8 +813,8 @@ export function findNextStepJob({
|
|
|
799
813
|
|
|
800
814
|
if (job) {
|
|
801
815
|
const analysis = calculateJobMatch(selfAssessment, job);
|
|
802
|
-
|
|
803
|
-
|
|
816
|
+
const trackBonus =
|
|
817
|
+
track.id === currentJob.track.id ? WEIGHT_SAME_TRACK_BONUS : 0;
|
|
804
818
|
candidates.push({
|
|
805
819
|
job,
|
|
806
820
|
analysis,
|
package/src/policies/composed.js
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
compareByLevelDesc,
|
|
14
14
|
compareByMaturityDesc,
|
|
15
15
|
compareByTypeAndName,
|
|
16
|
+
compareBySkillPriority,
|
|
16
17
|
} from "./orderings.js";
|
|
18
|
+
import { LIMIT_AGENT_PROFILE_SKILLS } from "./thresholds.js";
|
|
17
19
|
|
|
18
20
|
// =============================================================================
|
|
19
21
|
// Agent Skill Policies
|
|
@@ -78,6 +80,28 @@ export function sortJobSkills(skills) {
|
|
|
78
80
|
return [...skills].sort(compareByTypeAndName);
|
|
79
81
|
}
|
|
80
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
|
+
|
|
81
105
|
// =============================================================================
|
|
82
106
|
// Combined Filter + Sort Policies
|
|
83
107
|
// =============================================================================
|
package/src/policies/index.js
CHANGED
|
@@ -45,6 +45,35 @@ export {
|
|
|
45
45
|
WEIGHT_DEV_TYPE_SECONDARY,
|
|
46
46
|
WEIGHT_DEV_TYPE_BROAD,
|
|
47
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,
|
|
48
77
|
} from "./thresholds.js";
|
|
49
78
|
|
|
50
79
|
// Predicates
|
|
@@ -90,6 +119,7 @@ export {
|
|
|
90
119
|
ORDER_SKILL_TYPE,
|
|
91
120
|
ORDER_STAGE,
|
|
92
121
|
ORDER_AGENT_STAGE,
|
|
122
|
+
CHECKLIST_STAGE_MAP,
|
|
93
123
|
// Skill comparators
|
|
94
124
|
compareByLevelDesc,
|
|
95
125
|
compareByLevelAsc,
|
|
@@ -118,6 +148,8 @@ export {
|
|
|
118
148
|
// Agent skill filtering
|
|
119
149
|
filterAgentSkills,
|
|
120
150
|
filterToolkitSkills,
|
|
151
|
+
// Agent profile focus
|
|
152
|
+
focusAgentSkills,
|
|
121
153
|
// Sorting
|
|
122
154
|
sortAgentSkills,
|
|
123
155
|
sortAgentBehaviours,
|
|
@@ -34,6 +34,18 @@ export const ORDER_STAGE = ["specify", "plan", "code", "review", "deploy"];
|
|
|
34
34
|
*/
|
|
35
35
|
export const ORDER_AGENT_STAGE = ["plan", "code", "review"];
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Stage-to-handoff mapping for checklist derivation
|
|
39
|
+
*
|
|
40
|
+
* Maps stage IDs to the stage whose `.ready` criteria should be shown
|
|
41
|
+
* before leaving that stage.
|
|
42
|
+
*/
|
|
43
|
+
export const CHECKLIST_STAGE_MAP = {
|
|
44
|
+
plan: "plan",
|
|
45
|
+
code: "code",
|
|
46
|
+
review: "review",
|
|
47
|
+
};
|
|
48
|
+
|
|
37
49
|
// =============================================================================
|
|
38
50
|
// Skill Comparators
|
|
39
51
|
// =============================================================================
|
|
@@ -158,3 +158,160 @@ export const WEIGHT_DEV_TYPE_BROAD = 1;
|
|
|
158
158
|
* AI skills get extra emphasis in development planning.
|
|
159
159
|
*/
|
|
160
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
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
* Shared functions for deriving skill and behaviour profiles for both
|
|
5
5
|
* human jobs and AI agents.
|
|
6
6
|
*
|
|
7
|
-
* - prepareBaseProfile() -
|
|
8
|
-
* - prepareAgentProfile() -
|
|
7
|
+
* - prepareBaseProfile() - core derivation (skills, behaviours, responsibilities)
|
|
8
|
+
* - prepareAgentProfile() - agent-specific derivation using composed policies
|
|
9
9
|
*
|
|
10
|
-
* @see policies/
|
|
11
|
-
* @see policies/filters.js - Matrix-level filter functions
|
|
12
|
-
* @see policies/orderings.js - Comparator functions
|
|
13
|
-
* @see policies/composed.js - Composed policies
|
|
10
|
+
* @see policies/composed.js - Agent filtering and sorting policies
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
import {
|
|
@@ -20,11 +17,9 @@ import {
|
|
|
20
17
|
} from "./derivation.js";
|
|
21
18
|
|
|
22
19
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
compareByMaturityDesc,
|
|
27
|
-
} from "./policies/index.js";
|
|
20
|
+
prepareAgentSkillMatrix,
|
|
21
|
+
prepareAgentBehaviourProfile,
|
|
22
|
+
} from "./policies/composed.js";
|
|
28
23
|
|
|
29
24
|
// =============================================================================
|
|
30
25
|
// Utility Functions
|
|
@@ -47,14 +42,6 @@ export function getPositiveTrackCapabilities(track) {
|
|
|
47
42
|
// Profile Derivation
|
|
48
43
|
// =============================================================================
|
|
49
44
|
|
|
50
|
-
/**
|
|
51
|
-
* @typedef {Object} ProfileOptions
|
|
52
|
-
* @property {boolean} [excludeHumanOnly=false] - Filter out human-only skills
|
|
53
|
-
* @property {boolean} [keepHighestLevelOnly=false] - Keep only skills at the highest derived level
|
|
54
|
-
* @property {boolean} [sortByLevel=false] - Sort skills by level descending
|
|
55
|
-
* @property {boolean} [sortByMaturity=false] - Sort behaviours by maturity descending
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
45
|
/**
|
|
59
46
|
* @typedef {Object} BaseProfile
|
|
60
47
|
* @property {Array} skillMatrix - Derived skill matrix
|
|
@@ -66,13 +53,14 @@ export function getPositiveTrackCapabilities(track) {
|
|
|
66
53
|
*/
|
|
67
54
|
|
|
68
55
|
/**
|
|
69
|
-
* Prepare a base profile
|
|
56
|
+
* Prepare a base profile with raw derivation
|
|
70
57
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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:
|
|
73
61
|
*
|
|
74
|
-
* - Human jobs:
|
|
75
|
-
* - AI agents:
|
|
62
|
+
* - Human jobs: use raw output directly (sorted by type in derivation)
|
|
63
|
+
* - AI agents: use prepareAgentProfile() which applies composed policies
|
|
76
64
|
*
|
|
77
65
|
* @param {Object} params
|
|
78
66
|
* @param {Object} params.discipline - The discipline
|
|
@@ -81,7 +69,6 @@ export function getPositiveTrackCapabilities(track) {
|
|
|
81
69
|
* @param {Array} params.skills - All available skills
|
|
82
70
|
* @param {Array} params.behaviours - All available behaviours
|
|
83
71
|
* @param {Array} [params.capabilities] - Optional capabilities for responsibility derivation
|
|
84
|
-
* @param {ProfileOptions} [params.options={}] - Filtering and sorting options
|
|
85
72
|
* @returns {BaseProfile} The prepared profile
|
|
86
73
|
*/
|
|
87
74
|
export function prepareBaseProfile({
|
|
@@ -91,40 +78,16 @@ export function prepareBaseProfile({
|
|
|
91
78
|
skills,
|
|
92
79
|
behaviours,
|
|
93
80
|
capabilities,
|
|
94
|
-
options = {},
|
|
95
81
|
}) {
|
|
96
|
-
const {
|
|
97
|
-
excludeHumanOnly = false,
|
|
98
|
-
keepHighestLevelOnly = false,
|
|
99
|
-
sortByLevel = false,
|
|
100
|
-
sortByMaturity = false,
|
|
101
|
-
} = options;
|
|
102
|
-
|
|
103
82
|
// Core derivation
|
|
104
|
-
|
|
105
|
-
|
|
83
|
+
const skillMatrix = deriveSkillMatrix({ discipline, grade, track, skills });
|
|
84
|
+
const behaviourProfile = deriveBehaviourProfile({
|
|
106
85
|
discipline,
|
|
107
86
|
grade,
|
|
108
87
|
track,
|
|
109
88
|
behaviours,
|
|
110
89
|
});
|
|
111
90
|
|
|
112
|
-
// Apply skill filters using policy functions
|
|
113
|
-
if (excludeHumanOnly) {
|
|
114
|
-
skillMatrix = skillMatrix.filter(isAgentEligible);
|
|
115
|
-
}
|
|
116
|
-
if (keepHighestLevelOnly) {
|
|
117
|
-
skillMatrix = filterHighestLevel(skillMatrix);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Apply sorting using policy comparators
|
|
121
|
-
if (sortByLevel) {
|
|
122
|
-
skillMatrix = [...skillMatrix].sort(compareByLevelDesc);
|
|
123
|
-
}
|
|
124
|
-
if (sortByMaturity) {
|
|
125
|
-
behaviourProfile = [...behaviourProfile].sort(compareByMaturityDesc);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
91
|
// Derive responsibilities if capabilities provided
|
|
129
92
|
let derivedResponsibilities = [];
|
|
130
93
|
if (capabilities && capabilities.length > 0) {
|
|
@@ -148,14 +111,14 @@ export function prepareBaseProfile({
|
|
|
148
111
|
/**
|
|
149
112
|
* Prepare a profile optimized for agent generation
|
|
150
113
|
*
|
|
151
|
-
* Applies agent-specific
|
|
114
|
+
* Applies agent-specific policies from composed.js:
|
|
152
115
|
* - Excludes human-only skills
|
|
153
116
|
* - Keeps only skills at the highest derived level
|
|
154
117
|
* - Sorts skills by level descending
|
|
155
118
|
* - Sorts behaviours by maturity descending
|
|
156
119
|
*
|
|
157
|
-
* @param {Object} params - Same as prepareBaseProfile
|
|
158
|
-
* @returns {BaseProfile} The prepared profile
|
|
120
|
+
* @param {Object} params - Same as prepareBaseProfile
|
|
121
|
+
* @returns {BaseProfile} The prepared profile with agent policies applied
|
|
159
122
|
*/
|
|
160
123
|
export function prepareAgentProfile({
|
|
161
124
|
discipline,
|
|
@@ -165,18 +128,18 @@ export function prepareAgentProfile({
|
|
|
165
128
|
behaviours,
|
|
166
129
|
capabilities,
|
|
167
130
|
}) {
|
|
168
|
-
|
|
131
|
+
const base = prepareBaseProfile({
|
|
169
132
|
discipline,
|
|
170
133
|
track,
|
|
171
134
|
grade,
|
|
172
135
|
skills,
|
|
173
136
|
behaviours,
|
|
174
137
|
capabilities,
|
|
175
|
-
options: {
|
|
176
|
-
excludeHumanOnly: true,
|
|
177
|
-
keepHighestLevelOnly: true,
|
|
178
|
-
sortByLevel: true,
|
|
179
|
-
sortByMaturity: true,
|
|
180
|
-
},
|
|
181
138
|
});
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...base,
|
|
142
|
+
skillMatrix: prepareAgentSkillMatrix(base.skillMatrix),
|
|
143
|
+
behaviourProfile: prepareAgentBehaviourProfile(base.behaviourProfile),
|
|
144
|
+
};
|
|
182
145
|
}
|