Retire Planner v1: engine (live 2026 CPP/OAS/GIS/RRIF/tax), 17-step UI, 4 SVG charts, vision gallery, tests
This commit is contained in:
commit
e25dd4e0a1
29 files changed
+3143
No files matched your search
@@ -0,0 +1,47 @@
|
||||
# RETIRE PLANNER — Execution Report
|
||||
|
||||
**Run:** DSH local agent, PLAN.md v1 (2026-10-08). Status: **IN PROGRESS**.
|
||||
**Target:** https://retire.krisforbes.ca — Canadian retirement calculator, 100% client-side.
|
||||
|
||||
---
|
||||
|
||||
## Stage 0 — Preflight ✅ (all pass)
|
||||
|
||||
| Check | Command | Result |
|
||||
|---|---|---|
|
||||
| Workspace | `pwd` | `/home/ubuntu/projects/retire-planner` ✅ |
|
||||
| Node | `node --version` | v26.7.0 ✅ |
|
||||
| Git | `git --version` | 2.53.0 ✅ |
|
||||
| Gitea | `curl http://192.168.0.123:3000/api/v1/version` | `{"version":"28.0.0"}` ✅ |
|
||||
| Unraid SSH | `ssh root@192.168.0.123 'echo ok'` | `ok` ✅ — **note:** plain `ssh` fails on this box with `Bad owner or permissions on /etc/ssh/ssh_config.d/20-systemd-ssh-proxy.conf` (symlink owned by nobody). All ssh calls in this run use `ssh -F /dev/null -o BatchMode=yes` — verified working. |
|
||||
| Port 8103 | `ssh … 'ss -tln \| grep :8103'` | empty (grep exit 1) → **free** ✅ |
|
||||
| nano-gpt key | `grep -c '^HERMES_CUSTOM_NANO_GPT_API_KEY=' ~/.hermes/.env` | `1` ✅ (value never printed) |
|
||||
| Repo exists? | `GET /api/v1/repos/krisf/retire-planner` | **404** → no existing repo ✅ |
|
||||
| Clean slate | retireapp container / Caddyfile retire block / `/mnt/better/appdata/retireapp` | all absent ✅ |
|
||||
| rsync | local + remote `which rsync` | both present ✅ |
|
||||
|
||||
## Stage 1 — Engine + live constants ✅
|
||||
- `site/engine/engine.js` (UMD, pure fns) + `site/engine/canada.js` generated by `scripts/build_canada.js` from live fetches.
|
||||
- Sources fetched (evidence in `scripts/constants-evidence.json`): ESDC CPP/quarterly report (CPP max **$1,507.65**/mo @65, YMPE **$74,600**), canada.ca OAS page (max **$762.50** Oct–Dec 2026, +0.6%/mo deferral to +36%, ×1.10 @75, clawback 0.15 over **$95,323**), canada.ca GIS page (single max **$1,138.90**/mo, thr 23,112; couple 685.56×2, thr 30,528), CRA 2026 federal brackets (14/20.5/26/29/33 @ 58,523/117,045/181,440/258,482), taxtips.ca 2026 provincial tables all 13 (ON top 13.16%, ON surtax 20%/36% modelled from FAQ thresholds, QC abatement 16.5% in engine).
|
||||
- RRIF minimums: CRA statutory factor table (Reg. 8301(1)): 71→5.28 … 94→12.18, 95+→20. (Plan's `(115−age)/(120−age)` shorthand is the LRIF formula — used official RRIF table, 71→5.28 matches plan test.)
|
||||
- Canada.ca `curl` exit-28 timeouts → fetched via `web_fetch` tool (documented tool substitution).
|
||||
- Deviations recorded: constants are **2026** (live) not plan-stamped 2025; fed age amount $8,790 + PIA $2,000 use plan-stamped 2025 values (web_search unavailable at build time); BPA clawbacks simplified (fed 15% over $165k, prov over $173k); MB froze 2026 bracket indexation (BPA 15,780 = 2025).
|
||||
|
||||
## Stage 2 — Tests ✅
|
||||
- `node tests/engine.test.js` → **36 PASS, 0 FAIL** (full output in git log of this run).
|
||||
- Plan's 5 gate assertions: cpp 60→0.64× / 70→1.42× ✅ (literal `cppMonthlyAt(65,70)` returns 0 by design — pension not payable before start; asserted at payable age 70 + the 0 case, noted); rrifMinPct(71)=5.28 ✅; ON tax 80k@40 = **$14,747** (plan band 13,000–15,500) ✅; depletion scenario depletionAge=67 ✅; sustainableSpending=35,500 ≤ 100,000 ✅.
|
||||
- Extra coverage: GIS band re-derived for 2026 constants ($11,167 at 10k ex-OAS; plan band 8.2–9.5k was 2025-constant arithmetic), OAS deferral/75-boost, RRIF enforcement 71+, couple merge + pension splitting + GIS, QC abatement, monotonic tax, recommendations shape.
|
||||
|
||||
## Stage 4 — Retirement Vision images ✅ (done early, parallel)
|
||||
- 13/13 images generated via nano-gpt `z-image-turbo` (1024×1024 JPEG, 300–620 KB each) → `site/vision/*.png` + `manifest.json` (slugs: hero, muskoka, rockies, maritimes, prairies, quebec, vancouver, toronto, garden, travel, workshop, winter, dock).
|
||||
- Cost: API responses carry no `cost`/`remainingBalance` fields → list-price math 13 × $0.0119 = **$0.155** (cap $2; abort conditions unenforceable, documented). Log: `.fetch/vision_log.jsonl`.
|
||||
|
||||
## Stage 3 — UI ✅
|
||||
- `site/index.html` (marker `<!-- retire-planner v1 -->` first line of head; viewport meta; privacy pill “100% client-side — answers never leave your browser”; all text/CSS/JS local, zero CDN), `site/style.css` (mobile-first light theme, sticky panel → mobile bottom-sheet), `site/app.js` (vanilla, no framework).
|
||||
- 17-step questionnaire (one question/step: province, age, household + 3 conditional couple steps, income, RRSP, TFSA/non-reg, contributions+match, retirement age, spending, CPP start 60/65/70, OAS 65/70, other income, assumptions, Retirement-Vision interests), progress bar, Back, validation.
|
||||
- Persistent “Your plan so far” panel: 4 live chips (savings at retirement, CPP+OAS @67, money-lasts-to, lifetime tax) + context insight (depletion / CPP-deferral / OAS / QC-QPP / TFSA-cliff / couple-splitting).
|
||||
- 4 hand-rolled inline-SVG charts: G1 balance area + depletion marker; G2 stacked income (CPP/OAS net/GIS/from savings); G3 cash vs spending lines with first-shortfall callout; G4 four-scenario sustainable-spending bars (computed lazily, cached).
|
||||
- Gap cards: gap + Save-more / Work-longer / Trim-spending from `recommendations()`. What-if sliders (spending, savings/mo, retirement age, CPP age) re-render live. Year-by-year table, print button, share-link (base64 hash), localStorage `retire-planner-v1`.
|
||||
- Vision card: manifest + interest/province scoring, image with gradient fallback.
|
||||
- `tests/ui.smoke.js` — headless DOM shim driving the full flow: **17/17 PASS** (caught & fixed 3 real bugs: missing get/set on assumptions step, SVG path helper signature, stamp rounding).
|
||||
- Served smoke: python http.server 8099 → /, css, js, manifest, png all 200.
|
||||
Reference in new issue
Block a user