249e277696
Second real bug found by driving the running app while the suites were green. Flexible wishes were funded twice across propose -> apply -> re-propose. Because planned_leave_day carries no wish_id, the engine could not tell that applied days were that wish's own prior occurrence; it treated them as occupied space, routed the candidate search around them, and funded the year again on shifted dates while reporting weeks_funded 1.0. Applying twice turned "one week each spring" into 18 paid days instead of 9. Fixed wishes were unaffected and stay unaffected. Fix (allocation_service.py), per the approved approach of counting already-applied days inside the bounding range rather than adding a wish_id column: - _Ledger._persisted_lookup snapshots existing days BEFORE this run folds in its own freshly-computed windows, so a fixed wish's new placement is never mistaken for a flexible wish's history - _reuse_already_funded_window finds this wish's persisted dates within its bound and reuses the window containing them, via the ordinary fund_window path - full funding therefore yields zero new days and partial funding tops up exactly the remainder, with no duplication and no false shortfall - with no persisted dates, or if the reused window is no longer collision-free, it falls through to the original search, so an unsatisfied year is never frozen Calendar cache extended 2026-2035 -> 2026-2055 (39KB -> 116KB) and the allocator switched to strict, so an uncached year raises UncachedYearError -> ValidationError -> HTTP 422 instead of silently treating Julafton, Nyarsafton and Midsommarafton as working days. Also fixed normalize_day dropping the second name when two holidays share a date (Nationaldagen was being lost). 2049 and 2055 legitimately have 17 red days, not 18: Pingstdagen coincides with Nationaldagen. New backend/tests/integration/test_user_journey.py drives the real app over HTTP across seven journeys: the double-booking guard, the fixed-wish control, apply idempotency, fixed-never-displaced-by-flexible, flexible wishes tracking Easter per year, age-4 arithmetic self-consistency, and a recursive check that no Swedish text reaches the wire now that everything is message_key + data. New backend/scripts/seed_demo.py seeds a realistic workspace through the real service layer for manual testing: demo@example.com / DemoPass123! Backend 414 -> 435 tests. Frontend unchanged at 705. Still not verified in a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
58 lines
2.4 KiB
Markdown
58 lines
2.4 KiB
Markdown
# Backend — Föräldraledigplannerare
|
|
|
|
FastAPI + Python 3.14, packages managed with `uv`. Layered architecture
|
|
(entities → repositories → services → routers) with dependency injection so the
|
|
database backend (SQLite now, Postgres later) is swappable.
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
uv sync # install deps (incl. dev group)
|
|
uv run uvicorn app.main:app --reload # run dev server on :8000
|
|
uv run pytest # run tests with coverage
|
|
uv run ruff check . # lint
|
|
```
|
|
|
|
## Layout
|
|
|
|
```
|
|
app/
|
|
config.py # pydantic-settings (app config + frozen LeaveRules)
|
|
dependencies.py # DI factories (pick repo impl by settings.database_type)
|
|
entities/ # plain dataclasses (domain)
|
|
models/ # pydantic API schemas
|
|
repositories/ # abstract interfaces
|
|
repositories_impl/ # concrete impls per backend (sqlite/, postgres/)
|
|
services/ # business logic
|
|
routers/ # FastAPI routes
|
|
db/connection.py # connection + (dev) schema bootstrap
|
|
utils/ # errors, logger
|
|
```
|
|
|
|
## Demo data (manual/browser testing)
|
|
|
|
`scripts/seed_demo.py` seeds a realistic, ready-to-explore workspace through the
|
|
normal service/repository layers (no raw SQL) — a couple workspace with a
|
|
partner, a child born ~6 months ago, vacation rows for only some years (so the
|
|
`is_assumed` badge is visible), day overrides, and a spread of leave wishes
|
|
(fixed, flexible, and one deliberately underfunded to show a shortfall +
|
|
remedies + surplus advice). Login credentials are fixed and printed at the end.
|
|
|
|
```bash
|
|
uv run python scripts/seed_demo.py # create once; re-running is a no-op
|
|
uv run python scripts/seed_demo.py --reset # wipe the demo workspace and rebuild
|
|
```
|
|
|
|
Then open `http://localhost:5173` and log in with the printed `demo@example.com`
|
|
credentials. Safe to re-run: without `--reset` it detects the existing demo
|
|
user and changes nothing; `--reset` only ever deletes the one demo workspace.
|
|
|
|
## Reference vertical slice
|
|
|
|
`VacationDays` is implemented end-to-end (entity → schema → repo interface →
|
|
SQLite impl → service → router → tests). **Copy this slice** when adding a new
|
|
entity. See `JUNIOR_ONBOARDING.md` in the repo root for task-by-task guidance.
|
|
|
|
> The inline schema in `db/connection.py` exists only so dev/tests are
|
|
> self-contained. Production schema is owned by Alembic migrations (a senior task).
|