Skip to content

Pro-rate new_state_pension by reported amount#1634

Merged
MaxGhenis merged 2 commits intomainfrom
fix-state-pension-reported
Apr 19, 2026
Merged

Pro-rate new_state_pension by reported amount#1634
MaxGhenis merged 2 commits intomainfrom
fix-state-pension-reported

Conversation

@MaxGhenis
Copy link
Copy Markdown
Collaborator

Addresses issue #1632.

Summary

The previous new_state_pension formula paid the flat max rate (eligible * p.amount * WEEKS_IN_YEAR) to every NEW-type retiree, regardless of actual NI record length or pre-2016 SERPS/S2P accrual.

This switches to the same share-of-max pattern that basic_state_pension already uses: share = reported / max_new_data_year, scaled by the current period's max.

Why it matters

  • Partial NI records (< 35 qualifying years): retirees who in reality get pro-rated NSP were previously paid the full flat rate. The model now tracks their reported amount.
  • Protected Payment: retirees with substantial pre-2016 SERPS/S2P accrual have a starting amount that exceeds the flat new rate; the excess is the Protected Payment component of NSP. The old formula clamped everyone at p.amount, silently dropping Protected Payment entirely. The new formula allows share > 1 so it flows through.

Together these close part of the ~£12 bn residual state-pension gap vs the OBR target tracked in #1632.

Tests

Five new YAML cases covering under-max / exact-max / above-max / non-NEW / zero-max guard. Full 976-case policy suite still passes.

Generated with Claude Code

MaxGhenis and others added 2 commits April 19, 2026 15:24
The previous formula `eligible * p.amount * WEEKS_IN_YEAR` paid the
flat max NSP rate to every NEW-type retiree, regardless of their
actual NI record length or pre-2016 SERPS/S2P accrual. This:

- overstated retirees with partial NI records (< 35 qualifying years),
  who under the real NSP rules get pro-rated amounts; and
- silently dropped the Protected Payment component — the portion of a
  retiree's starting amount that exceeds the new flat rate because of
  pre-2016 SERPS/S2P contributions — because the formula clamped
  everyone at p.amount.

Switch to the same share-of-max pattern `basic_state_pension` already
uses: `share = reported / max_new_data_year`, scaled by the current
period's max. Share above 1 is preserved so Protected Payment flows
through, closing part of the ~£12 bn residual state-pension gap vs
the OBR target (tracked in #1632).

Five new YAML tests cover under-max (partial NI), exact max (full
rate), above-max (Protected Payment), non-NEW type returning zero,
and zero-max guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Subagent review of #1634 flagged two issues with the initial pass:

1. The uncapped NSP formula mixed 'headline new rate' with 'Protected
   Payment' semantics, surprising any downstream consumer who reads the
   variable name literally. BASIC routes excess through
   additional_state_pension; NEW should too for symmetry.

2. No test case for `state_pension_reported = 0` on a NEW-type
   retiree (deferral / FRS non-report) — the single highest-impact
   aggregate-drift scenario. Previously the formula paid full flat max
   regardless; under the new formula it correctly returns zero.

Changes:
- `new_state_pension`: cap share at 1 (use `min_(reported, max)`)
  like basic_state_pension does. Flat max is now the ceiling.
- `additional_state_pension`: extend to NEW-type retirees, using the
  NSP max as the ceiling (was BASIC-only, using the basic max). This
  captures Protected Payment as an explicit top-up.
- Added deferral/zero-report test case.
- Fixed stale 9627.80 comment to the correct 2021 figure 9339.20.

Full 978-case policy suite passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MaxGhenis
Copy link
Copy Markdown
Collaborator Author

Updated in response to a careful review. Two principled changes, one test addition, one comment fix:

  1. NSP now caps share at 1 (min_(reported, max) / max) — mirrors basic_state_pension. The flat-rate component stays bounded by statute; the pre-2016 SERPS/S2P Protected Payment is captured as an add-on in additional_state_pension (now extended to NEW type). This keeps the variable names semantically clean: new_state_pension is always the headline flat-rate component, additional_state_pension is always the earnings-related top-up, regardless of type.

  2. ASP extended to NEW-type retirees using the NSP max as the ceiling (was BASIC-only). Symmetric with BASIC's SERPS/S2P handling.

  3. Added test for state_pension_reported = 0 on a NEW-type retiree — returns zero for both variables. This is the biggest behavioural change vs the old formula, which paid the flat max regardless; now deferral and FRS non-report both correctly yield zero.

  4. Fixed stale 9627.80 comment to the correct 2021 max of 9339.20 (185.15 was the 2022 rate).

On the aggregate risk the reviewer flagged: rules should match statute, calibration should handle aggregate alignment. If the formula change shifts the NSP total vs OBR, that's something the weight-calibration pipeline can correct via upweighting NEW-type retirees; it shouldn't be patched by keeping a statutorily-wrong formula. Verifying actual aggregate impact remains useful but isn't a blocker.

Full 978-case policy suite still passes.

@MaxGhenis MaxGhenis merged commit 16fbff8 into main Apr 19, 2026
8 checks passed
@MaxGhenis MaxGhenis deleted the fix-state-pension-reported branch April 19, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant