Bhanu Prakash Putta · AI Engineer

Work / 01 / Placewise

Résumés in, evidence-backed shortlists out.

Recruiters at architecture, engineering and construction staffing firms spend hours reading résumés against vague job notes. Placewise sits on top of their existing ATS and does the first pass: it reads every résumé, finds the right people for a role, and backs each recommendation with the résumé line that proves it.

48→89%recall@10
98%precision@10
97.6%submit precision
$0.02LLM cost per résumé
~300automated tests

What it does

  • Intake. PDF, DOCX and text résumés arrive from an upload page, a watched folder or an n8n webhook, and wait in a Postgres job queue.
  • Understanding. A LangGraph pipeline per résumé dedupes, parses and screens the file, has Claude extract a structured profile, then validates it in code.
  • Job orders. Messy recruiter notes become a structured job order, and the system asks clarifying questions when something is missing.
  • Matching. Hard filters (license, state, years) plus hybrid search and rerank find candidates, and each one is scored with quoted evidence.
  • Ask. A conversational assistant answers questions about candidates and roles, citing résumé lines.
  • Human in control. Shortlists go through approve or reject. Nothing is sent automatically.
Placewise recruiter dashboard: counts of candidates, open job orders and shortlists to review, today's AI spend against the daily cap, a list of shortlists waiting for a decision, and a ring chart of how 568 screened résumés were classified (clean, needs cleanup, tampered)
Fig. 1The recruiter dashboard. Every file is classified before any model reads it; five résumés carried hidden instructions aimed at AI and were cleaned first. AI spend is shown against a hard daily cap.
Architecture: a recruiter uses the Next.js portal, which calls the FastAPI API. Résumé sources feed ingest workers running LangGraph. Both use Postgres with pgvector. Claude handles extraction, answers and scoring; Voyage AI handles embeddings and rerank; LangSmith collects traces. Sources Recruiter / adminbrowser Résumé sourcesupload · email · n8n Placewise · Docker Compose Web portalNext.js APIFastAPI · Ask · matching Ingest workersLangGraph pipeline Postgres 17 + pgvectorprofiles · chunks (HNSW + full-text) · job queue Model services Anthropic Claudeextract · score Voyage AIembeddings · rerank LangSmithtraces · evals /api files → job queue read · enqueue claim job · store
Fig. 2scroll → System architecture. Everything runs in one Docker Compose stack; the job queue is plain Postgres with SKIP LOCKED, so there is no separate broker to run.

Ingest: treat every file as untrusted

Real résumé folders are full of scans, two-column layouts, duplicates, corrupt files and occasionally hidden text written to manipulate an AI screener. The ingest graph screens each file before any model sees it, and never takes the model's word for a fact.

  • Hidden and injected text is detected and stripped in a plain-code step that takes about 30 ms.
  • Claude returns a structured profile with evidence quotes, then code validates the facts: each quote must exist in the résumé, and years of experience are computed from the dates.
  • Extraction is cached on the cleaned text, model and prompt, so reprocessing a file costs nothing.
  • Every model call is logged with tokens, latency and cost. A daily spend cap pauses work instead of failing it.

On the evaluation set, unusable and adversarial files were caught with 100% precision and recall. Moving extraction from a small model to Claude Sonnet raised precision on flawed résumés from 57% to 94%, at about two cents per résumé.

Ingest pipeline for one résumé: receive, parse and screen, extract with Claude, validate in code, embed and store. Duplicates and unusable files exit early. 01Receivehash filededupe 02Parse + screenPDF · DOCX · TXThidden text · no AI 03ExtractClaude Sonnetprofile + quotes 04Validatequotes must existyears from dates 05Embed + storeVoyage chunksone DB transaction duplicate needs attention searchable candidate
Fig. 3scroll → The LangGraph ingest pipeline for one résumé. Only step 03 uses a model; the steps either side are code.

Retrieval: from 48% to 89% recall

Pure vector search missed most qualified candidates, because requirements like “PE licensed in Texas, 10+ years” are hard constraints, not semantic similarity. Each layer stayed only because the evaluation showed it helped.

Retrieval setuprecall@10precision@10MRR
Vector search only48%––
+ hard filters84%––
+ hybrid keyword search + rerank89%98%1.00

Measured against an answer key the API never sees: 597 résumés and 15 job orders, including scanned, corrupt, duplicate and prompt-injection files.

Ask pipeline: a recruiter question is understood by Claude Haiku into a query and hard filters, retrieved with filters, vector and keyword search fused by reciprocal rank fusion, reranked by Voyage, answered by Claude Sonnet, and every citation is checked in code. Question“Texas PEs in DFW,drainage, 8+ yrs” UnderstandClaude Haikuquery + filters Retrieve · own SQL Hard filterslicense · metro · years Vector searchpgvector HNSW Keyword searchPostgres full-text Rank fusionRRF, k = 60 RerankVoyage reranktop 50 → 30 AnswerClaude Sonnetverified facts only Citation checkcode: each quotemust exist answer + citations ~14 s · ~3¢
Fig. 4scroll → The Ask pipeline. Retrieval is hand-written SQL rather than a generic retriever, so hard filters and both searches run in one query.

Matching you can audit

For each candidate, the model must quote the résumé line that supports each requirement. Code checks that every quote really exists, and code, not the model, computes the final score. Scoring is blind: names, addresses and graduation years are removed first.

97.6% submit precision, and a recruiter can see exactly why each person made the list.

A job order for a Senior Civil Project Manager: the structured role and must-have requirements on the left, and the ranked shortlist on the right, where each candidate has a code-computed score, a written summary, flags to confirm, and Approve or Reject buttons
Fig. 5A job order's shortlist. The score comes from code counting the requirements met; the model writes the summary and the flags a recruiter should confirm. Nothing is submitted until someone clicks Approve.

Production engineering

Deployment

  • Google Cloud VM running the Docker Compose stack behind Caddy and Cloudflare.
  • GitHub Actions: CI on every pull request, auto-deploy from main, health check, automatic rollback.
  • Uptime and budget alerts, and a hard spend cap on model calls.

Cost and quality controls

  • Prompt caching plus a Postgres response cache, so repeat questions cost nothing.
  • LangSmith tracing and versioned prompts.
  • 21 architecture decision records, one per trade-off.
From the decision logCode validates LLM facts (ADR 013). Own retrieval SQL instead of a generic retriever (ADR 017). A Postgres SKIP LOCKED queue instead of a broker (ADR 010). Auto-deploy with rollback (ADR 020). Claude cost caching (ADR 021).

Stack

  • Python
  • LangGraph
  • LangChain
  • LangSmith
  • Claude
  • Voyage AI
  • Postgres
  • pgvector
  • FastAPI
  • Next.js
  • n8n
  • Docker
  • GitHub Actions
  • Google Cloud
  • Cloudflare

Fits more than recruiting

The same pattern (untrusted documents in, validated structured data, filtered hybrid retrieval, evidence-backed answers) fits contracts, support tickets, claims, research papers and more.