Skip to content

Set State Pension age from date of birth, including the rise to 67 - #1899

Open
MaxGhenis wants to merge 3 commits into
mainfrom
state-pension-age-67-phase-in
Open

MaxGhenis wants to merge 3 commits into
mainfrom
state-pension-age-67-phase-in

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

PolicyEngine UK held one State Pension age per year, 66 from 2020 (gov/dwp/state_pension/age/male.yaml and female.yaml). So it never applied the Pensions Act 2014 s.26 rise to 67 for people born on or after 6 April 1960. From 2026-27, every 66-year-old counted as over State Pension age, which moves State Pension, Pension Credit, Universal Credit, National Insurance and the HBAI pensioner group.

The law sets State Pension age by date of birth: Pensions Act 1995 Sch 4 para 1, as amended by the Pensions Acts 2007, 2011 and 2014. This PR encodes that timetable row for row and works out each person's State Pension age from their date of birth.

  • People born 6 April 1960 to 5 March 1961 reach it at 66 years and 1 to 11 months. People born from 6 March 1961 reach it at 67.
  • In an annual model this means 75% of 66-year-olds are over State Pension age at the middle of 2026-27, 25% in 2027-28, and none from 2028-29. Main has 100% throughout.

How the model represents it

Parameters (gov/dwp/state_pension/age/). The old per-year male and female ages are replaced by:

Parameter Encodes
age_by_birth_date single_amount scale on date of birth (YYYYMMDD): the age, in months, at which State Pension age is attained; 0 where the statute sets a day
day_by_birth_date the same scale shape: the day (YYYYMMDD) on which it is attained (tables 1, 2 and 4); 0 where the statute sets an age
male/age, male/born_before rule (1): men born before 6 December 1953 attain it at 65

The two scales cover women, and men born on or after 6 December 1953, which is also the Pension Credit qualifying age for everyone (SPCA 2002 s.1(6)). A person attains State Pension age on the later of the two. Every row cites its rule and table and quotes the statutory period.

  • Month-end dates. An age of "N years and M months" is attained on the same day of the month, or on the month's last day where that day doesn't exist. This reproduces the three days in rule (7A) without special-casing them.
  • Effective date. Rows are keyed at 1995-07-19. Every amendment moved only people who had not yet reached State Pension age, so the current timetable gives the right status at any past date. Queries before the first key stay safe.

Variables:

  • months_since_last_birthday (new). Months since the person's last birthday on 6 October, the middle of the fiscal year. Together with age it places the date of birth. A birth instant within a day is taken as the day starting at or after it, so the person's legal age on 6 October is age, and each age is attained at the commencement of the anniversary (Family Law Reform Act 1969 s.9(1)).
    • A fractional age is read as the exact age.
    • Simulations of a household situation use 6, the middle of the year of age, so results stay deterministic and explainable.
    • Simulations built from data (the new Simulation.built_from_dataset, so also a region or constituency filtered from the data) spread each single year of age and sex evenly over the year. Records are ordered by splitmix64_uniform(person_id, salt=2) (the uc_deduction_random_draw precedent) and placed by their share of the group's weight (stratified_uniform), so the weighted share over State Pension age matches the statute to within one record's weight, not merely in expectation. Independent hashed draws would have a standard deviation of about 50k people a year at age 66.
    • filter_dataset carries each person's value into a household it extracts, so an extracted household keeps the status it had in the full simulation.
  • state_pension_age: now the person's own State Pension age, from their date of birth.
  • months_since_state_pension_age (new): months between attaining State Pension age and 6 October (negative before).
  • is_SP_age: attained by 6 October, so over State Pension age for most of the year.
  • state_pension_type: BASIC if the person reached State Pension age before 6 April 2016 (Pensions Act 2014 s.1(2)), else NEW. Main approximated this from integer ages.
  • meets_savings_credit_age_requirement: reached State Pension age before 6 April 2016 and has attained 65 (SPCA 2002 s.3(1)(a)). Main used the current State Pension age as if it were each cohort's own. Because ages are held fixed across years, that sent Savings Credit to £0 in 2030-31 and excluded men who reached it at 65 in 2015 (e.g. a man aged 75 in 2025). The new savings_credit/minimum_age parameter (65) adds the statutory age limb.
  • additional_state_pension: zero below State Pension age. Main kept paying additional State Pension and protected payment to people moved below it.

Docs: docs/book/programs/gov/dwp/state-pension.md has a new State Pension age section.

Invariants (stated and tested)

  1. Every parameter row is a statutory row. test_state_pension_age.py::TestParametersMatchStatute parses the verbatim statute text (fixture from legislation.gov.uk, cross-checked against a second independent parse: 77/77 rows agree). It rebuilds both scales and the male rule from the text and requires exact equality. It also checks that the statutory periods are contiguous.
  2. Model = statute, day by day. A reference written straight from the statute text with datetime, sharing no code with the model, covers births every third day from 1945 to 1984, both sides of every statutory boundary, both sexes and 13 fiscal years (2015-20, 2025-28, 2044-46). For each, is_SP_age, state_pension_type and the Savings Credit age test agree exactly, and the attainment day agrees to within a day. Status is only ever read on the 6th, where the grid is exact.
  3. Bounds, monotonicity and consistency (Hypothesis): State Pension age lies in [60, 68]; the attainment day never falls earlier for a later birth; is_SP_age ⇔ months_since_state_pension_age ≥ 0.
  4. Aggregates match the statute. In a weighted, microdata-like simulation, the weighted share of each age 60-70 and sex over State Pension age equals the statutory mid-year share, within the largest weight plus one day of births. This holds for 2015-20 and 2026-28.
  5. stratified_uniform (Hypothesis): values lie in [0, 1), are deterministic, don't depend on record order, and the weighted CDF within each stratum is uniform to within half the largest weight.
  6. Legal age is consistent (Hypothesis): for any whole age, any position in the year (including instants within a day) and any year from 2015 to 2050, the day of birth the model uses gives that legal age on 6 October, and status is the statute's for that day.
  7. Data-built simulations spread birthdays; situations don't: a data-built simulation with under a million people of weight (a constituency-sized subset) still gets the statutory shares, household situations get 6, and a filter_dataset extract keeps each person's value and status.

Boundary YAML tests: state_pension_age.yaml (25 cases, at every rule change, expected values from the day-level reading) and is_SP_age.yaml (15 phase-in cases). The phase-in cases cover mixed-age couples (UC, not Pension Credit), State Pension of zero below State Pension age, and Class 1 NI liability. state_pension_type.yaml and meets_savings_credit_age_requirement.yaml gain tests at the 6 April 2016 boundaries.

Microsimulation impact

Enhanced FRS 2024-25 (policyengine-uk-data-private 1.57.4, snapshot ace89433). Every figure is from a full microsimulation run of each version: origin/main 34e10d5, this branch at c46893e, and the same code with table 3 and rule 8 held at 66 (the counterfactual that isolates the rise). Scripts and CSVs are kept with the session's review notes.

The rise from 66 to 67 (this branch minus the same code with table 3 and rule 8 held at 66)

2025-26 2026-27 2027-28 2028-29 2029-30 2030-31
Adults over State Pension age (HBAI pensioners) k +0 -148 -436 -588 -591 -593
State Pension £bn +0.00 -1.64 -5.33 -7.27 -7.49 -7.71
Pension Credit (after take-up) £bn +0.00 -0.01 -0.04 -0.09 -0.09 -0.10
Pension Credit entitlement (before take-up) £bn +0.00 -0.02 -0.04 -0.10 -0.10 -0.10
Pension Credit recipient benefit units k +0 -3 -7 -22 -22 -22
Universal Credit £bn +0.00 +0.03 +0.07 +0.10 +0.10 +0.09
Universal Credit recipient benefit units k +0 +3 +7 +11 +11 +11
Housing Benefit £bn +0.00 +0.00 +0.00 +0.00 +0.00 +0.00
Council Tax Reduction £bn +0.00 +0.01 +0.02 +0.02 +0.02 +0.02
Winter Fuel Payment £bn +0.00 -0.01 -0.06 -0.07 -0.06 -0.06
Employee Class 1 NI £bn +0.00 +0.06 +0.07 +0.08 +0.08 +0.09
Employer Class 1 NI £bn +0.00 +0.15 +0.21 +0.23 +0.24 +0.25
Income tax £bn +0.00 -0.29 -0.69 -1.28 -1.35 -1.42
Household net income £bn +0.00 -1.43 -4.77 -6.17 -6.32 -6.49
Government balance £bn +0.00 +1.58 +4.97 +6.41 +6.56 +6.75
People in absolute poverty, BHC k +0 +55 +389 +431 +435 +437
People in absolute poverty, AHC k +0 +42 +407 +442 +443 +446

Other corrections (exact birth cohorts, the Savings Credit age test, no additional State Pension below State Pension age)

2025-26 2026-27 2027-28 2028-29 2029-30 2030-31
Adults over State Pension age (HBAI pensioners) k +0 +0 +0 +0 +0 +0
State Pension £bn -0.16 -0.02 -0.09 -0.20 +0.01 -0.06
Pension Credit (after take-up) £bn +0.06 +0.04 +0.05 +0.06 +0.07 +0.59
Pension Credit entitlement (before take-up) £bn +0.15 +0.13 +0.05 +0.10 +0.00 +0.61
Pension Credit recipient benefit units k +38 +40 +44 +36 +43 +198
Universal Credit £bn -0.00 -0.00 -0.00 -0.00 -0.00 +0.00
Universal Credit recipient benefit units k +0 +0 +0 +0 +0 +0
Housing Benefit £bn +0.00 +0.01 +0.00 +0.00 +0.00 +0.00
Council Tax Reduction £bn -0.00 +0.00 +0.00 +0.00 +0.00 -0.00
Winter Fuel Payment £bn +0.00 +0.00 -0.00 +0.00 +0.00 +0.00
Employee Class 1 NI £bn +0.00 +0.00 +0.00 +0.00 +0.00 +0.00
Employer Class 1 NI £bn +0.00 +0.00 +0.00 +0.00 +0.00 +0.00
Income tax £bn -0.03 -0.01 -0.02 -0.04 +0.02 -0.01
Household net income £bn -0.07 +0.04 -0.01 -0.09 +0.07 +0.57
Government balance £bn +0.07 -0.04 +0.01 +0.09 -0.07 -0.57
People in absolute poverty, BHC k -3 +2 +5 -7 +3 -2
People in absolute poverty, AHC k -1 +4 +15 +9 +7 -95

Whole PR (this branch minus origin/main)

2025-26 2026-27 2027-28 2028-29 2029-30 2030-31
Adults over State Pension age (HBAI pensioners) k +0 -148 -436 -588 -591 -593
State Pension £bn -0.16 -1.66 -5.42 -7.47 -7.48 -7.77
Pension Credit (after take-up) £bn +0.06 +0.03 +0.01 -0.03 -0.03 +0.49
Pension Credit entitlement (before take-up) £bn +0.15 +0.11 +0.01 +0.00 -0.10 +0.51
Pension Credit recipient benefit units k +38 +37 +36 +14 +20 +176
Universal Credit £bn -0.00 +0.03 +0.07 +0.10 +0.10 +0.09
Universal Credit recipient benefit units k +0 +3 +7 +11 +11 +11
Housing Benefit £bn +0.00 +0.01 +0.00 +0.00 +0.00 +0.01
Council Tax Reduction £bn -0.00 +0.01 +0.02 +0.02 +0.02 +0.02
Winter Fuel Payment £bn +0.00 -0.01 -0.06 -0.07 -0.06 -0.06
Employee Class 1 NI £bn +0.00 +0.06 +0.07 +0.08 +0.08 +0.09
Employer Class 1 NI £bn +0.00 +0.15 +0.21 +0.23 +0.24 +0.25
Income tax £bn -0.03 -0.30 -0.71 -1.32 -1.33 -1.43
Household net income £bn -0.07 -1.38 -4.78 -6.27 -6.25 -5.92
Government balance £bn +0.07 +1.54 +4.99 +6.50 +6.50 +6.17
People in absolute poverty, BHC k -3 +57 +394 +424 +438 +435
People in absolute poverty, AHC k -1 +46 +422 +451 +450 +351

Levels on this branch

2025-26 2026-27 2027-28 2028-29 2029-30 2030-31
People aged 66 k 581 584 586 588 591 593
of whom over State Pension age k 581 436 150 0 0 0
People aged 67 over State Pension age k 465 467 469 470 472 475
People aged 65 over State Pension age k 0 0 0 0 0 0
Savings Credit before eligibility and take-up (£bn) £bn 0.78 0.74 0.69 0.69 0.65 0.62

Caveats:

  • 66-year-olds are under-weighted. The data has 577k weighted 66-year-olds against 752,456 in the ONS mid-2024 UK estimate (Nomis NM_2002_1), with women at about half. The rise's effects are probably understated by about a quarter.
  • Employer NI. The employer NI increase is an artifact of ni_class_1_employer stopping at State Pension age. Charge employer NI on employees over state pension age #1895 fixes that; employers keep paying after the employee reaches it.
  • HBAI pensioners. "Adults over State Pension age" is the HBAI pensioner group: is_hbai_pensioner = is_hbai_adult & is_SP_age in Replace generic child and adult flags with each programme's legal definitions #1896, which inherits this fix.
  • State Pension composition. The small 2025-26 State Pension change comes from exact basic/new cohort boundaries interacting with additional_state_pension, which splits the reported amount by its data-year type. With ages held fixed across years, a record's data-year type can differ from its period type, so basic/new plus additional can overlap or leave a gap. Switching to the period type would remove about £1bn (2025-26) rising to about £7bn (2030-31) of overlap already on main. That needs its own review, so it's a separate follow-up, not part of this PR.

Removed parameters

gov.dwp.state_pension.age.male and .female are removed. The towncrier fragment is removed (a minor release), following this repo's practice for removed parameters (2.28.1 removed Pension Credit parameters as a patch); downstream packages pin exact versions (policyengine.py pins policyengine-uk==2.102.3), so upgrading is always explicit. The fragment says how to express a State Pension age reform now. Known readers outside this repo:

  • PolicyEngine/microcosm packages/microcosm-build/src/microcosm/build/uk_runtime/frs_take_up.py reads ages.male(instant) and ages.female(instant) to bound the UC take-up population. Migration is tracked in microcosm#1069.
  • PolicyEngine/nics-exemption-inactive-employees src/nics_exemption/pipeline.py reads them for SPA_MALE and SPA_FEMALE. Its working-age bounds need a migration follow-up.
  • PolicyEngine/uk-triple-lock src/triple_lock/config.py, STATE_PENSION_AGE_CHANGES, applies 2028–2042 overrides to both removed scalar paths. uk-triple-lock#21 tracks replacing those overrides with the built-in cohort timetable or an explicit cohort-based reform.
  • TheAxiomFoundation/axiom-oracles axiom_oracles/bridges/efrs_uk.py compares PolicyEngine's whole age with state_pension_age. axiom-oracles#576 prepares the migration and waits for this PR's release; the statutory specification follow-up is rulespec-uk#369.

Readers need cohort-specific eligibility from is_SP_age or state_pension_age; reform writers need the birth-date timetable parameters. These paths were checked against the downstream source during this review.

axiom: uk/statute/ukpga/1995/26/schedule/4/paragraph/1 TheAxiomFoundation/rulespec-uk#369 queued

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits September 29, 2026 20:41
The State Pension age parameters held one age per year and stayed at 66
from 2020, so the model never applied the Pensions Act 2014 s.26 rise to
67 for people born on or after 6 April 1960. From 2026-27 every
66-year-old counted as over State Pension age.

Encode Pensions Act 1995 Sch 4 para 1 by date of birth, row for row:
age_by_birth_date (the age, in months) and day_by_birth_date (the day,
where the statute sets one), for women and for men born on or after 6
December 1953, plus rule (1) for men born earlier. A person attains
State Pension age on the later of the two.

months_since_last_birthday places each date of birth within the year of
age, measured at 6 October, the middle of the fiscal year. A fractional
age is the exact age; single households use the middle of the year of
age; representative microdata spreads each single year of age and sex
evenly by weight, so the weighted share over State Pension age is the
statutory share (three quarters of 66-year-olds in 2026-27, a quarter
in 2027-28, none after).

state_pension_age is now the person's own State Pension age, and
is_SP_age, the basic/new State Pension split and the Savings Credit age
test (SPCA 2002 s.3(1)(a), including its age-65 limb) all follow it.
No additional State Pension is paid below State Pension age.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A birth instant within a day is the day starting at or after it, so a
  person's legal age on 6 October is their age and each age is attained at
  the commencement of the anniversary (Family Law Reform Act 1969 s.9(1)).
  The time-of-day carry onto the anniversary is gone.
- One helper gives exact age in months (float64), capping months since the
  last birthday a few minutes short of 12 so it never rounds onto the next
  birthday. Hypothesis found that edge.
- Birthdays are spread over the year in any simulation built from data
  (Simulation.built_from_dataset), not whenever weights exceed a million,
  so a constituency or local authority filtered from the data is still
  spread, and filter_dataset carries each person's place into an extract.
- Rewrite the old Savings Credit cases from dates of birth, keep one that
  tests the state_pension_age override, and test the attainment day
  exactly in the day-level differential test.
- male/born_before is a YYYYMMDD number like the scales; labels no longer
  hardcode the date; the NI reference notes its numbering; the removed
  fragment and docs say how to reform the timetable now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Sep 30, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Sep 30, 2026
The two boundary cases test how primary and secondary Class 1 split at
state pension age, not how that age is derived. Setting is_SP_age as an
input keeps them valid under #1899, which derives it from date of birth
(a 66-year-old in 2026-27 is then usually over it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vahid-ahmadi

Copy link
Copy Markdown
Collaborator

Reviewed: approve with nits.

Working out State Pension age from date of birth (encoded row by row, with the 6 October status date and the weighted spread of birthdays within each year of age) matches the statute and gives the right population shares.

  1. Should fix: removing gov.dwp.state_pension.age.male/.female also breaks PolicyEngine/uk-triple-lock src/triple_lock/config.py (STATE_PENSION_AGE_CHANGES), which isn't in the "Known readers" list, and any saved app/API policies on those paths. Add a deprecation alias that raises an error naming the replacement, and add uk-triple-lock to the follow-ups.
  2. Should fix: policyengine_uk/utils/dates.py:92 (first_of_month + 1) triggers NumPy's "generic timedelta unit … will raise an error" warning, 61 times in the tests. Use first_of_month + np.timedelta64(1, "M").
  3. Nit: in household simulations every whole-age person is born on 6 April, which is the first day of a timetable bracket. So a 66-year-old gets the full State Pension in 2026-27 and none in 2027-28. That's correct, but say it in the changelog for app users, and consider accepting a date of birth as an input.

Verified: all 77 rows in the test fixture match the current Sch 4 Part I text on legislation.gov.uk (rules 1–10, tables 1–4, rule 7A). I checked the shares of 66-year-olds over State Pension age by hand: 75% in 2026-27 (born up to 5 July 1960), 25% in 2027-28 (born up to 5 January 1961), and none after that. test_state_pension_age.py passed (24/24) and the four YAML files passed (61/61). Clones and branches carry built_from_dataset. It merges cleanly with #1939. CI is green.

This branch has not been deployed

No deployments
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.

2 participants