PS pension: add case K (G1 55+ min(F1,F2) lowest-reduction rule, PSPC-verified) + report wording fixes

This commit is contained in:
krisf committed 2026-10-09 11:54:12 -04:00
1 parent 583b5ee2f8
commit becdf53f1a
2 files changed
+24 -7

No files matched your search

+8 -6
View File
@@ -58,8 +58,9 @@ Eligibility & early reduction (annual allowance), ages/service to 1/10 year,
reduction applies to the **whole** pension, permanent:
- **G1 (joined ≤ 2012-12-31):** unreduced at 60+ w/ ≥2 yrs, or 55–59 w/ ≥30.
S<25: F1 = 5%×(60−age). S≥25: F2 = max(5%×(55−age), 5%×(30−S)); at 60+ w/ S≥25,
min(F1,F2) applies; leaving <50 → F1 pinned at 50 → 50% cap (`note: f1Under50`).
S<25: F1 = 5%×(60−age). S≥25: F2 = max(5%×(55−age), 5%×(30−S)); at 55+ w/ S≥25,
min(F1,F2) applies (PSPC: "compare Formula 1 and 2 and apply the lowest reduction");
leaving <50 → F1 pinned at 50 → 50% cap (`note: f1Under50`).
- **G2 (joined ≥ 2013-01-01):** unreduced at 65+ w/ ≥2, or 60–64 w/ ≥30.
S<25: F1 = 5%×(65−age). S≥25: F2 = max(5%×(60−age), 5%×(30−S)); at 60+ w/ S≥25,
min(F1,F2). Leaving <55: modelled on the **PSPC-default path — deferred annuity,
@@ -67,7 +68,7 @@ reduction applies to the **whole** pension, permanent:
the 50%-at-55 annual-allowance alternative is surfaced as a note).
- <2 yrs service → return of contributions only (pension 0, note `lt2`).
IT salary picker (occupational group **IT**): 5 levels × 8 steps, real 2024-12-22
IT salary picker (occupational group **IT**): 5 levels × 8 steps (IT-05 has 9), real 2024-12-22
rates (IT-01 $69,361–89,382 … IT-05 $133,582–174,076), fully overridable — the
step selection pre-fills the salary field, which stays editable.
@@ -117,12 +118,13 @@ insight, disclaimer. FR uses official terms (RRFP/PSCC, **prestation de raccorde
| Suite | Result |
|---|---|
| `node tests/ps-pension.test.js` | **63 pass, 0 fail** |
| `node tests/ps-pension.test.js` | **70 pass, 0 fail** (63 + case K added post-deploy by Hermes: G1 55+ min(F1,F2)) |
| `node tests/engine.test.js` (regression) | **36 pass, 0 fail** |
| `node tests/i18n.test.js` | **I18N-OK (48)** |
| `node tests/ui.smoke.js` (EN+FR quiz→results, + PS module flows) | **SMOKE-OK (74)** |
Plan cases **A–J verified to the cent** (A/B/C/D/E/F/G/H/I/J per plan §tests), plus
Plan cases **A–K verified to the cent** (A–J per plan §tests; K added post-deploy by Hermes —
the G1 55+ "lowest of F1/F2" branch was untested), plus
engine integration: bridge present at 64 / absent at 65; deferred pension gated
(startAge 65 ⇒ no income/tax/clawback before 65); module-off legacy behaviour;
OAS clawback gating at 65+; pre-65 couple splitting lowers joint tax
@@ -176,7 +178,7 @@ OAS clawback gating at 65+; pre-65 couple splitting lowers joint tax
- [x] Results card with full breakdown + dated sources; insight; chart series
- [x] Pension flows through income/tax/PIA/GIS/splitting incl. pre-65 life-annuity splitting
- [x] Full EN + FR (parity 48/48, FR smoke green)
- [x] Tests: ps-pension 63/63 · engine 36/36 · i18n OK · smoke 74/74
- [x] Tests: ps-pension 70/70 · engine 36/36 · i18n OK · smoke 74/74
- [x] Audits EN+FR @320/360/390 + desktop + off-LAN (evidence in `audit/`)
- [x] Deployed (DEPLOY-OK) and reachable off-LAN (OFFLAN-OK)
- [x] This report + evidence committed & pushed
+16 -1
View File
@@ -1,5 +1,7 @@
// Retire-planner PS pension tests — run: node tests/ps-pension.test.js (PS-PENSION-PLAN.md Stage 3)
// Hand-computed cases A–J from the plan (AMPE proxy = 2026 YMPE = 74,600), verified to the cent,
// Hand-computed cases A–K from the plan (AMPE proxy = 2026 YMPE = 74,600), verified to the cent,
// plus K added post-deploy (Hermes, 2026-10-09): G1 55+ with S≥25 must apply PSPC's
// "compare Formula 1 and 2 and apply the lowest reduction" — the subtlest branch, was untested.
// plus engine integration checks (bridge window, start-age gating, PIA gating, pre-65 splitting).
//
// NOTE (documented in PS-PENSION-REPORT.md): two plan cases contain arithmetic typos.
@@ -136,6 +138,19 @@ console.log("---- J. service 1.5 yrs: return of contributions only ----");
ok("J notes lt2", r.notes.indexOf("lt2") >= 0, JSON.stringify(r.notes));
}
console.log("---- K. G1, 70k, 25 yrs, retire 59: PSPC 55+ rule — lowest of F1 5% / F2 25% = 5% ----");
{
const r = calc(G1, 59, 25, 70000);
ok("K pct 5% min(F1,F2) at 55+ (PSPC: compare F1 and 2, apply the lowest)", cent(r.reductionPct, 0.05) && r.formula === "min(F1,F2)", r.reductionPct + " " + r.formula);
ok("K lifetime 24,062.50", cent(r.lifetime, 24062.50), r.lifetime);
ok("K bridge 10,937.50", cent(r.bridge, 10937.50), r.bridge);
ok("K atRetirement 33,250.00 (35,000 × 0.95)", cent(r.pensionAtRetirement, 33250.00), r.pensionAtRetirement);
ok("K from65 22,859.38 (24,062.50 × 0.95)", cent(r.pensionFrom65, 22859.38), r.pensionFrom65);
const r2 = calc(G1, 56, 25, 70000);
ok("K2 pct 20% min(F1 20% / F2 25%) at 56", cent(r2.reductionPct, 0.20) && r2.formula === "min(F1,F2)", r2.reductionPct + " " + r2.formula);
ok("K2 atRetirement 28,000.00 (35,000 × 0.80)", cent(r2.pensionAtRetirement, 28000.00), r2.pensionAtRetirement);
}
console.log("---- Engine integration ----");
const base = Object.assign({}, E.defaults, {
age: 55, retireAge: 60, household: "single", province: "ON",