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.
Everything below is an application of three cleanly separated gates. Blurring them is what made v1 confusing.
| Gate | Scope | Rule |
|---|---|---|
| Extract | per 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. |
| Score | per (résumé × context) | A family is scored only if its context weight > 0. The fit score is computed from high-confidence signals only. |
| Show | per scored signal | The same high-confidence set that scores becomes the chips. Medium → detail, unknown/missing-optional → hidden. |
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.
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.
PDF → text (fitz, pypdf fallback). Keyed by résumé hash.
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.
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.
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.
Sort Strong → Weak, persist the lean per-candidate result, notify the recruiter.
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).
W > 0 in this context. A requirement-gated family (W = 0) is skipped — not sent to the LLM, not shown.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 hidden — missing ≠ negative.
confidence is low.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.
// 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.
Three artefacts, each at its natural scope — no field stored twice (full detail in the schema doc).
| Artefact | Scope / cache key | Holds |
|---|---|---|
facts.json | résumé hash | ~28 extracted facts + GitHub API summary (if fetched). Position-independent. |
context.json | position + ctx hash | band · environment · domain · min-years · weight vector W[family]. |
score.json | résumé × context | lean 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.
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.
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.
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).
extract_facts hook.headline · explanation · show; colour derived from score+type), high-confidence-only fit formula with renormalised weights + coverage→confidence + thin-coverage guard, one summary; education_certs ignores bare degrees; drop the removed fields (incl. tone).STEP_VERSIONS for build_context, extract_facts, score; re-score the 10 sample candidates into the v2 shape.show === "chip" signals as pill + chips; hover = headline · explanation · evidence. No client-side display logic.Utkrusht — internal design · the Résumé Fit pipeline, end to end (v2)