User asked for a manual date range on the Dashboard chart instead of
the hard-coded 12-month window.
Backend (/networth/history):
- Read LIVE from wf_sync's daily_account_valuation JOIN accounts so the
chart spans the broker's full daily series. The account_snapshot
cache is only the latest snapshot — never had >2 daily points for
charting. Falls back to the cache when wf_sync isn't wired (tests).
- Accept `from=YYYY-MM-DD` and `to=YYYY-MM-DD` query params. When `from`
is set, the window is [from, to or today] (inclusive). Otherwise the
legacy `days` look-back still applies. 422 when from > to.
Frontend:
- New HistoryRangePicker component: preset buttons (1m / 3m / 6m / 1y /
3y / All) plus two date inputs for an explicit custom range.
- Dashboard wires the picker to the chart via react-query keyed on the
selected range, so the chart re-fetches on change.
Tests:
- Renamed `respects_days_filter` → `respects_from_to_filter` and added
inverted-window rejection. Old test asserted "days=1 returns 1 point"
which only worked when 'today' was within the seed window — the new
windowing is correct and explicit.
271 → 272 tests passing.
Adds the read+write endpoints the frontend needs to drive a
ProjectionLab-style UX on top of the existing engine.
- /networth, /networth/history — NW total + per-account from
account_snapshot (frontend chart)
- /scenarios CRUD + projection — list/get/create/patch/delete user
scenarios; cartesian read-only
- /scenarios/{id}/life-events — life event CRUD nested under scenario
- /life-events/{id} — patch + delete by id
- /scenarios/{id}/goals,
/goals/{id} — retirement goal CRUD
- /simulate, /compare — sync, no-DB-write what-if endpoints
Auth: Bearer-token dependency on writes + simulate when API_BEARER_TOKEN
is set; reads always open (lock down via Authentik-fronted ingress in
prod). Existing /recompute keeps its bearer auth.
CORS middleware reads FRONTEND_ORIGINS (comma-separated) for the dev
SPA. Lifespan now provisions the SQLAlchemy engine + session_factory
on app.state and disposes them on shutdown.
40 new tests covering happy paths and validation. 172 tests total.
mypy strict + ruff clean (B008 ignore added — Depends() in defaults
is the canonical FastAPI pattern, not a bug).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>