Skip to content

jeevan6996/joblens-ai

Repository files navigation

joblens-ai

Local-first LLM application for resume vs job description analysis.

What is included now

  • FastAPI service scaffold with /health endpoint
  • Streamlit shell page for API connectivity checks
  • SQLAlchemy 2 + Alembic migration setup
  • Initial test suite and dev tooling configuration
  • Docker + Docker Compose for reproducible local runs

Tech stack

  • FastAPI, Pydantic, SQLAlchemy 2, Alembic
  • Streamlit
  • Ollama (local model serving)
  • pytest, Ruff, mypy, bandit, pip-audit

Local development (recommended for fast iteration)

  1. Create and activate a virtualenv:
    • python3.11 -m venv .venv
    • source .venv/bin/activate
  2. Install dependencies:
    • python -m pip install -U pip && pip install -e .[dev]
  3. Run API:
    • uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
  4. Run UI (new terminal):
    • streamlit run app/ui/streamlit_app.py

Docker run (reproducible evaluator path)

  • Start stack:
    • docker compose up --build
  • API: http://localhost:8000/health
  • UI: http://localhost:8501

docker-compose.yml points API to host Ollama using http://host.docker.internal:11434.

Ollama setup (required for LLM endpoints)

  • Install Ollama and start it locally.
  • Pull recommended model for this machine class (M1 8GB):
    • ollama pull qwen2.5:3b
  • Optional quality model (slower):
    • ollama pull llama3.1:8b

Model selection

  • Default project model: qwen2.5:3b
  • You can switch models without code changes using env var OLLAMA_MODEL.
  • Example overrides:
    • OLLAMA_MODEL=qwen2.5:3b
    • OLLAMA_MODEL=qwen3.5:4b
    • OLLAMA_MODEL=llama3.1:8b

Tests and quality

  • All tests: pytest -q
  • Single file: pytest tests/integration/test_health.py -q
  • Single test: pytest tests/integration/test_health.py::test_health_endpoint_returns_expected_shape -q
  • Lint: ruff check .
  • Format: ruff format .
  • Type-check: mypy app tests
  • Security: bandit -r app && pip-audit

Pre-commit hooks (recommended)

  • Install hooks once per clone: pre-commit install
  • Run all hooks on demand: pre-commit run --all-files
  • Included hooks:
    • ruff-check
    • ruff-format
    • mypy

Database migrations

  • Create migration: alembic revision --autogenerate -m "describe change"
  • Apply migration: alembic upgrade head

Analyze endpoint (deterministic baseline)

Run this after starting the API:

curl -X POST "http://localhost:8000/analyze" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with Python, FastAPI, Docker, AWS, SQL, and production microservices experience.",
    "job_description_text": "Hiring backend engineer with Python, FastAPI, Docker, AWS, SQL, testing, and microservices skills."
  }'

The response includes:

  • total_score: weighted ATS-style score
  • categories: per-category score, weight, rationale, matched keywords
  • skill_gaps: missing, partial, and matched skill analysis
  • request_id, latency_ms, and version metadata

Analyze with files (.txt/.pdf)

curl -X POST "http://localhost:8000/analyze/upload" \
  -F "resume_file=@examples/resume.txt" \
  -F "job_description_file=@examples/jd.txt"

Upload behavior:

  • Allowed file types: .txt, .pdf, .docx
  • Max file size is controlled by MAX_UPLOAD_BYTES (default 2000000)
  • Raw input text is not stored in DB; only derived scores and SHA-256 hashes are saved

Flexible analyze (text/file mix per input)

You can provide each side as either text or file:

  • Resume: resume_text or resume_file
  • JD: job_description_text or job_description_file

Endpoint:

  • POST /analyze/flexible (multipart form)

This powers the Streamlit UI flow where users choose paste or upload independently for resume and JD.

Rewrite endpoint (LLM + fallback)

curl -X POST "http://localhost:8000/rewrite" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with FastAPI and Docker experience building production APIs.",
    "job_description_text": "Need backend engineer with FastAPI, Docker, AWS, and strong communication.",
    "bullets": ["Built API endpoints", "Managed deployment process"]
  }'

Behavior:

  • Uses Ollama model from OLLAMA_MODEL.
  • Enforces structured JSON output validation.
  • On timeout/invalid model output, returns deterministic fallback bullets with warnings.

Summary endpoint (LLM + fallback)

curl -X POST "http://localhost:8000/summary" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with FastAPI and Docker experience building production APIs.",
    "job_description_text": "Need backend engineer with FastAPI, Docker, AWS, and strong communication.",
    "target_role": "Senior Backend Engineer"
  }'

Interview endpoint (LLM + fallback)

curl -X POST "http://localhost:8000/interview" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with FastAPI and Docker experience building production APIs.",
    "job_description_text": "Need backend engineer with FastAPI, Docker, AWS, and strong communication.",
    "target_role": "Senior Backend Engineer",
    "question_count": 9
  }'

Both endpoints validate LLM JSON outputs and fall back to deterministic responses when needed.

Full report endpoint (one call)

curl -X POST "http://localhost:8000/report" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with FastAPI and Docker experience building production APIs.",
    "job_description_text": "Need backend engineer with FastAPI, Docker, AWS, and strong communication.",
    "target_role": "Senior Backend Engineer",
    "bullets": ["Built API endpoints", "Managed deployment process"],
    "question_count": 9
  }'

The /report response combines:

  • deterministic analysis score + gaps
  • rewritten bullets
  • tailored summary
  • interview questions
  • aggregated warnings and metadata

Flexible full report (paste or upload)

Use multipart form for mixed inputs (resume_text or resume_file, and job_description_text or job_description_file):

curl -X POST "http://localhost:8000/report/flexible" \
  -F "resume_file=@examples/resume.txt" \
  -F "job_description_file=@examples/jd.txt" \
  -F "target_role=" \
  -F "bullets_text=" \
  -F "question_count=9"

Behavior:

  • If target_role is blank, the API infers a role from JD text.
  • If bullets_text is blank, the API infers bullets from resume text.

UI flow (fast + reliable)

  • Provide only Resume + JD (paste or upload).
  • Click Analyze Match to run deterministic scoring plus lightweight LLM insights.
  • Use tabs for on-demand LLM features:
    • Application Optimizer (/optimize) for summary + bullets + positioning notes.
    • Interview (/interview) for role-aligned questions.
  • Export current app state from Export tab:
    • joblens_export.json
    • joblens_export.md

Analyze insights endpoint (lightweight LLM)

curl -X POST "http://localhost:8000/analyze/insights" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with FastAPI and Docker experience building production APIs.",
    "job_description_text": "Need backend engineer with FastAPI, Docker, AWS, and strong communication.",
    "total_score": 64,
    "skills_to_polish": ["testing"],
    "core_topics": ["Skills Match"]
  }'

Optimize endpoint (single meaningful LLM feature)

curl -X POST "http://localhost:8000/optimize" \
  -H "Content-Type: application/json" \
  -d '{
    "resume_text": "Backend engineer with FastAPI and Docker experience building production APIs.",
    "job_description_text": "Need backend engineer with FastAPI, Docker, AWS, and strong communication."
  }'

Flexible variant for text or upload inputs:

  • POST /optimize/flexible

Interview endpoint (on-demand)

curl -X POST "http://localhost:8000/interview/flexible" \
  -F "resume_text=Backend engineer with API delivery experience" \
  -F "job_description_text=Need backend engineer with testing and communication skills" \
  -F "question_count=6"

The Streamlit UI disables action buttons while requests are in progress to prevent duplicate calls.

Runtime notes for Apple Silicon (8GB)

  • The app is tuned for reliability over speed with local models.
  • Recommended defaults in .env:
    • OLLAMA_MODEL=qwen2.5:3b
    • REQUEST_TIMEOUT_SECONDS=90
    • OLLAMA_MAX_RETRIES=2
  • Typical local latency (varies by prompt size):
    • Deterministic analyze: sub-second
    • Match insights: ~8-25s
    • Optimizer/interview: ~15-60s
  • For portfolio screenshots, wait for Insight status: AI-generated insights when possible.

History endpoints

  • GET /history?limit=20 - list recent runs.
  • GET /history/{request_id} - detailed run output, breakdown, hashes, and LLM metadata.

Cleanup

  • Leave virtualenv: deactivate
  • Remove local env and caches:
    • rm -rf .venv .pytest_cache .mypy_cache .ruff_cache dist build *.egg-info
  • Stop containers and remove volumes:
    • docker compose down -v

About

AI-powered resume-to-job matcher with ATS scoring, skill-gap analysis, and LLM-based rewrite suggestions.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages