Every candidate is scored three ways against the same job description — skills overlap, experience fit, and soft fit — combined into one number whose weights you set. Each part keeps the evidence behind it, so "why is this person third?" has an actual answer rather than a shrug.
Both sides are parsed into structure first, then compared three separate ways. Two of the three comparisons are deterministic; only the third involves judgement, and it works on a redacted copy.
two things in
scored three ways
Paste the posting and it comes back as structure: required skills, nice-to-haves, minimum and preferred years, seniority, responsibilities, and the expectations that were implied rather than listed. That last category matters — "startup pace" and "comfortable with ambiguity" are real requirements that never appear in a checklist.
Claude API · structured job descriptionUpload a batch and each one is parsed into the same shape: canonical skills, work history with years attached role by role, education, total experience, highest seniority reached, and a one-line read on the trajectory. Skill variants are normalised against an editable taxonomy, so one candidate writing "Postgres" and another writing "PostgreSQL" stop being different people.
PyMuPDF · Claude API · editable skills taxonomyBefore the judgement-based scorer reads anything, a deterministic pass removes name, email, phone, profile links, postal address, pronouns, and age signals. Deterministic matters here: this is regex, not a model deciding what counts as identifying. The number of redactions made is shown per candidate, so the step is visibly auditable rather than merely claimed.
regex redaction · per-candidate redaction countsSkills are compared by embedding similarity, which catches equivalent skills written differently, with a bonus when a canonical taxonomy term matches exactly. Experience is pure Python: distance from the target years with a linear falloff, seniority gap, and a lift for an upward trajectory. Only soft fit involves a model, assessing achievement alignment and the implicit expectations — on the redacted copy.
embeddings · rules engine · one model call per candidateThe three sub-scores are weighted into a single value — forty, thirty, thirty by default, adjustable per job when a role genuinely hinges on one dimension. Candidates at or above a configurable threshold are flagged as shortlisted. Everyone below it is still scored and still explained, because the second-tier candidate you dismissed is the one you'll want to revisit when the first hire falls through.
configurable weights and threshold, per jobEvery component's evidence and sub-metrics are preserved rather than collapsed into the final number. The dashboard shows the ranked table and a per-candidate breakdown, the shortlist exports as a spreadsheet, and each candidate has a downloadable report carrying the composite score, the per-component blocks with their evidence, the redaction summary, and the career snapshot.
dashboard · CSV export · per-candidate PDF reportName, contact details, links, address, pronouns, and age signals are gone before the fit reasoner reads a word, and the redaction count is surfaced as evidence the pass ran. This closes the most direct route for irrelevant signal to reach a score — it does not make screening neutral, and it isn't claimed to. Two of the three scores are deterministic and every score shows its evidence precisely so a person can audit the ranking rather than trust it. The system ranks; it doesn't decide.
Three different questions, answered three different ways, because they aren't the same kind of question.
Embedding similarity between what the role requires and what the candidate lists, so related skills still register, plus a bonus when a canonical term matches exactly and a small lift for nice-to-have coverage.
Plain Python over the numbers: distance from the target years with a linear falloff, the gap to the required seniority, and credit for a career that's been moving upward. No model, no variance, same input to same output every run.
The judgement call — do the achievements line up with the responsibilities, are the implicit expectations met, are there signals pointing the other way. One model call per candidate, reading a copy with the identity removed.
Forty/thirty/thirty is a starting point, not a claim. A role that genuinely lives or dies on one specific skill should be weighted to say so, and the weights are set per job.
A configurable cut-off decides which candidates get flagged. Everyone else stays ranked and explained in the same table — the line moves without rerunning anything.
Parsing on both sides, three independent scorers in the middle, and a ranking layer that keeps everything it was given.
Turns a posting and a stack of PDFs into two comparable structures, normalising skill names against a taxonomy that ships as an editable file — a client swaps in their own without a code change. Identity is stripped on the copy bound for the model.
Cosine similarity between required and held skills, with exact-match bonuses. Runs locally and free by default; a hosted provider can be switched on.
Years, seniority distance, and trajectory. No model involved, which is what makes this component reproducible and arguable.
Assesses alignment and implicit expectations. Repeat documents are hashed and skipped, so re-running a batch doesn't re-pay for the same call.
Applies the weights, flags against the threshold, and preserves every component's evidence for the dashboard, the exported shortlist, and the per-candidate report.
Six modular blocks. The taxonomy and the weights change per client; the three-way split and the redaction pass don't.
Turns a posting into requirements, stated and implied.
Turns a PDF into skills, years, and trajectory.
Strips identifying detail before any judgement is made.
Similarity, rules, and one judgement call.
Combines the three, flags against the threshold.
Dashboard, export, and a report per candidate.