← Work
Client projectRAG, Graph RAG & RetrievalAI Backend EngineeringLocal LLM, GPU & Inference

Job Matching — AI Recruitment Platform

A French-language recruitment platform that turns CVs and assessment quizzes into structured profiles, ranks every candidate against a job description with an explainable score, and recommends the training that would close each candidate's gaps.

Role
AI engineer — extraction, matching and retrieval
Job Matching — AI Recruitment Platform interface
01

The problem

Keyword matching on CVs fails in both directions: it rejects strong candidates who describe their experience in different words, and it passes weak ones who happen to mirror the job ad. Recruiters end up reading every CV anyway, so the filter saves nothing. And a score on its own is not actionable — a recruiter needs to know why a candidate scored what they did, and what would fix it.

02

Architecture

01

Ingestion — CV and quiz PDFs upload per department and return immediately; extraction runs as a background task while the UI polls a processing_status field. Text comes out via PyPDF2, then an LLM turns it into a structured profile: education, experience, skills, languages, and per-category quiz scores.

02

Extraction fallback — when the model returns something unusable, a heuristic section parser and a direct regex quiz parser take over. A bad parse degrades the profile rather than losing the candidate.

03

Matching — the job description PDF is extracted into structured criteria, then every candidate in scope is evaluated by the model as a French recruiter would: a 0–100 compatibility score with strengths, gaps, matching skills, a written analysis and recommendations. A failed call on one candidate is caught and skipped rather than aborting the batch.

04

Training recommendations — TF-IDF and cosine similarity over a training catalogue, run against each candidate's identified gaps. Top three programmes above a 0.1 relevance threshold, returned with objectives, duration and cost.

05

Semantic search — profiles embedded into Qdrant (1536-dim OpenAI or 768-dim Ollama), searched by cosine similarity in plain French, with results re-hydrated from MongoDB and filtered to the caller.

06

Platform — FastAPI with MongoDB as the source of truth, JWT auth with bcrypt and account lockout, per-user data isolation, and a statically exported Next.js frontend served by nginx with no Node runtime in production.

Job Matching — AI Recruitment Platform architecture
03

Key decisions

Chose

TF-IDF for training recommendations

Over

Asking the LLM to pick the courses

Because

Matching a candidate's gaps to a fixed catalogue is similarity, not reasoning. TF-IDF is faster, free, deterministic and explainable — and a recruiter can see exactly why a course was suggested. The LLM is reserved for the part that genuinely needs judgement: evaluating the candidate.

Chose

MongoDB as the source of truth, Qdrant only for vectors

Over

Serving search results from the vector store payload

Because

Embeddings go stale, and the collection is rebuilt whenever the embedding model changes — switching between 1536-dim and 768-dim models drops every vector. Re-hydrating each hit from Mongo means search can be re-indexed at any time without the results going wrong in the meantime.

Chose

Per-candidate error isolation inside the batch

Over

Letting a failed call fail the run

Because

Matching runs across a whole department. One malformed CV or one timed-out call should cost you one candidate, not the other forty — and the recruiter should still get a ranked list they can act on.

Chose

Fire-and-forget ingestion with a polled status field

Over

Processing inside the upload request

Because

LLM extraction on a multi-page CV takes far longer than a request should hold open. Returning a candidate id immediately and surfacing processing → completed / error in the UI keeps uploads instant and makes failures visible instead of silent.

Chose

Runtime-swappable model provider

Over

Compiling the provider choice in

Because

Recruitment data is personal data. Being able to move from a hosted API to a self-hosted model without a redeploy is what makes an on-premise conversation possible at all.

04

The hard part

Making the score defensible. An opaque number is unusable in recruitment — a recruiter has to justify a rejection, and "the model said 43" is not a justification. The scoring prompt was constrained to a fixed rubric (70–100 strong fit, 40–69 partial, 0–39 weak) and required to return its reasoning as structured fields rather than prose: matching skills, gaps, strengths, and specific recommendations. Those gap fields then feed the TF-IDF training lookup, so the same evidence that produced the score produces the remedy. The output stopped being a verdict and became an argument the recruiter can read, check and overrule.

05

Results

0–100
Explainable compatibility score per candidate
3
Ranked training programmes per candidate
2
LLM providers switchable at runtime, hosted or on-prem
06

Stack

FastAPIMongoDBQdrantOpenAI gpt-4oOllamascikit-learnTF-IDFNext.js 15nginxDockerJWT
07

Related field notes

smoke
“Innovation distinguishes between a leader and a follower.”

Need an AI system that holds up in production? Tell me what breaks today and I'll tell you what I'd build.

Call Me now !

mail

abdelmajidzaddi088@gmail.com

address

Mohammedia, Morocco

phone

+212 695 557 631