An AI-powered job application assistant. Upload job postings, get a market analysis, compare your resume against the market, and generate per-posting application reports with fit scores, cover letters, resume rewrites, and interview prep.
Built with FastAPI, Next.js, OpenRouter (LLM), and Tavily (web search).
- Market Analysis — extracts structured data from job posting PDFs and synthesizes patterns across all postings (skills demand, seniority distribution, company profiles)
- Resume Gap Analysis — parses your resume and produces a triaged gap report comparing your skills to the market
- ATS Scorer — simulates keyword-based ATS filtering and shows exactly which keywords were found, missed, or semantically matched
- Application Advisor — generates a full report for any posting: fit score, resume adaptation advice, cover letter guidance, and interview prep
- Cover Letter Generator — two complete cover letter drafts (professional and conversational tones) tailored to the posting
- Resume Rewriter — rewrites your skills and experience bullets for the target posting with a changelog; no fabrication
- HTML Visual Reports — self-contained HTML report with fit score charts, skill match breakdown, and tabbed cover letter viewer
- Conversation Mode — interactive Q&A after report generation using full context (posting, resume, gap analysis, market summary)
- Python 3.11+
- Node.js 20+
- uv for Python dependency management
- A free OpenRouter API key
- A free Tavily API key
# Python
uv sync
# Frontend
cd frontend && npm installcp .env.example .envEdit .env and set your API keys:
OPENROUTER_API_KEY=your_key_here
TAVILY_API_KEY=your_key_here
Optional settings (with defaults):
APP_PASSWORD=changeme # Password to access the web UI
OPENROUTER_MODEL=google/gemini-3-flash-preview
LOG_LEVEL=info
Start the backend and frontend in separate terminals:
# Terminal 1 — API server (port 8000)
uv run uvicorn src.web.main:app --host 0.0.0.0 --port 8000 --reload
# Terminal 2 — Next.js dev server (port 3000)
cd frontend && npm run devOpen http://localhost:3000 and log in with your APP_PASSWORD.
All commands run from the project root. Pass -v / --verbose to any command for debug output.
Processes job posting PDFs in data/raw/jobs/ and generates a market report.
uv run python -m src.extract.market| Flag | Default | Description |
|---|---|---|
--jobs-dir PATH |
data/raw/jobs |
Directory containing job posting PDFs |
--out-dir PATH |
data/jobs |
Directory to save extracted JSON files |
--report PATH |
data/analysis/market_analysis.md |
Output path for market report |
--force |
off | Re-extract all postings even if JSON exists |
Re-runnable — skips postings already extracted unless --force is passed.
Parses your resume and produces a gap analysis against the market. Requires Phase 1.
uv run python -m src.analysis.gaps "path/to/resume.pdf"| Flag | Default | Description |
|---|---|---|
RESUME_PDF |
(required) | Path to your resume PDF |
--market-report PATH |
data/analysis/market_analysis.md |
Phase 1 market report |
--out-dir PATH |
data/resume |
Directory for resume JSON |
--gap-json PATH |
data/analysis/gap_analysis.json |
Output path for gap JSON |
--report PATH |
data/analysis/gap_analysis.md |
Output path for gap report |
--force |
off | Re-run even if outputs exist |
Scores your resume against a specific job posting using keyword matching.
uv run python -m src.analysis.ats_scorer data/jobs/some_posting.json| Component | Weight | Method |
|---|---|---|
| Required skills | 50% | Keyword + alias matching |
| Preferred skills | 20% | Keyword + alias matching |
| Experience years | 15% | Numeric comparison |
| Education | 10% | LLM-assisted |
| Section completeness | 5% | Presence check |
Verdicts: ≥75% → PASS, 50–74% → REVIEW, <50% → REJECT
Generates a full application report for a specific posting. Requires Phases 1 and 2.
# Use an existing Phase 1 JSON
uv run python -m src.advisor.advise data/jobs/some_posting.json
# Or provide a new PDF (extracted on the fly)
uv run python -m src.advisor.advise "data/raw/jobs/Some Posting.pdf"| Flag | Default | Description |
|---|---|---|
POSTING_FILE |
(required) | Job posting JSON or PDF |
--cover-letter / --no-cover-letter |
on | Generate 2-tone cover letter |
--rewrite / --no-rewrite |
on | Generate tailored resume rewrite |
--html / --no-html |
on | Generate HTML visual report |
--converse |
off | Enter interactive Q&A after report |
--force |
off | Re-extract posting even if JSON exists |
# Fast run — report only, no extras
uv run python -m src.advisor.advise data/jobs/some_posting.json \
--no-cover-letter --no-rewrite --no-htmlA Dockerfile and railway.json are included for one-command deployment to Railway.
The Docker build compiles the Next.js frontend to static files and serves everything from the FastAPI backend on a single port — no separate frontend server needed in production.
docker build -t fitforge .
docker run -p 8000:8000 --env-file .env fitforgefitforge/
├── .env.example # Environment variable template
├── pyproject.toml # Python dependencies (uv)
├── Dockerfile # Multi-stage build (Node + Python)
├── railway.json # Railway deployment config
│
├── data/
│ ├── raw/jobs/ # Input: job posting PDFs (not committed)
│ ├── jobs/ # Output: extracted posting JSON (Phase 1)
│ ├── analysis/ # Output: market and gap analysis (Phases 1 & 2)
│ └── resume/ # Output: parsed resume JSON (Phase 2)
│
├── reports/
│ └── advisor/ # Per-posting advisor reports (Phase 3)
│
├── eval/
│ ├── eval_config.json # Evaluation test cases
│ └── eval_report.md # Evaluation output
│
├── frontend/ # Next.js web UI
│ ├── app/
│ ├── components/
│ └── lib/api.ts # Typed fetch wrappers for all API routes
│
└── src/
├── shared/ # Config, schemas, LLM client, search, PDF, logging
├── extract/ # Phase 1: job posting extraction + market report
├── analysis/ # Phase 2: resume parsing, gap analysis, ATS scorer
├── advisor/ # Phase 3: advisor, cover letter, rewriter, HTML renderer
├── eval/ # Evaluation harness
└── web/ # FastAPI app, routes, auth, session state
All pipelines are idempotent — re-running without --force reuses cached JSON and skips repeated LLM/API calls.
| Phase | LLM Calls | Tavily Calls | Estimated Cost |
|---|---|---|---|
| Phase 1 (9 postings) | ~20 | ~18 | < $0.50 |
| Phase 2 (1 resume) | ~2 | 0 | < $0.05 |
| ATS Scorer (1 posting) | ~3 | 0 | < $0.02 |
| Phase 3 (1 posting, all extras) | ~10 | ~2 | < $0.20 |
Default model: google/gemini-3-flash-preview via OpenRouter. Override with OPENROUTER_MODEL in .env.