Files
retire-planner/PS-AUTO-SERVICE-REPORT.md

78 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PS Auto-Service — Years of Pensionable Service, Derived Automatically
**Date:** 2026-10-09 · **Plan:** `.hermes/plans/2026-10-09_ps-service-auto.md` · **Commit:** `807a966` (`ps: auto-derive years of service from join year + retirement age`, pushed to `origin/main`)
The "Years of pensionable service" quiz step is now **prefilled by calculation** from the join year,
current age, and retirement age. The user never has to type it; typing becomes an explicit, visible
**override** with a one-click "Use calculated" reset — mirroring the CPP "Use maximum" pattern.
## Derivation
```
yearsWorkedSoFar = CURRENT_YEAR − psJoinYear
ageAtJoin = age − yearsWorkedSoFar
serviceYears = retireAge − ageAtJoin (rounded, clamped to 0…60)
```
- `CURRENT_YEAR = new Date().getFullYear()` (2026), injectable for tests.
- Example: age 45, joined 2005, retiring at 65 → joined at 24 → **41 years**.
- Engine contract unchanged: `psCompute` feeds the **effective** value (override when set, auto
otherwise) to `computePSPension`, which applies its own 35-year cap → 41 → `serviceCapped` 35.
- Invalid/partial inputs (no join year, retireAge ≤ age-at-join, junk) → `null` → step falls back
to the plain manual entry, Next disabled until a valid number exists.
## Changes
| File | Change |
|---|---|
| `site/engine/pspension.js` | New exported `autoServiceYears(age, joinYear, retireAge, currentYear)` — guards (`1900…2999`, positive age/retire), rounds, clamps 0…60. |
| `site/app.js` | `state.a.psServiceOverride` (new; `psServiceYears` kept as legacy). `load()` migration: legacy saved `psServiceYears` → override (old plans keep their exact numbers). Helpers `psAutoServiceYears` / `psServiceEffective` / `psOverrideActive`. `psCompute` uses the **effective** value (the integration trap the plan flagged — engine reads `state.a.psServiceYears` directly via `psCompute`). ps_service step: prefills from effective value, `set()` writes only the override, min 0 / max 60, derivation hint EN+FR. Real-browser live swap on typing: `#ps-hint` text + `#ps-svc-reset` wrapper (always in DOM, `display` toggled) so the override hint and "Use calculated" button react without a re-render; clicking it clears the override and restores the auto prefill. |
| `site/i18n.js` | `q.ps_service.auto` / `.override` / `.useCalc` in EN and FR (parity + placeholder checks pass; FR goes through `frNorm`). |
| `site/style.css` | `.link` button style (bold blue underlined text button). |
| `tests/ps-auto-service.test.js` | **New**, 21 checks: derivation cases, future join → 0, retire ≤ join age → 0, invalid → null, clamp 60, string inputs, `currentYear` default, engine contract 41→35 and the 49-year plan example. |
| `tests/ui.smoke.js` | `runPsQuiz` rewritten: untouched auto prefill (`value="41"`, derivation hint EN+FR, Next enabled, reset hidden) → results card 35 years / 59 508,05 → **real route** (results → Edit answers → 6× Back) → type 30 → override hint + visible reset → results 30 years / 51 006,90 → "Use calculated" → auto restored → results 35 / 59 508,05; legacy-migration check in `runPs` (saved `psServiceYears: 30` → `psServiceOverride: 30`). Shim extensions: `#f1.value` synced from the rendered `value="…"` attribute (real DOM behaviour; a stale shim value let a slider's init wipe `retireAge` to 0), `.opt` registry. |
| `audit/ps_auto_service.py`, `audit/ps_auto_offlan.py`, `audit/ps-auto-service/`, `audit/ps-auto-offlan-check.json` | New live audits + evidence. |
## Test results (all green)
| Suite | Result |
|---|---|
| `tests/engine.test.js` | 36 pass, 0 fail |
| `tests/i18n.test.js` | 48 checks, I18N-OK |
| `tests/ps-pension.test.js` | 70 pass, 0 fail |
| `tests/ps-auto-service.test.js` | **21 pass, 0 fail (new)** |
| `tests/ui.smoke.js` | **128 checks, SMOKE-OK** (was 92; +36) |
## Live Playwright audit (local server, 390×844, real Chromium)
`audit/ps_auto_service.py` → **34 checks, 0 failed, PS-AUTO-AUDIT-OK**; screenshots + JSON in
`audit/ps-auto-service/`. Verified end-to-end: prefill 41 untouched, derivation hints EN+FR, real
typing of 30 **live-swaps** the hint and reveals "Use calculated"/"Utiliser le calcul", use-calc
restores 41 + clears the override in localStorage, results show 30 years → 51,006.90 and auto →
35 years → 59,508.05 (FR: 30 années / 51 006,90 $, 35 années / 59 508,05 $), module OFF parity
(15 steps, PS card hidden, no PS quiz text), no horizontal overflow and all controls + ps-card
inside the 390px shell on every audited screen.
Two real-browser bugs this audit caught that the shim could not:
1. Typing did not update the hint (no re-render in a browser) → live swap added in `oninput`.
2. The reset button is now always in the DOM, hidden via inline `display` — visibility is the
tested contract, not presence.
## Deploy
- `scripts/deploy.sh` → **DEPLOY-OK** (marker `retire-planner v1` served).
- LAN `http://192.168.0.123:8103/`: marker present; `app.js`, `i18n.js`, `engine/pspension.js`,
`style.css` all **byte-identical** to the workspace (sha256).
- Public `https://retire.krisforbes.ca/` from the Midhurst vantage (paramiko, `audit/ps_auto_offlan.py`):
200 + marker + `psServiceOverride`/`ps-usecalc` in app.js + FR auto hint in i18n.js +
`autoServiceYears` in engine → **OFFLAN-OK** (`audit/ps-auto-offlan-check.json`, 2026-10-09T19:41Z).
## Compatibility notes
- Old saved plans (pre-change `psServiceYears: 30`) migrate to `psServiceOverride: 30` on load →
identical numbers to before this change.
- Engine 35-year cap, G1/G2 tables, bridge benefit, IT grid: untouched.
- Module OFF path (15 steps) untouched.
PS_AUTO_SERVICE_DONE