# v1.0 worker dispatch cards Global rules for ALL workers (same as POC, still binding): - Work only inside your assigned paths. Never touch other modules. - Python 3.13 + uv. Node 22 + npm. NO SQLAlchemy/Alembic/Terraform. No em dashes anywhere. English code and docs. - LLM only via packages/llm-gateway; write prompts against mock mode first, real keys optional. Budget guards stay on. - The approval gate semantics from ADR-0001 MUST NOT be weakened. New send transports go through the same gate. - DinD sandbox: docker compose networking between containers works; host port publishing does NOT. DB tests run via `docker compose run --rm api-test` pattern (see README quick start). Image COPY pitfall: `COPY dir dest` flattens; use `COPY dir ./dir`. - Run your tests; report exact pass/fail counts. Branch naming: feat/Wn-. Push branch when green. ## W1: packages/connectors Paths: `packages/connectors/**` - `Connector` protocol: `fetch(query: SearchQuery) -> list[RawPosting]`; normalizer to the job_posting shape (source, external_id, url, company, title, location, description, raw). - `arbetsformedlingen.py`: official Platsbanken API (https://jobsearch.api.jobtechdev.se/search?q=... free, no key, GET, header accept json). Map fields (headline->title, employer.name->company, webpage_url->url, description.text->description). Params: q, region (Malmö = Skåne län filter), limit. Polite user-agent. - `generic_url.py`: fetch a posting URL, strip boilerplate (simple readability: prefer
/
, drop nav/footer/script/style), return title/company guesses + clean description text. Must NOT follow Cloudflare-challenge sites (detect challenge pages -> raise UnsupportedSite). - Dedupe helper: `(source, url)` + external_id keying. - Tests: VCR-style recorded fixtures (ship JSON/HTML fixtures in tests/fixtures; NO live network in tests). Cover AF mapping, generic extractor on a messy HTML fixture, challenge-page detection, dedupe. - README.md per package: usage + mock examples. ## W2: apps/api v1 features Paths: `apps/api/**` (extend, do not rewrite; keep existing 47 tests green) Migrations `002_followups.sql`: add `follow_up_after_days int` (default 7), `last_activity_at timestamptz`, `follow_up_snoozed_until date` on `application`; `interview_prep_artifact_id uuid null` on application. New endpoints (contract addition file `docs/api-contract-v2.md` first, then implement): - `POST /cv/import` body `{filename, content_base64}`: extract text with pypdf (PDF) or python-docx (DOCX) or plain text; LLM task `cv_extract` (cheap class) -> draft sections; DO NOT write to cv_section; return `{drafts: [...]}`. `POST /cv/import/confirm` body `{drafts}` -> creates sections. Empty/garbage file -> 422 with clear message. - `POST /postings/fetch` `{query, region?}` -> runs AF connector (import via packages.connectors), creates discovered applications for new postings, returns counts {new, dupes}. Behind env flag `CONNECTORS_ENABLED` (default true). - `POST /scoring/batch` `{application_ids: [uuid]}` -> scores all pending with cheap class; each response adds `red_flags: []` (extend scoring mock with red_flags field). - `GET /today` -> `{digest: [{application_id, title, company, score}], nudges: [{application_id, days_since_sent, suggestion}], pending_approvals: n}` (SQL for nudges: state='sent' and days > follow_up_after_days and not snoozed). - `POST /applications/{id}/interview-prep` (strong class) -> markdown Q&A stored via artifacts service as kind `other`, sets interview_prep_artifact_id. Content: 10 likely questions w/ suggested angle referencing profile sections + posting description. Mock mode returns deterministic Q&A. - `POST /concierge/seed-demo` -> idempotent demo seed (profile 'Demo Demosson' with åäö, 6 realistic Skåne postings, varied states incl. one scored high, one sent 8 days ago for nudge demo). Returns summary counts. - SMTP: `transport.py` gains `SmtpTransport` (env SMTP_HOST/PORT/USER/PASS/FROM, ssl on 465, starttls else). Selection order: SMTP configured -> SmtpTransport; else ClipboardTransport (marks sent + stores payload for UI copy). Gate checks UNCHANGED. Telemetry unchanged. - Scheduler: `app/scheduler.py` with APScheduler (AsyncIOScheduler): daily 07:00 fetch+batchscore job guarded by env `SCHEDULER_ENABLED` (default false). Start/stop in app lifespan. - Install packages into the api image: Dockerfile.test must pip install packages (COPY packages into image, `pip install -e /packages/connectors -e /packages/llm-gateway -e /packages/artifacts`). - Tests: extend suite — CV import (mock), fetch with connector stubbed at boundary (monkeypatch connector.fetch), batch scoring ordering, /today nudges SQL correctness (backdated last_activity), interview prep artifact creation, seed idempotency (run twice, same counts), SMTP selection logic. Total api suite must exceed 60 tests, all green via `docker compose run --rm api-test`. ## W3: apps/web v1 + CI + docs Paths: `apps/web/**`, `.forgejo/workflows/**`, `docs/**` (only ADD docs/user-guide.md + update README screenshots section placeholder; do not edit ADRs) - Onboarding wizard at `/welcome` shown when profile.full_name empty: steps Welcome -> Import CV (upload -> drafts review -> confirm) -> "Fetch postings" (query+region form -> /postings/fetch results) -> Done -> land on /today. - TodayView (`/` default route): digest cards + nudge cards (with "copy follow-up draft" using /today data) + pending approvals count + total cost line from /telemetry/tasks sum. - Applications kanban: red-flag badge (⚠ with tooltip listing red_flags), nudge hint dot when in nudges, interview-prep button on detail -> fetch/generate -> show artifact content in modal, editable textarea save -> new artifact version. - Research view: fetch form wired to /postings/fetch, scam badge column. - Cost display component: tokens-in + tokens-out + (cost if present). - CI (`.forgejo/workflows/ci.yml`): on push: job1 api tests via docker compose (build api-test, run), job2 package tests (uv), job3 web (npm ci + build + vitest). Use docker on runner responsibly; if actions-runner availability is uncertain, still write the YAML (server-side check happens later). - User guide: docs/user-guide.md, from fresh-user perspective: install, first run, import, digest, approve, interview prep, costs. Plain language, short paragraphs. - Keep all existing vitest tests green; add: wizard routing test, nudge badge render test, red flag tooltip fixture test. npm build + test must pass.