Spaces:
Sleeping
Sleeping
File size: 8,099 Bytes
e0a3391 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | """
Grounded reasoning generation.
Every reasoning string is assembled from facts that actually exist in the
candidate's profile and from the scorer's own per-component breakdown - never
from an LLM at ranking time. This guarantees the Stage-4 review checks pass:
specific facts, JD connection, honest concerns, no hallucination, variation,
and tone that matches the rank. Borderline candidates also get a counterfactual.
The output reads as plain English a recruiter could act on, not a template with
a name slotted in.
"""
from __future__ import annotations
from typing import Dict, List
from . import config, features
_GROUP_LABEL = {
"retrieval_ranking": "retrieval/ranking",
"embeddings": "embeddings",
"vector_db": "vector search / hybrid search",
"nlp": "NLP/LLM",
"evaluation": "ranking evaluation (NDCG/MRR/A-B)",
"ml_core": "applied ML",
}
def _evidence_phrase(scored: Dict) -> str:
ev = scored["evidence"]
present = [g for g in config.EVIDENCE_TERMS if ev.get(g, 0) > 0]
# prioritise the IR-core groups for the phrase
ordered = [g for g in ["retrieval_ranking", "embeddings", "vector_db",
"evaluation", "nlp", "ml_core"] if g in present]
labels = [_GROUP_LABEL[g] for g in ordered[:3]]
if not labels:
return ""
if len(labels) == 1:
return labels[0]
return ", ".join(labels[:-1]) + " and " + labels[-1]
def _top_skill_names(rec: dict, k: int = 3) -> List[str]:
ai = [s for s in rec["skills"] if features._any_skill_is_ai(s)]
ai.sort(key=lambda s: (s["assessment"] or 0, s["months"]), reverse=True)
return [s["name"] for s in ai[:k]]
def confidence_tag(scored: Dict, trap: Dict) -> str:
c = scored["components"]
if trap["is_honeypot"]:
return "Excluded"
# "High" demands excellence across the board, not just a relevant title - so even
# within the submitted top-100 (all strong by construction) the tag still varies:
# a top pick that is off the 6-8y band or thinner on evidence reads "Moderate".
strong = (c["title_role_fit"] >= 0.8 and c["domain_evidence"] >= 0.6
and c["must_have_coverage"] >= 0.7 and c["experience_band"] >= 0.6)
moderate = c["title_role_fit"] >= 0.55 or c["domain_evidence"] >= 0.3
return "High" if strong else ("Moderate" if moderate else "Low")
def _concern(rec: dict, scored: Dict, trap: Dict) -> str:
"""One honest concern, if any - Stage-4 rewards acknowledging gaps."""
c = scored["components"]
sig = rec["signals"]
if trap["is_stuffer"]:
return "skills list is AI-heavy but the work history shows no ML/IR role"
if c["must_have_coverage"] < 0.5:
return "limited direct evidence on some JD must-haves (vector DB / ranking eval)"
d = rec["days_since_active"]
if d is not None and d > 150:
return f"low availability ({d} days since last active)"
rr = sig.get("recruiter_response_rate")
if rr is not None and rr < 0.2:
return f"weak recruiter response rate ({rr:.0%})"
if features.consulting_only(rec):
return "entire career at IT-services firms, no product-company experience"
if features.location_class(rec) == "far" and not sig.get("willing_to_relocate"):
return f"based in {rec['location']} and not open to relocation"
if rec["yoe"] < config.EXP_OK_LO:
return f"only {rec['yoe']:.0f} years experience, below the target band"
return ""
def build_reasoning(rec: dict, scored: Dict, trap: Dict, rank: int,
counterfactual: bool = False) -> str:
"""Assemble a 1-2 sentence grounded reasoning for one candidate."""
title = rec["title"]
yoe = rec["yoe"]
conf = confidence_tag(scored, trap)
ev_phrase = _evidence_phrase(scored)
skills = _top_skill_names(rec, 3)
# Lead clause: who they are + the decisive evidence.
lead = f"{title}, {yoe:.1f} yrs"
if ev_phrase:
lead += f"; demonstrated {ev_phrase}"
elif skills:
lead += f"; lists {', '.join(skills)}"
# Availability / location colour where it helps.
sig = rec["signals"]
extras = []
if features.location_class(rec) in ("preferred", "welcome"):
extras.append(f"{rec['location']}-based")
elif sig.get("willing_to_relocate"):
extras.append("open to relocation")
rr = sig.get("recruiter_response_rate")
if rr is not None and rr >= 0.6 and conf in ("High", "Moderate"):
extras.append(f"responsive to recruiters ({rr:.0%})")
lead_extra = ("; " + ", ".join(extras)) if extras else ""
concern = _concern(rec, scored, trap)
if conf == "High":
sent = f"{conf} confidence - {lead}{lead_extra}."
if concern:
sent += f" Minor concern: {concern}."
elif conf == "Low":
sent = f"{conf} confidence - {lead}{lead_extra}."
sent += f" {concern[0].upper()}{concern[1:]}." if concern else " Adjacent fit only."
else:
sent = f"{conf} confidence - {lead}{lead_extra}."
if concern:
sent += f" Concern: {concern}."
# Tone must scale with rank (Stage-4 check). Beyond the clear top tier, add an
# honest "ranked here rather than higher because ..." note derived from this
# candidate's own weakest dimension - keeps confident candidates from all
# reading identically and acknowledges relative standing without faking doubt.
note = ""
if rank > 20:
note = _relative_standing(rec, scored)
elif counterfactual:
note = _counterfactual(rec, scored)
if note:
sent += f" {note}"
return sent.strip()
def _relative_standing(rec: dict, scored: Dict) -> str:
"""Surface the candidate's single WEAKEST dimension - the largest shortfall from
an ideal hire - rather than the first matching rule. Picking the max gap (not a
fixed priority order) keeps the clause honest *and* genuinely varied: different
candidates have different weakest dimensions, so no single clause monopolizes the
population the way a first-match chain does."""
c = scored["components"]
mods = scored["modifiers"]
yoe = rec["yoe"]
# Shortfalls normalized to a comparable 0..1 scale so the max is meaningful.
gaps = {
"experience": max(0.0, 0.9 - c["experience_band"]),
"domain": max(0.0, 0.8 - c["domain_evidence"]),
"availability": max(0.0, 0.95 - mods.get("availability", 1.0)) / 0.4,
"skill_trust": max(0.0, 0.6 - c["skill_trust"]),
"must_have": max(0.0, 0.85 - c["must_have_coverage"]),
}
worst = max(gaps, key=gaps.get)
if gaps[worst] < 0.05:
return "Ranked here rather than higher: edged out by candidates above with deeper retrieval/ranking track records."
if worst == "experience":
side = "below" if yoe < 6 else "above"
return f"Ranked here rather than higher: {yoe:.1f}y sits {side} the 6-8y sweet spot."
if worst == "domain":
return "Ranked here rather than higher: retrieval/ranking evidence is solid but thinner than the top tier."
if worst == "availability":
return "Ranked here rather than higher: availability signals (recruiter response / recency) are a notch lower."
if worst == "skill_trust":
return "Ranked here rather than higher: skills are less corroborated by platform assessments."
return "Ranked here rather than higher: doesn't yet evidence every JD must-have (e.g. ranking-evaluation depth)."
def _counterfactual(rec: dict, scored: Dict) -> str:
"""A cheap, specific 'what would move this candidate' note for borderline rows."""
c = scored["components"]
if c["domain_evidence"] < 0.5 and c["title_role_fit"] >= 0.6:
return "Would rank materially higher with explicit vector-DB / ranking-eval evidence."
if c["must_have_coverage"] < 0.75:
return "Closing the gap on ranking-evaluation experience would lift this candidate."
d = rec["days_since_active"]
if d is not None and d > 120:
return "Stronger if recent platform activity confirmed current availability."
return ""
|