Full-stack application for scoring and ranking candidate resumes against a job description. The backend combines similarity metrics, NLP and NER, optional classical ML classifiers, and optional transformer-based models. Results include per-method scores, explanations, optional LLM-assisted JD expansion and narrative explanations, and lightweight batch-level fairness statistics. The API is implemented in Flask; the UI is Next.js.
- Multipart upload of multiple resumes (PDF, DOCX, DOC, TXT) with configurable size and count limits
- Parallel execution of selected scoring methods
- Ranked output with combined score, per-algorithm breakdown, and skill hints where NER or similarity details are available
- Optional Groq (Llama 3 8B) integration for structured job-description expansion and richer explanations
- CSV export via the UI or
POST /api/export-results - Optional academic training and evaluation workflow under
/api/academic/* - Legacy API at
/api/*, extended routes at/v2/api/*, Prometheus-compatible metrics, rate limiting, and structured logging with request IDs
| Home | Prediction |
|---|---|
![]() |
![]() |
| Algorithm selection | Results (multi-algorithm) |
|---|---|
![]() |
![]() |
| Results (table) |
|---|
![]() |
Client (multipart: resumes, job description, methods)
|
v
+-------------------+
| Request validation | limits: app_config (file size, count, JD length)
+---------+---------+
|
v
+------------------+ optional: expand_jd() (Groq, cached by JD hash)
| FileProcessor |
+---------+---------+
|
v
+---------------------------+
| AlgorithmManager (parallel)|
| cosine, bm25/jaccard, ner, |
| transformers, traditional ML|
+-------------+-------------+
|
v
+------------------+
| Score combination | weighted strategies (see backend/core/score_combiner.py)
+---------+---------+
|
v
+------------------+ optional: explain_match() (Groq) or template text
| Response assembly |
+---------+---------+
|
v
Fairness fields, Prometheus metrics, X-Request-ID
- Cosine similarity: Term-based vector similarity between resume and job description text.
- BM25: Probabilistic ranking with term saturation and document length normalization (registered as both
bm25andjaccardfor backward compatibility). - NER (spaCy): Entity-oriented signals and extracted skill-like spans for matching and display.
- BERT / DistilBERT / SBERT: Encoder-based semantic similarity where enabled (models load on demand; heavy methods increase memory use).
- XGBoost, Random Forest, SVM, Neural Network: Optional supervised-style scorers when models are available or academic mode is used.
Default per-method weights for a weighted average are defined in backend/core/score_combiner.py; the API can use other combination strategies where supported.
- Python 3.11+ (see
backend/pyproject.toml) - uv for Python dependencies
- Node.js 18+ for the frontend
- Sufficient RAM if enabling large transformer models (CPU-only PyTorch is supported; GPU is optional via configuration)
cd backend
uv sync
uv sync --all-groups # include pytest and other dev dependenciesRun the API (default port 5000):
uv run python app.pyProduction-style:
uv run gunicorn app:app --workers 2 --bind 0.0.0.0:5000Add packages with uv add <package>, not pip install. Legacy backend/requirements.txt redirects to this workflow.
spaCy model (if you use NER or related paths):
uv run python -m spacy download en_core_web_smRedis is optional; if unavailable, caching in the v2 blueprint degrades gracefully.
cd frontend
npm install
npm run devApplication: http://localhost:3000 (ranking UI at /predict).
If the API is not on http://localhost:5000, set NEXT_PUBLIC_API_BASE in frontend/.env.local.
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Health and algorithm inventory |
| GET | /api/algorithms |
Algorithm metadata |
| GET | /api/positions |
Job role presets |
| POST | /api/process-resumes |
Rank resumes (multipart) |
| POST | /api/rank |
Same handler as process-resumes |
| POST | /api/validate-files |
Validate uploads without scoring |
| POST | /api/export-results |
JSON body with results; CSV export |
| GET | /api/processing-status/<id> |
Reserved; returns 501 (ranking is synchronous) |
| POST | /api/cancel-processing/<id> |
Reserved; returns 501 (no background jobs) |
Extended implementation (benchmarks, cache-aware paths, richer health) is also mounted under /v2/api/... (e.g. GET /v2/api/algorithm-benchmarks).
- Responses under
/api/*(excluding/v2prefix):X-API-Version: legacy - Responses under
/v2/api/*:X-API-Version: v2
| Variable | Required | Description |
|---|---|---|
SECRET_KEY |
Recommended in production | Flask secret |
GROQ_API_KEY |
Optional | Enables Groq LLM features (llama3-8b-8192 on the free tier) |
USE_LLM_JD_EXPANSION |
Optional (false) |
Structured JD expansion before scoring |
USE_LLM_EXPLANATIONS |
Optional (false) |
LLM-generated three-sentence explanations |
MAX_FILE_SIZE_MB |
Optional (10) |
Per-file upload limit |
MAX_FILES_PER_REQUEST |
Optional (10) |
Max resumes per request |
MAX_JD_LENGTH |
Optional (10000) |
Max job description length (characters) |
RATELIMIT_ENABLED |
Optional (true) |
Toggle Flask-Limiter |
PHOENIX_ENABLED |
Optional | Hint for local Arize Phoenix instrumentation |
DEBUG / FLASK_ENV |
Optional | Development vs production |
DEBUG_METRICS |
Optional | Set to 1 to expose /metrics when Flask debug reloader is on |
Central limits and feature flags live in backend/app_config.py (import path avoids clashing with the config/ package).
cd backend
uv run pytest --cov=. --cov-report=term-missingPrometheus exposition is registered at GET /metrics on the API port when the app is not running with Flask’s debug reloader (or when DEBUG_METRICS=1). Custom series include resume_ranking_requests_total, resume_ranking_latency_seconds, resume_ranking_score_distribution, and llm_expansion_calls_total.
- Batch responses may include
fairness_warning,score_variance, andscore_std_dev. - Rate limiting via Flask-Limiter (default in-memory; configure Redis-backed storage for multi-worker deployments if needed).
- Structured JSON logging via
structlogandX-Request-IDon responses.
From the repository root:
docker compose up --buildThe API service uses two Gunicorn workers and mounts backend/uploads for persistent uploads. Default variables come from committed backend/.env.example. Optionally copy it to backend/.env and edit (cp backend/.env.example backend/.env); Compose merges optional .env on top when present.
The stack includes three services:
- api: Flask backend with Gunicorn (port 5000)
- frontend: Next.js application (port 3000)
- redis: Cache backend for v2 API (port 6379)
See CONTRIBUTING.md for development setup, coding standards, and the pull request process. For a deeper look at the system design and algorithm pipeline, see docs/ARCHITECTURE.md.
- Fork the repository and create a branch for your change.
- Keep commits focused; match existing style and run tests for backend changes.
- Open a pull request with a clear description of behavior and risk.
This project builds on the broader ecosystem of open tools, including Hugging Face Transformers, Sentence Transformers, spaCy, scikit-learn, and PyTorch.
This project is licensed under the MIT License; see LICENSE.




