Utkrusht · Résumé Fit · Pipeline design v2

The pipeline, end to end

From a list of résumés to high-quality signals on the candidate card. The organising idea: extract once per résumé, score & show per context, and let the same high-confidence set both drive the score and become the chips.

The three gates (read this first)

Everything below is an application of three cleanly separated gates. Blurring them is what made v1 confusing.

GateScopeRule
Extractper résumé (position-independent, hash-cached)Always pull every fact. GitHub is the only conditional fetch (needs an API call) — run it only if a URL is present.
Scoreper (résumé × context)A family is scored only if its context weight > 0. The fit score is computed from high-confidence signals only.
Showper scored signalThe same high-confidence set that scores becomes the chips. Medium → detail, unknown/missing-optional → hidden.

What happens after a list of résumés arrives

Position → context (inferred) per résumé: fetch → facts → github? → score rank + store cards: pill + chips adjust → re-score

Context is built once when the position is published (no recruiter confirmation). Each résumé then runs the per-résumé pipeline in parallel; the résumé-level steps (fetch, facts, github) are cached by résumé hash, so re-scoring against a different position or an adjusted context reuses them for free.

The steps, with the v2 changes

1 · Build the evaluation context step01–05 changed

Infer band · environment lens · domain · min-years from the JD + a company-website crawl. No confirmation screen. Output includes a per-position weight vector W[family] = band-adjusted × requirement-gated: a family the role doesn't care about (e.g. domain requirement = false → industry_domain_match W = 0) is zeroed and its weight redistributed. company_environment only sets the startup/enterprise interpreter; with no preference it defaults to a neutral lens.

2 · Fetch & text-extract the résumé fetch

PDF → text (fitz, pypdf fallback). Keyed by résumé hash.

3 · Extract facts step06_extract_facts

Always extract all résumé facts (~28 fields: skills, experience, tenure, scale claims, ownership cues, education, red-flag facts, public presence incl. any GitHub URL). Position-independent, cached by résumé hash — this is what makes re-scoring free. Extraction is never pruned by context.

4 · Fetch GitHub — only if a URL is present step06a_fetch_github NEW

If facts contain a GitHub URL → call the GitHub API (profile, public repos, languages, last-commit recency, tests/CI presence, stars, OSS PRs) → summarise to a short string → feed the existing extract_facts(github_summary=…) hook so the github facts populate. No URL → skip the fetch entirely (the signal stays missing, which is never a penalty). Cached by résumé hash.

5 · Score signals step07_score reworked

For each family with W > 0, the scorer emits the lean record (score · confidence · show · headline · explanation · evidence). It computes fit_score from the high-confidence signals only, sets each signal's show, and writes one summary sentence. Families at W = 0 are not scored. Chip colour is derived later from score+type — not stored. See the rules below.

6 · Rank + store step08_rank

Sort Strong → Weak, persist the lean per-candidate result, notify the recruiter.

7 · Cards & adjust New

Position-page cards + task-report profile card render the bucket pill + high-confidence chips (hover = that signal). A "what we assumed" strip lets the recruiter adjust the context → re-score (facts/github reused from cache).

Which signals get scored, which score the fit, which show

Scored?
W > 0 in this context. A requirement-gated family (W = 0) is skipped — not sent to the LLM, not shown.
Scores the fit?
Only high-confidence signals. Medium/low/unknown are excluded from the number (Unknown ≠ Low).
Shows as a chip?
The same high-confidence set — that has a real finding, W > 0, and isn't a missing-optional.

The elegant part: the high-confidence signals are both the fit-score inputs and the chips — one set, no divergence. Medium-confidence signals still surface as detail on hover (informative), but never move the number or earn a chip. Missing optional signals (GitHub absent) are hiddenmissing ≠ negative.

The fit-score formula

Let H = core families with W > 0 and confidence = high.

fit_score = Σf∈H ( scoref × Ŵf ) × 100   +   capped_bonus   −   risk_gate
where   Ŵf = Wf / Σf∈H Wf  // renormalise so the high-confidence set owns the full 100

confidence = coverage = Σf∈H Wf / Σf∈core, W>0 Wf

Thin-coverage guard. Because only high-confidence signals score, a candidate assessed on very little weight (say coverage < 0.5) would get a crisp-looking number built on thin evidence. In that case the scorer marks confidence: low and the card leads with "Low confidence — needs review" rather than a falsely precise bucket. The number is only as trustworthy as its coverage.

GitHub via the API — the new step in detail

// step06a_fetch_github  (runs only if facts.public_presence has a github url)
url = find_github_url(facts)
if not url: return None            // no fetch, github signal stays "missing"
summary = github_api.summarise(url)   // profile, repos, languages, last_commit,
                                      // tests_pct, cicd_repos, stars, oss_prs
facts = extract_facts(resume_uri, github_summary=summary)  // existing hook

The raw GitHub summary lands in the résumé-level facts cache (not the per-candidate result). The scorer then reads facts.github to produce the github bonus signal. Only high-confidence, present GitHub activity earns the bonus; a dead or empty profile is neutral, not a penalty.

How the data is stored

Three artefacts, each at its natural scope — no field stored twice (full detail in the schema doc).

ArtefactScope / cache keyHolds
facts.jsonrésumé hash~28 extracted facts + GitHub API summary (if fetched). Position-independent.
context.jsonposition + ctx hashband · environment · domain · min-years · weight vector W[family].
score.jsonrésumé × contextlean result: fit_score · bucket · confidence · summary + signals[] (family · score · confidence · show · headline · explanation · evidence).

Static family metadata (label · type · effect) lives once in FAMILY_META; weight lives in context.json. Dropped from v1: positives / concerns / unknowns / report / recommendation and per-signal type · effect · weight · contribution_pts · state · verdict · score_impact — all derivable or duplicated.

Worked example — Deepanshi (UX designer, Senior QA role)

Domain requirement is set here, so industry_domain_match has W > 0; had it been false, that signal would be absent entirely. High-confidence signals (skills, experience, domain) are all confident negatives → they score the fit and become warning chips. Medium ones (scale, ownership, career) are excluded from the number and shown only on hover.

Deepanshi
● Weak fit ⚠ 0 / 8 required skills ⚠ 2.5 yrs · below 5-yr bar ⚠ No HR-tech domain

Scored the fit: required_skill_match, evidence_strength, relevant_experience, industry_domain_match (all high-confidence). Excluded: scale, ownership, career_progression (medium) → detail only. Hidden: github (missing), skill_recency (unknown), education_certs (bare degree = table stakes, no cert). Result: Fit 7 · Weak · confidence medium.

Hover on each chip — drawn straight from the signal record

No modal: hovering any chip shows that one signal's headline → title, explanation → body, confidence+type → meta, evidence[] → rows. Chip colour is derived from score+type (not stored). The bucket pill's hover carries the aggregate (fit_score · bucket · summary).

⚠ 0 / 8 required skills — required_skill_match
0 / 8 required skills
Confidence: High · core
resume · skillsProduct Thinking, Visual Design… no QA skills
⚠ 2.5 yrs · below 5-yr bar — relevant_experience
2.5 yrs · below the 5-yr bar
Confidence: High · core
evaluation contextmin_years = 5; candidate ~2.5 yrs, wrong domain
⚠ No HR-tech domain — industry_domain_match
No HR-tech / recruiting domain
Confidence: High · core
resume · companiesFLAM (AR), LAXIS (SaaS), GROWW (Fintech), EY (Consulting)
● Weak fit — overall (bucket pill)
Fit score 7 / 100 · Weak
Confidence: Medium
Strong reject — UX designer; none of the 8 mandatory QA skills; 2.5 yrs vs a 5-yr bar. (summary)

Rollout

Utkrusht — internal design · the Résumé Fit pipeline, end to end (v2)