Migrated from monorepo during Forgejo registry consolidation 2026-05-07
Find a file
Viktor Barzin 23d11bdf6d
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ingest: switch wealthfolio to pg-sync mirror reads
The previous SQLite-direct reader queried `holdings_snapshot` (singular)
and `accounts.type` — both wrong against the live wealthfolio schema
(plural `holdings_snapshots`, column `account_type`). It silently
returned [] via the OperationalError fallback, leaving fire-planner with
stale account snapshots.

Switch to reading from the wealthfolio_sync PG mirror. The pg-sync
sidecar (defined in infra/stacks/wealthfolio) hourly mirrors SQLite to
Postgres with a clean schema. We read from `daily_account_valuation`
which already has total_value, cost_basis, and explicit fx_rate_to_base
per row — no JSON-decoding of position blobs.

CLI ingest no longer takes --db-path (no kubectl-exec gymnastics);
reads WEALTHFOLIO_SYNC_DB_CONNECTION_STRING from env. Falls back to
DB_CONNECTION_STRING for single-DB local dev.

13 new tests covering: latest-per-account, multi-currency FX, explicit
as-of, empty mirror, null cost_basis, full pipeline through upsert.
140 tests pass; mypy strict + ruff clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 21:33:48 +00:00
alembic Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
fire_planner ingest: switch wealthfolio to pg-sync mirror reads 2026-05-09 21:33:48 +00:00
tests ingest: switch wealthfolio to pg-sync mirror reads 2026-05-09 21:33:48 +00:00
.gitignore Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
.woodpecker.yml Phase 4: drop registry.viktorbarzin.me, Forgejo only 2026-05-07 23:17:46 +00:00
alembic.ini Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
Dockerfile Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
PLAYBOOK_VIKTOR.md Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
poetry.lock Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
pyproject.toml Initial extraction from monorepo 2026-05-07 17:06:19 +00:00
README.md Initial extraction from monorepo 2026-05-07 17:06:19 +00:00

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/ — package
    • tax/ — per-jurisdiction tax engines (UK, nomad, Malaysia, Thailand, Cyprus, Bulgaria)
    • returns/ — Shiller 1871+ data + block bootstrap sampler
    • strategies/ — Trinity 4% SWR, Guyton-Klinger guardrails, VPW
    • ingest/ — pulls from wealthfolio / payslip-ingest / hmrc-sync
    • simulator.py — vectorised NumPy MC engine
    • scenarios.py — Cartesian product over (jurisdiction × strategy × leave-UK-year × glide)
    • app.py — FastAPI on-demand /recompute
    • __main__.pyclick CLI: 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 definition
  • mc_run — execution metadata + summary stats per (scenario, run_at)
  • mc_path — sparse storage (top decile, bottom decile, median)
  • projection_yearly — deterministic point projection per scenario
  • scenario_summary — denormalised fast-read for Grafana