v1.0 design: perspective review + ADR-0002 scope + worker dispatch cards
This commit is contained in:
parent
7560bdb4c9
commit
d1753bb70a
3 changed files with 125 additions and 0 deletions
31
docs/adr/0002-v1-scope.md
Normal file
31
docs/adr/0002-v1-scope.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# ADR-0002: v1.0 scope (post perspective review)
|
||||||
|
|
||||||
|
Status: accepted (2026-07-30)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
POC proved the architecture (state machine, approval gate, budgets, mock mode).
|
||||||
|
`docs/user-research/desperate-jobseeker.md` drives v1.0 scope.
|
||||||
|
|
||||||
|
## v1.0 adds
|
||||||
|
|
||||||
|
1. **CV import** (PDF/DOCX/paste -> extracted section drafts -> confirm-to-merge). No import, no adoption.
|
||||||
|
2. **Arbetsförmedlingen connector** (Official Swedish public employment API, free, no key, no ToS risk) + generic URL ingestion with readability extraction. LinkedIn stays manual-paste (account-ban risk documented).
|
||||||
|
3. **Nightly digest scheduler**: fetch -> dedupe -> batch score (cheap class) -> `GET /today` digest (top matches + follow-up nudges).
|
||||||
|
4. **Follow-up rules**: days-since-sent -> suggested nudge with one-click draft.
|
||||||
|
5. **Interview prep generator** (strong class, per application, stored as artifact).
|
||||||
|
6. **Scam/red-flag check** inside scoring (cheap class, extends rubric output with `red_flags: []`).
|
||||||
|
7. **SMTP transport** (env-configured) behind the unchanged approval gate; `ClipboardTransport` default when unconfigured.
|
||||||
|
8. **Onboarding wizard** in web: welcome -> import -> digest -> first application.
|
||||||
|
9. **Forgejo CI**: pytest suites (api, artifacts, llm-gateway, connectors) + web build/test on push.
|
||||||
|
10. **Demo seed**: `POST /dev/seed-demo` inserts a demo profile + 6 sample postings so a new user sees the product in 60 seconds with zero keys.
|
||||||
|
|
||||||
|
## Not in v1.0
|
||||||
|
|
||||||
|
Multi-user auth, SaaS, auto-apply, LinkedIn write, mobile app, interview scheduling.
|
||||||
|
|
||||||
|
## Targets
|
||||||
|
|
||||||
|
- `docker compose up` to running app in < 15 min on a dev machine.
|
||||||
|
- All tests green in CI.
|
||||||
|
- No endpoint requires an LLM key; everything degrades to mock.
|
||||||
42
docs/user-research/desperate-jobseeker.md
Normal file
42
docs/user-research/desperate-jobseeker.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Perspective review: the desperate job seeker
|
||||||
|
|
||||||
|
Who uses this at v1.0? Not a senior dev fielding recruiter InMails. Someone who is unemployed, stressed, maybe burned through savings, possibly on A-kassa, who needs to land a job in weeks. Reviewed against the POC, here is what they actually experience:
|
||||||
|
|
||||||
|
## What breaks for them today
|
||||||
|
|
||||||
|
1. **They don't have a structured CV to edit.** Their CV is a PDF or Word file, possibly years old, possibly bad. A tab that asks them to hand-type every section is dead on arrival. They need: drop in the old file, the system extracts sections, they fix mistakes. CV import is THE onboarding feature.
|
||||||
|
|
||||||
|
2. **They can't config an API key hunt.** "Add three provider keys to .env" filters out 90% of desperate job seekers. v1.0 must run fully with zero keys (mock LLM + demo data) and work with a single free key for real use. Cost must be visible per action ("this scoring costs ~1 cent") — someone counting kronor will not press a button marked "maybe $$$".
|
||||||
|
|
||||||
|
3. **Volume AND tailoring.** They might need 30 applications out in a week. One-at-a-time paste-URL flow too slow, mass-auto-apply gets them flagged as spam. Middle path: a real source connector that pulls relevant postings nightly (Sweden: Arbetsförmedlingen's official free API), batch-scores them cheap, and presents a ranked morning digest ("3 worth your time today, 2 borderline"). Their scarce resource is writing energy, not job links.
|
||||||
|
|
||||||
|
4. **Rejection management.** A kanban full of red columns crushes morale. They need follow-up tracking that acts FOR them: applied 7 days ago, no reply -> "nudge them, here's a 2-line follow-up draft". Small forward motion daily.
|
||||||
|
|
||||||
|
5. **The interview is the bottleneck.** Weeks of silence, then "can you do Tuesday?". They panic. Interview prep must be one click per application: likely questions from the posting + their profile, draft answers in THEIR voice (they edit, system critiques).
|
||||||
|
|
||||||
|
6. **They can be scammed.** Desperation attracts fraud postings (fake employers harvesting personal data, pay-to-apply schemes). Scoring should include cheap red-flag checks as a visible "⚠" on postings.
|
||||||
|
|
||||||
|
7. **LinkedIn gray zone.** Mass-automation gets accounts banned; a desperate person losing their LinkedIn account is catastrophic. Official APIs (Arbetsförmedlingen) and read-paste flows only at v1.0.
|
||||||
|
|
||||||
|
8. **Swedish AND English.** Many will write applications in both. Artifacts and critique must handle both without mangling å/ä/ö (proven in POC) and the UI must not shame imperfect Swedish.
|
||||||
|
|
||||||
|
## What they do NOT need at v1.0
|
||||||
|
|
||||||
|
- Multi-user, auth, SaaS, billing. Single-user self-hosted.
|
||||||
|
- Auto-apply anything. Ever.
|
||||||
|
- Mobile app. Responsive web is enough.
|
||||||
|
- Interview scheduling integrations.
|
||||||
|
|
||||||
|
## Consequences for design (v1.0 deltas over POC)
|
||||||
|
|
||||||
|
| Need | Feature | Owner component |
|
||||||
|
|---|---|---|
|
||||||
|
| Import old CV | `POST /cv/import` (PDF/DOCX text -> LLM extract -> section drafts -> user confirms) | api + llm-gateway |
|
||||||
|
| Zero-config start | demo seed data + mock mode already default; first-run wizard | web + api seed |
|
||||||
|
| Volume | Arbetsförmedlingen connector (official, free, keyless), nightly fetch + batch score + digest endpoint | connectors + scheduler |
|
||||||
|
| Burn transparency | `/telemetry/cost-estimate` returning per-task token+cost totals | api |
|
||||||
|
| Follow-up engine | `follow_up_rule` on application; `GET /today` returns nudges + digest + open nudges count | api |
|
||||||
|
| Interview prep | `POST /applications/{id}/interview-prep` -> markdown Q&A in user's languages, saved as artifact | api |
|
||||||
|
| Scam shield | scoring task v2 adds red_flags array (unpaid trial, asks for money, harvesters) | llm-gateway + api |
|
||||||
|
| Real send | SMTP transport behind existing approval gate (unchanged gate semantics); clipboard fallback transport when SMTP unconfigured | api |
|
||||||
|
| Onboarding | wizard: welcome -> import CV -> first digest -> first approval | web |
|
||||||
52
docs/worker-tasks/v1-tasks.md
Normal file
52
docs/worker-tasks/v1-tasks.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# 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-<name>. 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 <main>/<article>, 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.
|
||||||
Loading…
Reference in a new issue