Most “resume vs job” tools give you a number like 73% and nothing else. You cannot tell why it is 73, what to fix, or whether the tool simply counted keywords. I built ResumeAI to do the opposite: an AI resume matcher where every requirement of the job gets a status, the evidence behind it and a concrete next step, and the overall score can be traced back to those parts.

It is also a complete SaaS: profiles, resume builder with templates and exports, job tracking, Free and Pro plans, Stripe billing and an admin dashboard. This article focuses on the parts that are hard to get right with AI: trustworthy scoring, privacy, and stopping the model from inventing things.

The project at a glance

What it does One master profile → tailored resumes → explainable match against a job → improvement, cover letters, interview prep, application tracking
Backend Java 21, Spring Boot 4, Spring Security (JWT access and refresh tokens), JPA/Hibernate, Flyway, Apache Tika for CV import
Front end Angular 17 with standalone components and signals; a Chrome extension that captures job postings
AI Ollama running locally: a chat model for extraction and an embedding model (nomic-embed-text) for semantic matching
Data PostgreSQL 16 with the pgvector extension as an embedding cache
Operations Docker Compose, Nginx with a strict Content-Security-Policy, health checks with an AI “degraded” state, structured JSON logs, CI with unit and integration tests
AI resume matcher result in ResumeAI: an 82% match score with category scores and the next steps to take
An explainable match: overall score, weighted categories and what to do next (fictional candidate and job).

The problem with keyword scores and with pure AI scores

There are two common ways to match a resume to a job, and both fail users:

Recruiters reason differently: they look for evidence. A skill used in a dated role counts. A skill only listed under “Skills” is a claim. A missing must-have is a deal-breaker no matter how good the rest is. I designed the engine around that reasoning.

Architecture of the AI resume matcher

browser ──► Nginx (Angular app, /api proxy, CSP) ──► Spring Boot API ──► PostgreSQL 16 + pgvector
                                                             └──────────► Ollama (chat + embeddings)

Running the models through Ollama on the same infrastructure was a deliberate choice: a CV contains a person’s name, contact details and work history. With local models, that text never goes to a third-party AI provider. The trade-off is hardware cost and smaller models, which is why the engine does as much as possible without AI.

Step 1: Turn the job description into structured requirements

When a user pastes a job description, the chat model extracts a list of requirements, each with a type (skill, tool, experience, education, certification, language), a priority (must-have or nice-to-have) and details such as minimum years. The user can edit the list or add their own, and a re-analysis keeps the ones they added. From this point on, matching is done against structured data, not against free text.

Step 2: Exact matching first, with no AI at all

Layer 1 is deterministic. It is fast, free and gives the same answer every time, so every requirement it can resolve never reaches a model:

Step 3: Semantic matching only where it helps, with guardrails

Requirements that exact matching cannot resolve go to Layer 2. The requirement and the resume’s evidence sentences are embedded in one batch and compared by cosine similarity. Embeddings are good at “built REST APIs for a payments platform” vs “experience designing backend services”, and dangerous elsewhere, so the layer has hard rules:

Embeddings are cached in PostgreSQL with pgvector, so the same sentence is not embedded twice.

Step 4: Decide what counts as evidence

Layer 3 is the heart of the “explainable” part. Each requirement ends up with one of four statuses:

Status Meaning
MATCHED Demonstrated in an experience, project, degree, certificate or language, and the minimum years or level is met
PARTIAL Demonstrated but not fully: fewer years, lower level, related field, expired certificate, or related wording
UNVERIFIED Claimed in the skills list or summary, but nothing on the resume shows it being used
MISSING No evidence at all
ResumeAI requirement list with matched, partial and unverified statuses and the evidence for each
Every requirement gets a status, a score and the evidence behind it.

“Java” in the skills list does not mean “five years of Java”. That single rule is what makes the score feel fair to recruiters and honest to candidates.

Step 5: A score you can trace

Layer 4 turns statuses into numbers in a way a user can follow:

Every stored result records a scoringVersion, so results calculated under different rules are never compared as if they were the same. The engine runs on an immutable snapshot of the resume, which makes it pure and easy to unit-test with “golden” scenarios, and no database transaction is held open while a model is thinking.

Step 6: Guard against invented claims

The app also writes: tailored resume sections, translations between French and English, cover letters and interview answers. Language models are tempted to “improve” a resume by adding a technology or a number that is not true. ResumeAI checks generated text against the user’s own resume:

Step 7: Make it a product, not a demo

Step 8: Production hardening

Lessons for anyone adding AI to a product

Want AI in your product, done this way?

The same approach works for any “match, score or extract” problem: candidates and jobs, leads and offers, documents and checklists, support tickets and answers. I can design the scoring so your users trust it, run the models on your own infrastructure when privacy matters, and build the product around it.

Official resources for AI resume matcher

Documentation and references for AI resume matcher: