Six engine stages (entry gates, Layer 1 rubric, rules overlay, adjacency, Layer 2 LLM, verdict) each contribute differently to the final candidate verdict. This table shows what each stage produces and how it feeds the next. Renumbered 2026-06-12: the old "Step 5.3 industry filter" is split into 6.0 (drop gates, before L1) and 6.2 (boost/penalty + L2 flags, after L1).
| Phase | Components | Purpose | Score weightage | How it contributes to the final composite / verdict |
|---|---|---|---|---|
| Entry gates (Step 6.0) |
sub_function_match gate (JD vs CV sub-function).Ratified drop-type hard gates from the rules config (mined ex-5.3 content). |
Eliminate non-starters before any scoring or rubric load. Is the CV even applying to the right role? |
None — absolute gates, pass/drop. Not part of any composite. | Sub-function mismatch → drop with reason sub_function_mismatch (adjacency may salvage).Any drop rule fires → eliminated with rule id as reason.Survivors proceed to 6.1 scoring. |
| Layer 1 (Step 6.1) |
4 deterministic dimensions: tag_overlap, seniority_delta, years_experience, red_flag_penalty.Weights load per (sub_function, sub_vertical) cell.Weights sum to 1.0. |
Cheap quantitative filter. Drops obvious misses before any LLM cost. |
tag_overlap 0.40seniority_delta 0.20years_experience 0.20red_flag_penalty 0.20Dan tunes per sub-vertical at ratification. |
Each dim scored 0–5 deterministically. L1 composite = Σ (dim_score × weight) → 0–5. Cutoff: ≥ 4.0 applied after the 6.2 overlay. |
| Rules overlay (Step 6.2) |
L2 flags (block_at_l2 / require_evidence).Boost-penalty rules (±delta on composite). ( drop gates moved to 6.0.) |
Recruiter heuristics layered on top of L1. Catches geography, employer, language, certs that the rubric math can't. |
No weights — boost/penalty deltas sum, then clip to net ±0.5 on the L1 composite (locked 2026-06-12). By design a nudge for borderline candidates, never strong enough to overturn the rubric. |
block_at_l2 → flagged but continues.require_evidence → L2 must find quote for specified dim or that dim collapses to 1.boost-penalty → net-capped ±0.5 on L1 composite; then threshold re-check ≥ 4.0. |
| Step 7.1 Adjacency | LLM proposes adjacent skill matches (LPG → LNG). Ratified config + live inference hybrid. |
Expands positive-signal set before L2 fires. Catches cross-sub-vertical fit (Tanker → LNG cargo). |
Per-adjacency weight 0–1 (e.g. Tanker→LNG = 0.8). Recomputes L1 tag_overlap: weight × 5 replaces the 0 the missed tag would score. |
Adjacency evidence feeds L2 prompt. Not a separate composite score — informs L2's skills_adjacency dim. |
| Layer 2 (Step 7.2) |
5 LLM-scored dims: experience_alignment, skills_adjacency, technical_knowledge, tools_and_systems, education_certifications.Plus 2 list axes: red_flags + strengths.Plus 1 narrative: executive_summary. |
Qualitative deep assessment with verbatim CV quotes. Primary hallucination defence: every dim score must cite a quote. |
Per-sub-function weights (config/rubric-weights.json), SEPARATE from L1 rubric weights.Numeric defaults not locked yet — design intent: cargo-ops raises technical + tools; finance-fo raises education + certs. |
Each scored dim 1–5 + quotes. L2 composite = Σ (L2 dim × per-sub-function L2 weight) → 0–5. L2 weights are SEPARATE from L1 rubric weights. |
| Step 8.1 Layer 3 verdict | Threshold matching across L1 composite + L2 composite + red_flag severity. | Final verdict band. Both composites must clear thresholds independently — not summed. |
No weights — fixed thresholds per composite (L1: 4.5 / 4.0; L2: 4.2 / 3.5 / 2.5) + red-flag severity gate. | Strong yes: L1 ≥ 4.5 AND L2 ≥ 4.2 AND no red flag ≥ 4 severity. Soft yes: L1 ≥ 4.0 AND L2 ≥ 3.5. Soft no: L1 ≥ 4.0 AND L2 < 3.5. Strong no: L1 dropped OR L2 < 2.5. |
Every action in the matching pipeline, sequenced by phase (prep → entry gates 6.0 → Layer 1 6.1 → rules overlay 6.2 → adjacency 7.1 → Layer 2 7.2 → verdict 8.1). Each row lists what consumes the output downstream and the open tasks to ship it.
| # | Step | Action | Input | Output | Used where | Plan(s) | Tasks needed to ship |
|---|---|---|---|---|---|---|---|
| PHASE A — pre-matching prep | |||||||
| 1 | 1.1 | JD ingested. | Raw JD file (PDF/DOCX). | Raw text. | Consumed by Step 2 parser only. Not used downstream. |
matching-engine + matching-engine-impl. | Decide code location (reuse cv-parser/extractor.ts or clone to jd-parser/extractor.ts).Document shared dependency or extract to lib/extractor/.Write extraction tests against 41-JD corpus. |
| 2 | 2.1 | JD parsed. | Raw JD text. | ParsedJD: must_have_skills, technical_knowledge, tools_and_systems, certifications, education, years_floor, seniority_floor, experience_description. |
L1: years_floor → years_experience dim.L1: seniority_floor → seniority_delta dim.L2: technical_knowledge → technical_knowledge dim.L2: tools_and_systems → tools_and_systems dim.L2: certifications + education → education_certifications dim.L2: experience_description → experience_alignment dim.6.0/6.2 rules: hsse_compliance + shift_or_24_7 → hard-gate triggers. |
matching-engine + matching-engine-impl. | Build jd-parser/types.ts (ParsedJD interface).Build jd-parser/parser.ts (Claude tool-use call).Build jd-parser/index.ts (public API).Build scripts/batch-parse-jds.ts.Write parser tests against fixture JDs. |
| 3 | 3.2 | JD normalised. | ParsedJD + locked tag vocab. |
NormalisedJD: sub_function, sub_vertical, tags[], seniority_tier, must/nice tags. |
Rubric load (6.1): sub_function + sub_vertical → picks 22-cell rubric.6.0 gate: sub_function → sub-function match gate.L1: tags[] + must/nice → tag_overlap dim.L1: seniority_tier → seniority_delta dim.6.0/6.2 rules: sub_function + sub_vertical → rule scope matching.L2: entire NormalisedJD passed to LLM as prompt context. |
matching-engine + taxonomy-3level. | Wait for 3.1 vocab lock BLOCKED — Dan + Jon ratification session pending. Amend impl plan Tasks 4–6 with locked vocab + 3-level taxonomy. Build jd-normaliser/normaliser.ts.Build jd-normaliser/taxonomy-extension.ts (finance-fo + ops-back-office).Test all 41 JDs round-trip. |
| 4 | 5.1 | CV ingested + parsed. | Raw CV file. | ParsedCV: work history, skills, education, certifications, years, employer history, location. |
L1: total years → years_experience dim.L2: work history → experience_alignment + technical_knowledge + tools_and_systems quote source.L2: education + certifications → education_certifications dim.L2: full text → red_flags + strengths list candidates.5.3: employer history → cv_employer_in / cv_employer_matches predicates.5.3: location → cv_location_not_in predicate.5.3: certifications → cv_missing_certification predicate. |
5.1.1-cv-parser + 5.1.1-cv-parser-impl. | Re-batch over full 107-CV set (queued). OCR pass for Chloe Wong (Market Risk, Metals).pdf. |
| 5 | 5.1.2 | CV hardening. | ParsedCV. |
Red-flag signals: career_gaps, short_tenure_clusters, stale_cv, role_downgrades. |
L1: all signals → red_flag_penalty dim.6.0/6.2 rules: short_tenure_clusters → cv_consecutive_short_tenures predicate.L2: role_downgrades → red_flags list candidates. |
5.1.2-cv-parser-hardening + 5.1.2-cv-parser-hardening-impl. | No open tasks (shipped). |
| 6 | 5.2 | CV normalised. | ParsedCV + locked tag vocab. |
NormalisedCV: sub_function, tags[], inferred seniority, language. |
6.0 gate: sub_function → sub-function match gate.L1: tags[] → tag_overlap dim.L1: inferred seniority → seniority_delta dim.6.0/6.2 rules: sub_function → rule scope matching.6.0/6.2 rules: tags[] → cv_tag_present predicate.5.3: language → cv_missing_language predicate.L2: entire NormalisedCV passed to LLM as prompt context. |
taxonomy-3level (schema). | Wait for 3.1 vocab lock BLOCKED. Write impl plan for cv-normaliser/.Build cv-normaliser/normaliser.ts.Test all 107 CVs round-trip. |
| PHASE B — entry gates (Step 6.0) | |||||||
| 7 | 6.0 | Sub-function hard gate. | JD.sub_function + CV.sub_function. |
Pass if equal; else drop. | Mismatch → drop with reason sub_function_mismatch, before any scoring or rubric load.Adjacency may salvage. Not weighted — removed from the L1 composite. |
matching-engine + matching-engine-impl. | Build gate check in matching/entry-gates.ts.Wire adjacency salvage path. |
| 8 | 6.0 | Ratified drop gates fire. | NormalisedJD + NormalisedCV + ratified drop-type rules. |
drop per fired rule. |
Fires BEFORE L1 scoring (moved 2026-06-12 — was post-L1 as part of old 5.3).drop → candidate eliminated with rule id as reason.Survivors proceed to 6.1. |
5.3-industry-filter + bootstrap-rubric-ext (content). | Get hard-rules content from bootstrap mine DONE — xlsx awaiting Dan + Jon ratification. Re-split xlsx hard-gate sheet: drop rules → 6.0 config; block_at_l2 / require_evidence → 6.2 config.Run Dan + Jon scaffold-review session (pending since 2026-05-25). Build matching/entry-gates.ts rule evaluation. |
| PHASE C — Layer 1 (Step 6.1) | |||||||
| 9 | 6.1 | Load rubric for JD's cell. | NormalisedJD.sub_function + sub_vertical. |
4 dimension weights (sum to 1.0) + must/nice signals + disqualifying signals. | Drives the 4 dimension scorings + composite below. | matching-engine (formulas) + bootstrap-rubric-ext (content). | Get rubric content from bootstrap mine — 22 sub-vertical configs DONE — xlsx awaiting Dan + Jon ratification. Re-emit xlsx weights for 4-dim scheme (sub_function_match moved to hard gate) before ratification. Delete 4 wrong-taxonomy stubs in reference/rubric-configs/ (done).Lock red-flag input source. Amend impl plan Tasks 9–10 to key on (sub_function, sub_vertical). |
| 10 | 6.1 | Score tag_overlap. |
JD.tags[] + CV.tags[]. |
(overlap count ÷ JD required count) × 5. | L1 composite via 0.40 default weight. | same as 9. | Build matching/layer1-hard-fit.ts.Build config/rubric-weights.json (per-sub-vertical weights).Test composite scoring across 107 × 41 = 4,387 pairs. |
| 11 | 6.1 | Score seniority_delta. |
JD.seniority_tier + CV.inferred_seniority. |
0–5 by categorical match. | L1 composite via 0.20 default weight. | same as 9. | same as 10. |
| 12 | 6.1 | Score years_experience. |
JD.years_floor + CV.total_years. |
0–5 numeric. | L1 composite via 0.20 default weight. | same as 9. | same as 10. |
| 13 | 6.1 | Score red_flag_penalty. |
Step 5.1.2 hardening signals. | 5 if clean, lower with flags present. | L1 composite via 0.20 default weight. Counterintuitive: high score = no penalty applied. |
same as 9. | same as 10. |
| 14 | 6.1 | Compute L1 composite. | All 4 dim scores + rubric weights. | Composite 0–5. | Σ (score × weight). Cutoff 4.0 applied at next stage (after the 6.2 overlay). |
same as 9. | same as 10. |
| PHASE D — rules overlay (Step 6.2) | |||||||
| 15 | 6.2 | Boost-penalty fire. | NormalisedJD + NormalisedCV + L1 composite + ratified boost-penalty rules. |
Net ±delta on L1 composite, clipped to ±0.5. | Sum all firing deltas → clip to net ±0.5 → adjusted composite. Cap locked 2026-06-12: rules are a nudge for borderline candidates, never strong enough to overturn the 4-dim rubric. |
5.3-industry-filter + bootstrap-rubric-ext (content). | Get boost-penalty content from bootstrap mine DONE — xlsx awaiting Dan + Jon ratification. Update mined xlsx delta values for the ±0.5 net cap (was ±0.3–1.5) before ratification. Build matching/rules-overlay.ts (6.2 module) with net-cap enforcement.Wire threshold re-check after overlay. |
| 16 | 6.2 | Attach L2 flags. | NormalisedJD + NormalisedCV + ratified block_at_l2 / require_evidence rules. |
Flags attached to the match record. | block_at_l2 → flagged but candidate continues to L2.require_evidence → L2 must find quote for specified dim or that dim collapses to 1 (enforced at the L2 evidence check). |
same as 15. | same as 15. |
| 17 | 6.2 | Threshold re-check. | Adjusted L1 composite. | Pass if ≥ 4.0. | Below 4.0 → drop with reason composite_below_4_after_rules. |
same as 15. | same as 15. |
| PHASE E — adjacency (Step 7.1) | |||||||
| 18 | 7.1 | Adjacency resolver. | NormalisedJD + NormalisedCV + ratified adjacency config. |
Expanded match evidence + proposed adjacencies. | Catches LPG→LNG style cross-skill fit. Proposed adjacencies need ratification to count. Output feeds L2 prompt as adjacency evidence. |
matching-engine. | Build matching/adjacency/config.ts (ratified adjacency loader).Build matching/adjacency/resolver.ts (two-tier resolution).Build matching/adjacency/live-inference.ts (Claude API call).Build config/adjacency-ratified.json (seeded from bootstrap mine).Test adjacency proposals against CV corpus. |
| PHASE F — Layer 2 (Step 7.2) | |||||||
| 19 | 7.2 | Single Claude API call. | NormalisedJD + ParsedCV (full text) + adjacency evidence. |
Layer2Result struct. |
Only fires on Phase D survivors (~5–10% of pairs). | matching-engine. | Build matching/layer2-deep-assessment.ts.Define prompt: read Step 6 rubric dimensions, find quotes per dimension. Define per-dimension 1–5 scoring anchors in the L2 prompt (currently undefined). Enforce verbatim quote citing. Run only on Step 6 survivors to cap LLM cost. Amend impl plan to collapse old 5.3 (deleted) + 7.2 into single LLM call. |
| 20 | 7.2 | Score experience_alignment. |
JD.experience_description + CV work history. |
1–5 + evidence quotes. | L2 composite weighting. | same as 19. | same as 19. |
| 21 | 7.2 | Score skills_adjacency. |
Adjacency evidence (from 18) + JD requirements. | 1–5 + evidence quotes OR null. | L2 composite weighting (null if adjacency didn't fire). | same as 19. | same as 19. |
| 22 | 7.2 | Score technical_knowledge. |
JD.technical_knowledge field + CV narrative. |
1–5 + quotes. | L2 composite weighting. | same as 19. | same as 19. |
| 23 | 7.2 | Score tools_and_systems. |
JD.tools_and_systems field + CV narrative. |
1–5 + quotes. | L2 composite weighting. | same as 19. | same as 19. |
| 24 | 7.2 | Score education_certifications. |
JD edu + cert fields + CV edu + cert fields. | 1–5 + quotes. | L2 composite weighting. NOT a hard gate. Hard cert requirements live in the 6.0/6.2 rules config. |
same as 19. | same as 19. |
| 25 | 7.2 | Generate red_flags list. |
Full CV narrative + JD context. | List of { signal, evidence, severity 1–5 }. | Feeds Step 8.1 verdict (Strong yes requires no red flag ≥ 4 severity). Includes subjective patterns parser hardening missed. |
same as 19. | same as 19. |
| 26 | 7.2 | Generate strengths list. |
Full CV narrative + JD context. | List of { signal, evidence }. | Feeds executive_summary + recruiter UI. |
same as 19. | same as 19. |
| 27 | 7.2 | Generate executive_summary. |
All of the above. | 2–3 sentence narrative. | Recruiter-facing summary. Not scored. |
same as 19. | same as 19. |
| 28 | 7.2 | Evidence requirement check. | Per-dimension quotes. | Any dim without quote → force score 1 + low_evidence flag. |
Primary hallucination defence. | same as 19. | same as 19. |
| 29 | 7.2 | Compute L2 composite. | 5 scored dims × per-sub-function L2 weights. | composite_l2_score 0–5. |
L2 weights are SEPARATE from L1 rubric weights. Configured per sub_function, not per cell. |
same as 19. | same as 19. |
| PHASE G — verdict synthesis (Step 8.1) | |||||||
| 30 | 8.1 | Strong yes check. | L1 ≥ 4.5 AND L2 ≥ 4.2 AND no red flag ≥ 4 severity. | Verdict band: Strong yes. | All three conditions must hold. | matching-engine + matching-engine-impl. | Build matching/layer3-verdict.ts.Wire to recruiter override UI (Step 10 surface). Log overrides with reason → feeds back to rubric weight tuning. Decide: Soft no (3.5–3.99) → skip Step 7 LLM, or run it? Update design doc with locked score bands. |
| 31 | 8.1 | Soft yes check. | L1 ≥ 4.0 AND L2 ≥ 3.5. | Verdict band: Soft yes. | If Strong yes failed. | same as 30. | same as 30. |
| 32 | 8.1 | Soft no check. | L1 ≥ 4.0 AND L2 < 3.5. | Verdict band: Soft no. | L1 passed but L2 evidence weak. | same as 30. | same as 30. |
| 33 | 8.1 | Strong no check. | L1 dropped OR L2 < 2.5. | Verdict band: Strong no. | Catch-all for failures. | same as 30. | same as 30. |
| 34 | 10.x | Recruiter override. | Recruiter UI input. | Verdict overridden + reason logged. | Optional. Feeds rubric weight tuning loop. |
Step 10 productisation surface (not yet planned). | TBD — Step 10 design pending. |
The 19-sub-step phase inventory across Steps 1–9. Each sub-step lists the plan(s) covering it and the open tasks needed to ship it. Statuses honour the 2026-06-11 D5 mine completion.
cv-parser/extractor.ts or clone to jd-parser/extractor.tslib/extractor/ firstParsedJD (role, responsibilities, requirements, seniority, must/nice tags).jd-parser/types.ts — ParsedJD interface (impl plan Task 1)jd-parser/parser.ts — Claude tool-use call (Task 2)jd-parser/index.ts — public API parseJD(file) (Task 3)scripts/batch-parse-jds.ts — parse all 41 JDs in reference/JD/plans/amendment-3.1+5.3+6.1-bootstrap-rubric-extension.md — combined amendment plansrc/lib/vocab-bootstrap/ to mine 3 artefactsParsedJD → NormalisedJD (sub-function, sub-vertical, tag list, seniority tier, must/nice-to-have tags).jd-normaliser/normaliser.ts — Claude tool-use calljd-normaliser/taxonomy-extension.ts — sub-functions finance-fo + ops-back-officesourcing/apify-client.tsNormalisedJD → search-query constructionsourcing/apollo-client.tssourcing/email-finder.tssourcing/bd-scraper.tsCandidateRecord. Without this, Step 5.1.1 only knows how to parse file blobs — scraped JSON has no defined ingestion path. Design: plans/step-5.0-candidate-merge-design.md.step-5.0-candidate-merge-impl.mdcalibre-app/src/lib/candidate-merge/ParsedCV structured fields. 66+ CVs batch-tested.Chloe Wong (Market Risk, Metals).pdf — only known unreadablefeat/parser-hardeningParsedCV → NormalisedCV using same vocab as 3.2 — symmetric tagging.cv-normaliser/cv-normaliser/normaliser.tspipeline-1.1+2.1+3.2+6.1+7.1+7.2+8.1-matching-engine-impl.md — collapse 5.3 tasks into 7.2drop-type rules from the mined ruleset (ex-5.3). Eliminates non-starters with rule id as reason before any rubric math runs.drop → 6.0 config; block_at_l2 / require_evidence → 6.2 configmatching/entry-gates.ts — sub-function gate + drop-rule evaluationreference/rubric-configs/(sub-function, sub-vertical)matching/layer1-hard-fit.tsconfig/rubric-weights.json — per-sub-vertical weightsblock_at_l2 / require_evidence flag attachment for L2, then threshold re-check ≥ 4.0.matching/rules-overlay.ts — boost/penalty + net-cap enforcement + flag attachmentcomposite_below_4_after_rulesmatching/adjacency/config.ts — ratified adjacency loadermatching/adjacency/resolver.ts — two-tier resolution (config → live inference)matching/adjacency/live-inference.ts — Claude API call for proposalsconfig/adjacency-ratified.json — seeded from bootstrap minematching/layer2-deep-assessment.ts4.5+ Strong yes / 4.0–4.49 Soft yes / 3.5–3.99 Soft no / <3.5 Strong no.matching/layer3-verdict.tsoutreach/personalisation.tsoutreach/email-send.ts — delivery + trackingoutreach/linkedin-sequence.tsoutreach/response-handler.tsdrop gates → 6.0 (before L1), boost/penalty + block_at_l2 / require_evidence → 6.2 (after L1). Boost/penalty net-capped at ±0.5. Step ID 5.3 retired.step-5.0-candidate-merge-design → write impl plan.pipeline-9-outreach-design → write impl plan.amendment-3.2+5.2+6.1-taxonomy-3level. Needs separate impl plan.pipeline-1.1+2.1+3.2+6.1+7.1+7.2+8.1-matching-engine-* into 14 per-sub-step files?calibredocs.3nm.io live deploy reflects this session's projection logic fix (slugify) + engine map publication.amendment-3.1+5.3+6.1-bootstrap-rubric-extension — single plan covering vocab + 5.3 rules + 22 rubrics.fca2e81.fca2e81 → 0af6f45.step-5.0-candidate-merge-design.pipeline-4-sourcing-design covering 4.1–4.4.pipeline-9-outreach-design covering 9.1–9.4.