e4719cb9ae
Manual entry now takes one parent's sjukpenningnivå, lägstanivå and dubbeldagar left (plus optional reserved days) and derives the rest in build_snapshot, shared with the scraper. The scraper also reads the family panel and exposes a partner deduction; with two parents it is saved as a 'deduced' snapshot (migration 002). Parent count is the single source of entitlement: one member gets 480 days and 96 savable at age 4, two get 240 and 48 each. Workspaces are capped at two parents. Also lands the in-flight squash of migrations 002–011 into the initial schema and the removal of planning_mode. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
137 lines
5.2 KiB
Markdown
137 lines
5.2 KiB
Markdown
# Föräldraledigplannerare
|
|
|
|
A planner for Swedish parental leave (föräldraledighet) — solo or with a
|
|
partner — that folds in vacation days and optimizes leave over time so that
|
|
as few days as possible are lost at the child's 4th birthday, after which
|
|
only 96 days may be saved (48 per parent, non-poolable, as soon as the child
|
|
has two or more parents).
|
|
|
|
Full-stack monorepo: TypeScript/React/Vite frontend, Python/FastAPI backend,
|
|
SQLite by default. Postgres is a partial stub, not a usable swap yet — see
|
|
`backend/README.md`.
|
|
|
|
## Quick start
|
|
|
|
From `frontend/`:
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # runs uvicorn (:8000) and Vite (:5173) together
|
|
```
|
|
|
|
Open **`http://localhost:5173`** (not `127.0.0.1` — Vite's dev server binds
|
|
IPv6 `::1`, so only `localhost` resolves to it).
|
|
|
|
The backend can also run on its own, from `backend/`:
|
|
|
|
```bash
|
|
uv sync
|
|
uv run uvicorn app.main:app --reload --port 8000
|
|
```
|
|
|
|
Seed a ready-to-explore demo workspace (couple, a partner, a child, vacation
|
|
history, leave wishes, a deliberate shortfall) through the normal
|
|
service/repository layers — no raw SQL:
|
|
|
|
```bash
|
|
cd backend
|
|
uv run python scripts/seed_demo.py --reset
|
|
```
|
|
|
|
It prints the demo login (fixed credentials, safe to re-run — without
|
|
`--reset` it's a no-op if the demo user already exists).
|
|
|
|
The Försäkringskassan scraper (BankID login flow via Playwright) is enabled
|
|
by default. Exercise the login UI directly with `npm run login` from
|
|
`frontend/`; Chromium must be installed separately:
|
|
`uv run playwright install chromium`.
|
|
|
|
## The app
|
|
|
|
Three tabs:
|
|
|
|
- **Översikt** — the headline figure for days at risk at the 4th birthday,
|
|
per-parent bars against each parent's savable cap, a year-by-year
|
|
timeline, and shortfall/surplus detail below the fold.
|
|
- **Kalender** — where leave is authored. Drag a range to create a period,
|
|
click a band to edit it, click a day to change which resource funds it
|
|
(sjukpenning/lägstanivå/vacation). A season-preset rail helps place common
|
|
periods (summer, Christmas, Easter). Day overrides (marking a specific date
|
|
as a working day or a free day, overriding the calendar) live in
|
|
Inställningar, not on the calendar itself.
|
|
- **Inställningar**.
|
|
|
|
**The calendar is the plan.** There's no separate propose-then-apply step:
|
|
every edit recomputes and persists immediately, reporting what changed with
|
|
an undo. A `LeaveWish` is a recurring intent (e.g. "summer", "Christmas");
|
|
the allocator decides which resource — parental days, vacation, unpaid —
|
|
funds each day, without retyping days a user has pinned by hand.
|
|
|
|
First run walks a wizard: add the child, optionally add a partner (planning
|
|
solo vs. together is derived from whether a partner has been added — there
|
|
is no separate solo/couple choice), then set balances — fetch from
|
|
Försäkringskassan via BankID, enter manually, or skip (figures are then
|
|
marked as estimates).
|
|
|
|
## Layout
|
|
|
|
```
|
|
backend/app/
|
|
entities/ persistence-layer row shapes
|
|
models/ Pydantic v2 API schemas
|
|
repositories/ abstract repository interfaces
|
|
repositories_impl/ sqlite/ (default, complete) and postgres/ (partial — one entity only)
|
|
services/ business logic — planning, tips, analytics, auth
|
|
scrapers/ Försäkringskassan scraper (Playwright)
|
|
routers/ thin FastAPI route handlers
|
|
backend/migrations/ Alembic migrations (hand-written, not ORM-generated)
|
|
backend/scripts/ seed_demo.py and other dev-only scripts
|
|
|
|
frontend/src/
|
|
pages/ route-level screens (Dashboard, Calendar, Settings, ...)
|
|
components/ presentational + composite components (BEM CSS)
|
|
hooks/ data-fetching/state (the data layer — no TanStack Query)
|
|
services/ api.ts (HTTP client) + errors.ts
|
|
context/ Auth + Workspace React context
|
|
frontend/e2e/ Playwright end-to-end specs
|
|
```
|
|
|
|
Routers call services; services call the repository interfaces — never
|
|
`repositories_impl` directly. Components consume hooks; hooks call
|
|
`services/api.ts` — components never call `fetch` directly.
|
|
|
|
## Tests & gates
|
|
|
|
Backend (from `backend/`):
|
|
|
|
```bash
|
|
uv run ruff check .
|
|
uv run ruff format --check .
|
|
uv run pytest --cov=app --cov-report=term-missing --cov-fail-under=80
|
|
```
|
|
|
|
Frontend (from `frontend/`):
|
|
|
|
```bash
|
|
npm run typecheck
|
|
npm run lint # ESLint, zero warnings allowed
|
|
npm run coverage # Vitest, thresholds: lines 96% / functions 91% / branches 92% / statements 96%
|
|
```
|
|
|
|
Both suites pass comfortably above their gates (backend ~84% vs. an 80%
|
|
floor; frontend above 96/91/92/96 — see `frontend/vitest.config.ts`). Gates
|
|
are a regression ratchet, not a target; run the commands above for current
|
|
numbers rather than trusting a hardcoded count here, which drifts fast.
|
|
|
|
CI (`.github/workflows/ci.yml`) runs all of the above, plus a Playwright E2E
|
|
suite that reports but does not yet block merges.
|
|
|
|
## Further reading
|
|
|
|
- `CLAUDE.md` — conventions, stack details, model-tier rules for contributors.
|
|
- `ARCHITECTURE.md` / `PLANNING_ENGINE_SPEC.md` — design and rationale.
|
|
Both predate parts of the current behavior; sections marked
|
|
**SUPERSEDED** note what changed and why.
|
|
- `backend/README.md` — backend-specific setup and the reference vertical
|
|
slice to copy when adding a new entity.
|