AI-assisted job-hunt platform. Human-approved pipeline: discover -> score -> approve -> draft -> send. LLM at decision points only, deterministic state machine owns the flow.
packages/artifacts: - render_cv_pdf(profile, sections) -> bytes: data-driven CV PDF generation using fpdf2 with bundled DejaVuSans TTF for unicode (Swedish chars tested). Jinja2 template for layout data prep, adapted from build_cv.py approach. - render_cover_letter(text, profile) -> bytes: simple cover letter PDF. - hash_bytes(b) -> str: sha256 hex digest. - next_version(existing) -> int: version numbering helper. - 16 tests, all passing: PDF validity, Swedish characters, hash stability, cover letter rendering, hash correctness, version logic. packages/llm-gateway: - Async-first Gateway class with provider config from env. - Mock mode default when no API key env present (deterministic canned outputs per task name, defined in mock.py). - Telemetry sink injectable (async or sync callable, receives TelemetryRow). - Budget guard raises BudgetExceeded BEFORE any provider call is made. - Retry policy: max 2 retries on 429/5xx, then fallback provider for STRONG tasks only. CHEAP tasks never use fallback (paid provider protection). - Schema validation via jsonschema; SchemaValidationError on mismatch. - Task class routing: CHEAP (score, extract, cv_assist) vs STRONG (critique, cl_critique, research). Model routing per task class. - Paid provider detection heuristic; warns on paid fallback config. - 31 tests, all passing: mock determinism, schema pass/fail, budget guard (mock + real mode), telemetry sink (async/sync/none), config from env, provider calls with mocked HTTP (retry, fallback, no-fallback-for-cheap). docker-compose.yml: - postgres:16 service, user/pass/db = jobhunt, host port 5433->5432, named volume jobhunt_pgdata, healthcheck. .env.example: - DATABASE_URL, LLM provider config (primary + fallback), task budgets, API and web settings. |
||
|---|---|---|
| apps | ||
| docs | ||
| packages | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| README.md | ||
Jobhunt Platform
An AI-assisted job-hunt platform where you stay in control. The system discovers jobs, scores them against your profile, drafts application material and prepares sends — but nothing external ever happens without your explicit approval.
Open source. Self-hosted. Single-user first, multi-user later.
Core ideas
- Approval gate, architecturally enforced. Every external action (send email, submit application) requires a server-side confirmed
Approvalreferencing the exact artifact hash. No approval row, no send. This is a hard constraint, not a style guide. - State machine owns the flow, LLM answers questions inside it. Pipeline transitions live in a table. The LLM scores, extracts, critiques and suggests — it never picks the next step. Deterministic orchestration, probabilistic judgment.
- Per-task model routing with token budgets. Cheap model for extraction/scoring, strong model for prose review, budgets enforced per task so the bill stays boring.
- The user drafts, the system reviews. Measured reality: human-drafted prose outperforms full AI drafts. Default cover-letter flow is user-writes, AI-reviews with tracked suggestions.
Architecture
apps/web Vue 3 + Vite + Tailwind — tabs: CV editor, Research, Applications (kanban), Application detail
apps/api FastAPI + PostgreSQL — REST API, state machine, scheduler, approval enforcement
packages/
llm-gateway/ Model routing, per-task budgets, structured JSON IO, retry/fallback policy
connectors/ Job source adapters -> normalized JobPosting (LinkedIn read-focused, jobindex, paste-a-URL)
artifacts/ CV + cover-letter generation: Jinja templates -> PDF (fpdf2), versioning, hashing
docs/ ADRs, data model, API contract, worker task cards
Pipeline
discovered -> scored -> approved -> drafting -> sent -> interviewing -> offer -> closed
\-> rejected (by user) \-> expired
External comms are only possible from approved/drafting states, and only with a matching confirmed Approval.
Quick start (POC)
cp .env.example .env # add LLM provider keys
docker compose up -d postgres
cd apps/api && uv venv .venv && . .venv/bin/activate && uv pip install -e .
pytest # backend tests
uvicorn app.main:app --reload
cd apps/web && npm install && npm run dev
Status
POC scaffolding in progress. See docs/ for the design.