jobhunt-platform/docs/worker-tasks/v1-tasks.md

6.4 KiB

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.