#cv-screening #hr-tech #ai-recruitment #fastapi #react #docker
#anthropic #claude #machine-learning #fairness #automation
Upload a CV (PDF, DOCX, or image) → Claude Haiku 4.5 parses it into structured JSON → ML model predicts Invite or Reject → stored in PostgreSQL → visible in a React dashboard.
Built for LuxTalent Advisory Group S.A. — production-grade, fairness-audited, Docker-ready.
┌──────────────────────────────────────────────────────────────────┐
│ React Dashboard (:3000) │
│ Upload CV · Browse Candidates · Stats & Filters │
└──────────────────────────┬───────────────────────────────────────┘
│ REST API (/api/v1/)
▼
┌──────────────────────────────────────────────────────────────────┐
│ FastAPI Backend (:8000) │
│ │
│ 1. Text extraction (pdfplumber / python-docx) │
│ 2. Anthropic Claude Haiku 4.5 → Universal JSON schema │
│ 3. ML model → Invite / Reject + confidence score │
│ 4. Store in PostgreSQL │
└──────────────────────────┬───────────────────────────────────────┘
│
┌──────┴──────┐
│ PostgreSQL │
│ (:5432) │
└─────────────┘
Stack : FastAPI · Anthropic Claude (claude-haiku-4-5) · scikit-learn · PostgreSQL · React + Vite + TypeScript + Tailwind · Framer Motion · Docker Compose
git clone https://github.com/ForgedEmir/Filtrant-CV-Pre-Screening.git
cd Filtrant-CV-Pre-Screening
cp .env.example .envOpen .env and set your Anthropic API key:
ANTHROPIC_API_KEY=sk-ant-...
docker compose up --buildThis starts two containers:
| Container | Port | Description |
|---|---|---|
backend |
8000 | FastAPI + auto-creates DB tables on first boot |
postgres |
5432 | PostgreSQL 16, data persisted in a named volume |
Wait for: backend | INFO: Application startup complete.
cd frontend
npm install
npm run devOpen http://localhost:3000.
- Go to the Upload tab → drag & drop any PDF/DOCX CV
- System returns
InviteorRejectwith confidence within seconds - Switch to Candidates to browse all processed CVs with filters
Or via curl:
curl -X POST http://localhost:8000/api/v1/upload \
-F "file=@path/to/cv.pdf"
curl http://localhost:8000/api/v1/healthbackend/ml/model.joblib is gitignored and must be generated. Without it, every candidate returns pending.
# 1 — Export labelled candidates
curl http://localhost:8000/api/v1/candidates/export.csv -o backend/ml/candidates_export.csv
# 2 — Train (compares 6 classifiers, saves best)
docker compose exec backend python /app/ml/train.pytrain.py compares 6 classifiers (LogisticRegression, RandomForest, GradientBoosting, SVM, HistGradientBoosting, XGBoost), selects the best by cross-validated F1 score, optimises hyperparameters with RandomizedSearchCV, and saves the pipeline.
The system includes a dedicated fairness module (backend/src/services/fair_wrapper.py) that:
- Tracks prediction distributions across demographic groups
- Audits for disparate impact
- Generates fairness reports
See docs/SPECS_WP2.md for the full fairness methodology.
Claude is called with temperature=0 and a hardened system prompt for fully deterministic output. Tested across 3 runs per CV — all fields identical across every run.
details in the original analysis
All endpoints prefixed /api/v1/.
| Method | Path | Description |
|---|---|---|
POST |
/upload |
Upload CV (multipart) → parse → predict → store |
GET |
/candidates |
Paginated list with filters (recommendation, date_from, date_to) |
GET |
/candidates/{id} |
Full candidate record including raw CV JSON |
GET |
/candidates/export.csv |
ML-ready CSV export (features + recommendation) |
GET |
/health |
DB status + model file presence |
├── docker-compose.yml # backend + postgres
├── .env.example # Copy to .env
│
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── ml/
│ │ ├── train.py # 6 classifiers → save best
│ │ ├── setup_data.py # Raw data import helper
│ │ ├── train_fair.py # Fairness-augmented training
│ │ └── model.joblib # Trained model (gitignored)
│ └── src/
│ ├── main.py # FastAPI entry point
│ ├── config.py # Pydantic settings
│ ├── db.py # psycopg2 pool
│ ├── models/cv_schema.py # Universal JSON schema
│ └── services/
│ ├── extractor.py # PDF/DOCX/TXT → text + SHA-256
│ ├── llm_parser.py # Claude Haiku → structured JSON
│ ├── features.py # JSON → ML feature vector
│ ├── predictor.py # Load model, predict
│ ├── fair_wrapper.py # Fairness auditing
│ └── watcher.py # Auto-process incoming CVs
│
├── frontend/
│ ├── Dockerfile
│ ├── src/
│ │ ├── App.tsx
│ │ ├── api/client.ts # Typed API client
│ │ ├── types/cv.ts # TypeScript ↔ Pydantic
│ │ └── pages/
│ │ ├── DashboardPage.tsx # Stats + activity chart
│ │ ├── UploadPage.tsx # Drag-and-drop upload
│ │ └── CandidatesPage.tsx # Paginated list + detail modal
│ └── ... config files
│
├── data/
│ ├── incoming_cvs/ # Drop CVs → watcher auto-processes
│ ├── processed_cvs/ # Moved here on success
│ └── failed_cvs/ # Moved here on failure
│
└── docs/
├── SPECS_WP1.md # Client requirements (Work Package 1)
├── SPECS_WP2.md # Fairness audit & model redesign
├── ML_REPORT.md # Metrics, feature importance, v1 vs v2
└── ML_COMPLETE_GUIDE.md # Deep-dive on every ML algorithm
The backend polls data/incoming_cvs/ every 10 seconds. Drop any CV in → auto-processed → moved to processed_cvs/ on success or failed_cvs/ on failure. Configurable via WATCHER_INTERVAL env variable.
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY |
Yes | sk-ant-... from console.anthropic.com |
DATABASE_URL |
Yes | PostgreSQL connection string (Railway auto-sets this) |
ENV |
No | development (default) or production |
LOG_LEVEL |
No | INFO (default) |
WATCHER_INTERVAL |
No | File watcher poll interval in seconds (default: 10) |
VITE_API_BASE_URL |
Frontend | API base URL (defaults to http://localhost:8000) |
- New Railway project → add PostgreSQL plugin
- Add service from repo, root
backend/, Docker auto-detected - Set
ANTHROPIC_API_KEYin Railway env vars
- Import repo, root
frontend/(Vite auto-detected) - Set
VITE_API_BASE_URL=https://<your-railway-backend-url>
The app runs inside Docker, but your IDE needs local packages for IntelliSense.
cd backend
python -m venv .venv
# Windows
.venv\Scripts\pip install --only-binary=:all: fastapi uvicorn[standard] python-multipart httpx python-docx pdfplumber scikit-learn pandas numpy joblib pydantic pydantic-settings python-dotenv psycopg2-binaryUse Python 3.11.x — the Docker image is pinned to
python:3.11.12-slim.
The repo includes .vscode/settings.json — VS Code picks up the venv automatically.
# Stop containers (preserves data)
docker compose down
# Stop + wipe database
docker compose down -v| Resource | Description |
|---|---|
| LICENSE | MIT — free to use, modify, distribute |
Filtrant — Forked from Oliphantaupe/Filtrant-CV-Pre-Screening.
