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

5.6 KiB
Raw Permalink Blame History

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