Perfect CV - Job-Tailored Resume Engine
Perfect CV turns a pasted job posting into a tailored resume, scoring every record in your history with a decision router and letting code, not a language model, choose what goes on the page.
Technologies Used

Perfect CV keeps a career history as structured blocks and builds a .docx resume per application from the pieces that fit the posting. It is a free initiative rather than a product: no subscription, no templates, data held on EU servers.
The design rests on one rule: a language model never decides what goes on the resume. Every record is scored against the posting by a decision router, and the choosing — how many roles, which ones, in what order — is plain code. The model is used for a single job: rewriting the records it is handed into prose aimed at that posting.
Highlights
- Three-stage LangGraph pipeline with the user supervising between stages: read the posting, score and choose, write and render
- Per-record scoring through OpenRouter's decision API, with caps, thresholds and chronology applied deterministically in code
- Semantic retrieval over the user's own history with Qdrant and BGE-M3 embeddings
- Resume rendered into the user's own .docx template by regenerating only
word/document.xml, so styles and fonts survive byte for byte - PDF import: a second graph parses an existing CV into history records and proposes profile titles across distinct career directions
- Writing split into one small call per entry, keeping every prompt bounded for a size-sensitive self-hosted gateway, with automatic failover to a second provider
- 192 tests, covering the selection rules, the prompt contracts and the failure modes each rule exists to prevent
Engineering notes
Most of the interesting work was finding where a model should not be trusted:
- Asking the writer not to claim a qualification the candidate lacked failed in five runs out of five. Withholding the posting's qualification bar from the prompt fixed it.
- Three proposed profile titles kept coming back as one job renamed. Embedding similarity could not separate them — with BGE-M3, "LLM Application Developer" and "Mobile Application Developer" score 0.829 while the genuine duplicate "AI Engineer" and "LLM Application Developer" scores 0.560. Labelling each role's angle and spending one slot per direction did.
- Banning stock resume phrasing in the prompt did not hold; detecting it in code and asking again by name did.
Technologies
- Next.js 16 (App Router, cacheComponents), React 19, TypeScript
- Tailwind CSS v4 and shadcn/ui on Radix
- LangGraph and LangChain for the generation and import graphs
- OpenRouter Decisions for per-record scoring
- Qdrant for semantic search over personal history
- Neon Postgres with Drizzle ORM
- Better Auth with manual account approval
- Bunny CDN with signed URLs for generated files
- Vitest and Testing Library
Images
