Migrated from monorepo during Forgejo registry consolidation 2026-05-07
|
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
The Plan-tab editors (interactive Gantt for life events, flex spending
rules) are now available in What-If too — with local state instead of
DB persistence so users can tweak before committing to a scenario.
Architecture refactor:
- EventGantt is now a controlled component. The `scenarioId` prop +
internal useMutation/useQueryClient hooks went away; the component
takes a `persister: { create, patch, delete }` prop and delegates
every mutation through it. Plan tab wires it to lifeEventsApi +
cache invalidation; What-If wires it to React local state with
negative ids for new events.
- FlexRulesEditor is similarly controlled. Takes `rules + onChange`
instead of a `scenario` object. Plan tab wraps it with the PATCH
/scenarios/:id mutation; What-If wraps it with setFlexRules.
Backend:
- New stateless POST /scenarios/spending-profile-preview endpoint
takes base_spending_gbp + horizon + life_events + flex_rules in the
body and returns the same SpendingProfileResponse shape as the
read-only /scenarios/{id}/spending-profile endpoint. Used by
What-If to render the stacked-area chart against unsaved events.
- SpendingProfileResponse.scenario_id loosened to int | None to
support the preview variant.
Frontend:
- What-If page gains `events` + `flexRules` local state, an
EventGantt + FlexRulesEditor wired through them, and a Visx
spending-profile chart fed by /spending-profile-preview.
- Sim auto-refresh: a 600ms debounced effect re-fires /simulate
whenever the form / events / flex rules change. Manual "Run
simulation" button stays as an immediate trigger.
- "Save as scenario" still works — preserves the scenario params but
not yet the life events / flex rules (a Wave-3 follow-up could
POST them after the scenario is created).
247 pytest pass; mypy + ruff + frontend typecheck/test/build all clean.
|
||
|---|---|---|
| alembic | ||
| fire_planner | ||
| frontend | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| .woodpecker.yml | ||
| alembic.ini | ||
| Dockerfile | ||
| PLAYBOOK_VIKTOR.md | ||
| poetry.lock | ||
| pyproject.toml | ||
| README.md | ||
fire-planner
Risk-adjusted, tax-minimised FIRE retirement planner. Consumes today's
portfolio, savings rate, and RSU vest schedule from sibling services
(wealthfolio, payslip-ingest, hmrc-sync) and returns the after-tax
probability of success for each combination of jurisdiction, withdrawal
strategy, and "year you break UK tax residency".
Layout
fire_planner/— packagetax/— per-jurisdiction tax engines (UK, nomad, Malaysia, Thailand, Cyprus, Bulgaria)returns/— Shiller 1871+ data + block bootstrap samplerstrategies/— Trinity 4% SWR, Guyton-Klinger guardrails, VPWingest/— pulls fromwealthfolio/payslip-ingest/hmrc-syncsimulator.py— vectorised NumPy MC enginescenarios.py— Cartesian product over (jurisdiction × strategy × leave-UK-year × glide)app.py— FastAPI on-demand/recompute__main__.py—clickCLI:ingest,simulate,recompute-all,migrate
Common commands
poetry install
pytest -v
mypy .
ruff check .
yapf --recursive .
# Run migrations against the local DB:
DB_CONNECTION_STRING=postgresql+asyncpg://... alembic upgrade head
# CLI
DB_CONNECTION_STRING=... python -m fire_planner ingest
DB_CONNECTION_STRING=... python -m fire_planner simulate --scenario=cyprus-vpw-leave-y3
DB_CONNECTION_STRING=... python -m fire_planner recompute-all
Schema
Six tables in fire_planner schema on pg-cluster-rw:
account_snapshot— daily NW per account (Wealthfolio)scenario— Cartesian-product scenario definitionmc_run— execution metadata + summary stats per (scenario, run_at)mc_path— sparse storage (top decile, bottom decile, median)projection_yearly— deterministic point projection per scenarioscenario_summary— denormalised fast-read for Grafana