jobhunt-platform/docs/worker-tasks/poc-tasks.md
hermes b77c8b0044 Bootstrap: README, ADR-0001 (LLM at decision points), data model, API contract, worker task cards
Design foundation for POC. Monorepo: FastAPI+Postgres backend, Vue 3 frontend,
Python packages (llm-gateway, artifacts, connectors). Approval gate and
token budgets are architectural constraints per measured prototype findings.
2026-07-30 17:56:07 +00:00

55 lines
5.5 KiB
Markdown

# Worker tasks for POC (dispatch cards)
Each card is self-contained. Rules for ALL workers:
- Work only inside your assigned paths. Do NOT touch other modules or top-level files.
- Python 3.13, `uv` for env management. Node via `npm`. No SQLAlchemy, no Alembic, no Terraform (owner rules).
- Run your own tests before reporting done. Report what actually ran: exact test command + pass/fail counts.
- Output style: plain code, no em dashes anywhere, English.
- Do not invent external services. LLM calls go through packages/llm-gateway and must have a `mock` mode defaulting to on when no API key env var is present.
## T1: apps/api backend core
Paths: `apps/api/**`, `apps/api/tests/**`
Build the FastAPI backend per `docs/api-contract.md` + `docs/data-model.md`.
- PostgreSQL via psycopg (v3), repository pattern in `apps/api/db/`. `schema.sql` + tiny migration runner that applies `migrations/NNN_*.sql` in order, tracked in a `schema_migrations` table.
- Connection from env `DATABASE_URL` (default `postgresql://jobhunt:jobhunt@localhost:5433/jobhunt`).
- Pydantic schemas for all contract payloads. State machine module `apps/api/statemachine.py` implementing the transition table exactly, guard violations -> 409.
- Approval enforcement as described: hash check + expiry at send time. Send transport: pluggable `Transport` interface, default `EchoTransport` that records payload and marks sent (no real email in POC).
- LLM usage via `packages.llm_gateway` (add repo root to pythonpath in pyproject). api-assist + critique endpoints must pass through gateway task runner with task names `cv_assist` and `cl_critique`, and must work with gateway mock mode.
- Tests with pytest: state machine transitions (happy + 409), approval gate (confirm/hash-match/expiry), at least one API-level happy path test using FastAPI TestClient with a test database (transaction rollback or truncate fixtures; must not require a live server).
Done = `cd apps/api && . .venv/bin/activate && pytest -q` green locally (they may start postgres via `docker compose up -d postgres` first).
## T2: packages/artifacts + packages/llm-gateway
Paths: `packages/artifacts/**`, `packages/llm-gateway/**`, plus one shared file `docker-compose.yml` (add postgres service: postgres:16, user/pass/db jobhunt, port 5433->5432, volume) and root `.env.example`.
### artifacts
- Port the CV-render approach from `/opt/data/home/cv_work/build_cv.py` (read it; it uses fpdf2). Make `render_cv_pdf(profile: dict, sections: list[dict]) -> bytes`. Use Jinja2 for layout data prep, fpdf2 for PDF. Swedish characters must render correctly (unicode font: bundle DejaVu or use a TTF from system; if none available, ensure the test asserts text extraction equals input exactly).
- `render_cover_letter(text: str, profile: dict) -> bytes` simple.
- `hash_bytes(b: bytes) -> str` (sha256 hex).
- Versioning helpers: `next_version(existing: list[int]) -> int`.
- Tests: render a sample profile with `å ä ö Å Ä Ö` in name/summary, assert PDF bytes start with `%PDF`, assert content hash stable for same input.
### llm-gateway
- Async-first client with provider config from env: `LLM_PRIMARY_BASE_URL`, `LLM_PRIMARY_KEY` (or `OLLAMA_API_KEY`), `LLM_PRIMARY_MODEL` (default glm-5.2), optional `LLM_FALLBACK_*` (opencode). NO paid-provider fallback may be configured for cheap task classes; add an assertion test that config rejects or warns on that.
- `run_task(task: str, prompt: str, schema: dict | None = None) -> dict` returns parsed JSON when schema given (validate with pydantic/jsonschema), writes telemetry row via injected sink (for POC: callable), records tokens+model+duration. Retry policy: max 2 retries on 429/5xx, then fallback provider, then raise.
- Mock mode: when no key set, return deterministic canned outputs per task (defined in `mock.py`) so API tests run offline.
- Tests for: mock determinism, schema validation failure path, budget guard (per-task max tokens configurable, over-budget -> exception before call).
## T3: apps/web frontend shell
Paths: `apps/web/**` (+ root README quick-start adjustment if port assumptions change).
Vue 3 + Vite + TypeScript + Pinia + vue-router + Tailwind. Tabs per README:
1. CV: profile form (name/email/phone/location/headline/summary) + section list editor (add/edit/delete sections with kind select, title/org/dates, bullet list editor with per-bullet AI-assist button calling `POST /profile/sections/{id}/ai-assist`, suggestions shown with accept/dismiss). "Render CV" button -> posts to /profile/render-cv, shows returned URL iframe/download link.
2. Research: simple table of postings (GET /postings) with company/title/location/source/fetched_at; "Add by URL" input posting to /postings; "Score" button per row -> shows score+rationale popover.
3. Applications: kanban board grouped by state (columns per data-model states), cards show company/title/score; drag between columns -> `POST /applications/{id}/transition`, on 409 revert and show toast with reason.
4. Application detail: route `/applications/:id` — posting info, state, artifacts list, cover-letter editor (textarea save -> critique list rendered as cards with severity), approval widget: select artifact + action -> request approval -> "I confirm" button -> send button (enabled only after confirm; show 409 errors).
- API base via `import.meta.env.VITE_API_BASE` default `http://localhost:8000/api`.
- Tailwind configured locally (no CDN). Vitest smoke tests: router renders tabs; kanban groups by state from fixture; approval widget disables send until confirmed (component test with mocked api module).
Done = `npm run build` succeeds and `npm run test` green.