mochirank / src /reasoning_generator.py
DawnOfJustize's picture
Initial HF Spaces deployment (orphan — no history)
7a9cfa1
Raw
History Blame Contribute Delete
10.1 kB
"""
Stage G — SHAP-derived reasoning strings.
Routing logic: SHAP confirms the rank is correct; the sentence structure is
chosen based on what is most *distinctive* in the actual profile, so each
candidate reads differently. All facts are pulled from the raw profile —
no hallucination.
"""
from __future__ import annotations
import numpy as np
_JD_CORE = [
"python", "pytorch", "tensorflow", "transformers", "hugging face",
"llm", "large language model", "fine-tuning", "lora", "rlhf",
"embeddings", "vector database", "faiss", "pinecone", "weaviate",
"retrieval", "rag", "ranking", "recommendation", "search",
"information retrieval", "nlp", "natural language processing",
"mlops", "model serving", "kubernetes", "docker", "triton",
"langchain", "llamaindex", "openai", "anthropic", "opensearch",
"elasticsearch", "semantic search", "reranking", "qlora",
]
_IT_SERVICES = {
"tcs", "infosys", "wipro", "hcl", "tech mahindra", "cognizant",
"accenture", "capgemini", "mphasis", "mindtree", "hexaware",
"niit", "zensar", "l&t infotech",
}
_PROD_KW = {
"deployed", "production", "serving", "latency", "throughput",
"billion", "million users", "real-time", "a/b test", "monitoring",
"inference", "scalable", "high-availability",
}
_TIER1 = {"Tier-1", "IIT", "NIT", "Top-10-Global"}
# ------------------------------------------------------------------ #
# Profile fact extractors — all read raw profile, nothing invented
# ------------------------------------------------------------------ #
def _skills(c: dict) -> list[str]:
seen: set[str] = set()
out: list[str] = []
for s in c.get("skills", []):
name = s.get("name", "")
if not name:
continue
nl = name.lower()
if any(kw in nl or nl in kw for kw in _JD_CORE) and nl not in seen:
seen.add(nl)
out.append(name)
return out
def _production_count(c: dict) -> int:
text = " ".join(j.get("description", "") for j in c.get("career_history", [])).lower()
return sum(1 for kw in _PROD_KW if kw in text)
def _startup_co(c: dict) -> str:
"""Name of first startup the candidate spent 6+ months at."""
for j in c.get("career_history", []):
if j.get("company_size") in ("1-10", "11-50", "51-200") and j.get("duration_months", 0) > 6:
return j.get("company", "")
return ""
def _product_cos(c: dict) -> list[str]:
seen: set[str] = set()
out: list[str] = []
for j in c.get("career_history", []):
co = j.get("company", "")
if not co or any(t in co.lower() for t in _IT_SERVICES):
continue
if j.get("duration_months", 0) > 6 and co.lower() not in seen:
seen.add(co.lower())
out.append(co)
return out[:2]
def _best_edu(c: dict) -> tuple[str, str]:
"""(institution, tier) of highest-tier degree."""
rank_of = {"Top-10-Global": 5, "IIT": 5, "Tier-1": 4, "NIT": 3}
best, best_r = ("", ""), 0
for e in c.get("education", []):
t = e.get("tier", "")
r = rank_of.get(t, 0)
if r > best_r:
best_r, best = r, (e.get("institution", ""), t)
return best
def _response_rate(c: dict) -> float:
return float(c.get("redrob_signals", {}).get("recruiter_response_rate", 0.0))
def _last_active(c: dict) -> str:
return c.get("redrob_signals", {}).get("last_active_date", "")[:7]
def _notice_days(c: dict) -> int:
return int(c.get("redrob_signals", {}).get("notice_period_days", 90))
def _longest_tenure(c: dict) -> tuple[int, str]:
"""(months, company) of longest single role."""
best = (0, "")
for j in c.get("career_history", []):
dm = j.get("duration_months", 0)
if dm > best[0]:
best = (dm, j.get("company", ""))
return best
# ------------------------------------------------------------------ #
# Routing: pick the most distinctive story based on profile, not SHAP
# ------------------------------------------------------------------ #
def _story_type(c: dict) -> str:
"""
Profile-first routing. Every candidate in the top-100 has good SHAP
semantic scores (that's why they're here). The interesting question is
*what else* makes them stand out.
"""
# Founding-team / startup experience is rare and highly valued
if _startup_co(c):
return "startup"
# Tier-1 education is a strong differentiator
inst, tier = _best_edu(c)
if inst and tier in _TIER1:
return "education"
# Heavily production-focused career
if _production_count(c) >= 7:
return "production"
# Highly responsive / immediately available
if _response_rate(c) >= 0.80:
return "behavioral"
# Notable product-company tenure (name the companies)
cos = _product_cos(c)
if cos and len(cos) >= 2:
return "career"
# Default: describe skills + role (still specific due to different skill sets)
return "role"
# ------------------------------------------------------------------ #
# Sentence composers
# ------------------------------------------------------------------ #
def _s(c: dict, yoe: float, title: str, co_type: str) -> str:
"""Dispatch to the right composer."""
story = _story_type(c)
skills = _skills(c)
s3 = ", ".join(skills[:3]) if skills else "core ML/NLP stack"
if story == "startup":
name = _startup_co(c)
prod = _production_count(c)
prod_note = f"; {prod} production signals across descriptions" if prod >= 5 else ""
return (
f"{yoe:.0f}yr {title} with founding-team experience at {name}"
f"{prod_note}; skills include {s3}."
)
if story == "education":
inst, _ = _best_edu(c)
cos = _product_cos(c)
co_note = f" now at {cos[0]}" if cos else ""
return (
f"{yoe:.0f}yr {title} from {inst}{co_note}; "
f"evidenced skills in {s3}."
)
if story == "production":
n = _production_count(c)
cos = _product_cos(c)
co_note = f" at {cos[0]}" if cos else f" at {co_type}"
return (
f"{n} production deployment signals across career descriptions"
f"{co_note}; {yoe:.0f}yr {title} with skills in {s3}."
)
if story == "behavioral":
rr = _response_rate(c)
la = _last_active(c)
active = f", active as of {la}" if la else ""
return (
f"{rr:.0%} recruiter response rate{active}; "
f"{yoe:.0f}yr {title} with skills in {s3}."
)
if story == "career":
cos = _product_cos(c)
co_str = " and ".join(cos[:2])
prod = _production_count(c)
prod_note = f"; {prod} production signals" if prod >= 4 else ""
return (
f"{yoe:.0f}yr {title} across {co_str}{prod_note}; "
f"skills include {s3}."
)
# role — most common; vary the sentence lead using skill count or YOE
n_skills = len(skills)
prod = _production_count(c)
if n_skills >= 6:
return (
f"{yoe:.0f}yr {title}; {n_skills} JD-matched skills "
f"({', '.join(skills[:4])})."
)
if prod >= 4:
return (
f"{yoe:.0f}yr {title} at {co_type}; "
f"{prod} production signals and skills in {s3}."
)
return (
f"{yoe:.0f}yr {title} at {co_type}; "
f"evidenced in {s3} across career."
)
# ------------------------------------------------------------------ #
# Concern note (SHAP-driven — only things that pushed score DOWN)
# ------------------------------------------------------------------ #
def _concern(negatives: list[tuple[str, float]], c: dict) -> str:
sig = c.get("redrob_signals", {})
for feat, _ in negatives[:2]:
if feat == "notice_penalty":
days = _notice_days(c)
return f" Note: {days}-day notice period."
if feat == "recency_score":
la = sig.get("last_active_date", "")
return f" Note: inactive since {la[:7]}." if la else " Note: low recent activity."
if feat == "behavioral_composite":
rr = _response_rate(c)
return f" Note: recruiter response rate {rr:.0%}." if rr > 0 else ""
if feat == "product_company_ratio":
return " Note: limited product company exposure."
if feat == "sim_anti_max":
return " Note: partial profile overlap with anti-persona archetype."
if feat == "title_chaser_flag":
return " Note: average tenure under 18 months across roles."
return ""
# ------------------------------------------------------------------ #
# Public API
# ------------------------------------------------------------------ #
def generate_reasoning(
candidate_id: str,
candidate: dict,
shap_row: np.ndarray,
feature_names: list,
rank: int,
) -> str:
"""
1-2 sentence reasoning string.
Story type chosen from profile facts; concern note from SHAP negatives.
All facts sourced directly from the candidate dict — no hallucination.
"""
shap = dict(zip(feature_names, shap_row.tolist()))
negatives = sorted(
[(f, v) for f, v in shap.items() if v < -0.005], key=lambda x: x[1]
)
profile = candidate.get("profile", {})
yoe = float(profile.get("years_of_experience", 0))
title = profile.get("current_title", "professional")
cs = profile.get("current_company_size", "")
co_type = (
"startup" if cs in ("1-10", "11-50", "51-200")
else "mid-size company" if cs in ("201-500", "501-1000")
else "large company"
)
sentence = _s(candidate, yoe, title, co_type)
concern = _concern(negatives, candidate) if rank <= 60 else ""
return (sentence + concern).strip()
def build_explainer(model_path: str = "artifacts/ranker_model.json"):
import shap, xgboost as xgb
model = xgb.Booster()
model.load_model(model_path)
return model, shap.TreeExplainer(model)