Skip to content

Read the State Pension Credit qualifying age for UC's no work-related requirements group (reg 89(1)(a)) - #2160

Merged
MaxGhenis merged 2 commits into
mainfrom
uc-wrr-spc-qualifying-age
Oct 6, 2026
Merged

MaxGhenis merged 2 commits into
mainfrom
uc-wrr-spc-qualifying-age

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2161

Follow-up to #2081 and #1907.

Summary

UC Regs 2013 reg 89(1)(a) puts a claimant in the group with no work-related requirements (Welfare Reform Act 2012 s.19) if "the claimant has reached the qualifying age for state pension credit". #2081 encoded that route with is_SP_age until #1907 added the qualifying age. This PR switches it to has_attained_state_pension_credit_qualifying_age.

  • The law. WRA 2012 s.4(4) gives "the qualifying age for state pension credit" the meaning in State Pension Credit Act 2002 s.1(6), for the basic condition in s.4(1)(b). Interpretation Act 1978 s.11 gives the phrase the same meaning in the regulations made under the Act. UC Regs reg 2 does not define it. The meaning is a woman's pensionable age, and for a man "the age which is pensionable age in the case of a woman born on the same day as the man".
  • Who changes. Only men born before 6 December 1953, and only in 2018-19 and earlier. Pensions Act 1995 Sch 4 para 1 rule (1) gives them a pensionable age of 65, but they reach the qualifying age on the day a woman born the same day reaches pensionable age: at 60 if born before 6 April 1950 (rule (2)), otherwise the table 1 day (rule (3)). That day comes before the man's 65th birthday, or on it for a birth on 6 November 1953. Under reg 3(2)(a) such a man could claim UC jointly with a partner under the qualifying age. He is now in s.19 from his qualifying age, so the minimum income floor (reg 62(1)(b): s.22 only) stops applying then, not at 65.
  • One line of code in uc_work_related_group_apart_from_earnings, plus a reference to SPCA 2002 s.1(6). No other UC rule read is_SP_age for this test.

Tests

YAML, in uc_work_related_group_apart_from_earnings.yaml (6 new cases). Each case is a couple with a partner aged 40 (41 in case 3, a year on). Every expected day comes from Pensions Act 1995 Sch 4 para 1 as it stood on 6 October 2016, read on legislation.gov.uk:

Case Elder member Year Qualifying age reached Own State Pension age Group
1 man born 6 April 1952 2016-17 6 May 2014 (table 1) 6 April 2017 (65) no requirements (was all requirements)
2 man born 6 June 1953 2016-17 6 March 2017 (table 1), after 6 October 6 June 2018 all requirements
3 the same man 2017-18 6 March 2017 6 June 2018 no requirements (was all requirements)
4 woman born 6 April 1952 2016-17 6 May 2014 6 May 2014 no requirements (unchanged)
5 man born 6 December 1953 2018-19 6 March 2019 (table 2, both sexes) 6 March 2019 all requirements (unchanged)
6 case 1's man, self-employed (£5,000 profit) 2016-17 6 May 2014 6 April 2017 no requirements, so uc_mif_applies is false (was true)

On the base commit (3b7c1f9fe, main after #2081 and #1907), the new YAML file fails exactly cases 1, 3 and 6, each on uc_work_related_group_apart_from_earnings (ALL_REQUIREMENTS where the statute gives NO_REQUIREMENTS). Cases 2, 4 and 5 pass on both commits. A direct run of case 6 on the base gives uc_mif_applies [true, false], so "was true" holds: 3 failed, 28 passed on the base, 31 passed on this branch.

Property and statute tests, added to test_uc_work_related_groups_properties.py (invariant 8):

  1. Statute differential. Take births every fifth day from 6 April 1948 to April 1956, with both sides of 6 December 1953. Use both sexes and the years 2015-16 to 2019-20. Each person is the elder member of a couple. The elder is in s.19 exactly when the statute puts a woman born on the same day at pensionable age on or before 6 October. The statute reading comes from test_state_pension_age.reference_attainment_day, which shares no code with the model. The partner stays in s.22.
  2. Where the routes differ. In 2015-16 to 2018-19, some men are in s.19 while under their own State Pension age, and all of them were born before 6 December 1953. From 2019-20 nobody is. Everyone over State Pension age is in s.19.
  3. Property (Hypothesis). Families include the earlier property tests' circumstances: children, caring, limited capability, the prescribed descriptions and self-employment. Their first adult is 60 to 65, of either sex, in 2015-16 to 2018-19. A claimant past the qualifying age is in s.19 whatever else applies, and never has the floor. Differential against the old route, rebuilt by supplying is_SP_age as the qualifying-age status: only claimants past the qualifying age and under State Pension age change group, all into s.19. A fixed example (case 1's man) makes every run exercise the change.

Run locally:

All on 00536b934, one at a time (the head before the review's comment fixes; see below for 84a1a1ce3):

  • uv run --frozen --extra dev policyengine-core test policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit -c policyengine_uk: 376 passed (uc_work_related_group_apart_from_earnings.yaml: 31).
  • uv run --frozen --extra dev pytest -q -p no:cacheprovider policyengine_uk/tests/test_uc_work_related_groups_properties.py: 14 passed (the 7 existing tests, plus 5 years of the statute differential, the before-2019 check and the Hypothesis differential), 2,035 s. The host was heavily loaded, and this file, unlike the repo's other property files, builds a new model for every Simulation. So the run added an out-of-tree pytest plugin that applies the repo's own cloned_uk_tax_benefit_system conftest fixture to every test. Nothing in the repo changed. CI runs the file as committed.
  • uv run --frozen --extra dev ruff format --check policyengine_uk: 1,264 files already formatted.
  • Fail-on-base: as above.
  • Head 84a1a1ce3 changes comments only, from the review's nits. Both changed Python files have an AST identical to 00536b934's (ast.dump), and the YAML file parses to the same 31 cases. On 84a1a1ce3, policyengine-core test on the WRR YAML file: 31 passed; ruff format --check policyengine_uk: 1,264 files already formatted.

Microsimulation impact

Real Microsimulation runs on the Enhanced FRS 2024-25 (private copy, SHA-256 e433e532b17bd8ce…), one at a time: base 3b7c1f9fe9aa0db7809c6b9ba1ed3b26d905e6da (main after #2081 and #1907) against this branch at 00536b934e8e2f25aeffcf994fef0ee404eaa9ac, both with a clean tree, for 2025-26 to 2030-31. Each run saves 17 arrays a year: per person, the weight, id, is_SP_age, has_attained_state_pension_credit_qualifying_age, the work-related group, uc_mif_applies, uc_minimum_income_floor_gross, uc_individual_earned_income and the BHC and AHC poverty flags; per benefit unit, the weight, id, universal_credit, universal_credit_pre_benefit_cap and uc_earned_income; per household, household_net_income and the weight. The comparison is exact (no tolerance).

Year Arrays compared Arrays that differ Records with qualifying-age status != State Pension age status (branch) UC change (£bn) Household net income change (£bn) No-requirements group change (thousand people) Floor applies change (thousand people)
2025-26 17 none 0 +0.000000 +0.000000 +0.000 +0.000
2026-27 17 none 0 +0.000000 +0.000000 +0.000 +0.000
2027-28 17 none 0 +0.000000 +0.000000 +0.000 +0.000
2028-29 17 none 0 +0.000000 +0.000000 +0.000 +0.000
2029-30 17 none 0 +0.000000 +0.000000 +0.000 +0.000
2030-31 17 none 0 +0.000000 +0.000000 +0.000 +0.000

The runs used 00536b934. Head 84a1a1ce3 changes only comments (AST-identical code), so the result holds for it unchanged.

Exactly zero. No array differs in any year, and no person in the data has qualifying-age status different from State Pension age status. That follows from the years: the simulated years are 2025-26 onward, and the two statuses have agreed for everyone since 2019-20 (men born 5 December 1953, the last cohort with a male pensionable age of 65, turned 65 on 5 December 2018). The change matters for 2018-19 and earlier, which the YAML and property tests cover.

Invariants

  • Statute agreement: reg 89(1)(a) holds exactly when SPCA s.1(6)'s day is on or before 6 October, the model's mid-year convention. Test 1 checks this.
  • Superset: the qualifying age is never later than State Pension age (Use the State Pension Credit qualifying age, and start-of-year status for Class 4 NI #1907's invariant), so the route never takes a claimant out of s.19. Tests 2 and 3 check this.
  • Locality: the change moves only claimants past the qualifying age and under State Pension age, and none from 2019-20. Test 3 checks the first part against the old route in 2015-16 to 2018-19 (it generates only those years); test 2 checks that, on its birth grid (where the qualifying age is the elder's only s.19 route), nobody under State Pension age is in s.19 in 2019-20, the grid's last year. Later years follow from Sch 4 tables 2 to 4 applying to both sexes, and the impact runs find no differing record in 2025-26 to 2030-31.
  • Floor scope: nobody past the qualifying age has the minimum income floor. Case 6 and test 3 check this.

Caveats

  • Same mistake elsewhere. git grep -n is_SP_age -- policyengine_uk/variables/gov/dwp policyengine_uk/utils on this head finds additional_state_pension and state_pension_type (the State Pension itself, where pensionable age is the right test), winter_fuel_allowance (reads the qualifying age before 2024-25 and is_SP_age from 2024-25, where its comment cites SI 2024/869's "has reached pensionable age") and comments in utils. No rule whose law says "qualifying age for state pension credit" still reads is_SP_age. The benefit-cap readers (is_benefit_cap_exempt_health_disability, is_benefit_cap_exempt_earnings) no longer read is_SP_age or has_pensioner. Readers outside gov/dwp and utils (National Insurance, HBAI, the Scottish Pension Age Winter Heating Payment, a contrib basic income) were not checked against their law here.
  • Wording elsewhere (not in this PR). The documentation of has_attained_state_pension_credit_qualifying_age (from Use the State Pension Credit qualifying age, and start-of-year status for Class 4 NI #1907) says men born before 6 December 1953 "reach it before" their State Pension age; for a birth on 6 November 1953 the two days coincide. The formula is unaffected. Tracked in Follow-ups from #2160: qualifying-age docstring, UC work-related groups property-file speed, changelog fragment example #2162.
  • Test speed (follow-up, not in this PR). test_uc_work_related_groups_properties.py does not use pytestmark = pytest.mark.usefixtures("cloned_uk_tax_benefit_system"), which the repo's other property files use, so it loads the model again for every Simulation. Tracked in Follow-ups from #2160: qualifying-age docstring, UC work-related groups property-file speed, changelog fragment example #2162.

Axiom parity

On rulespec-uk main (ac0a151), uk/regulations/uksi/2013/376/89.yaml does not exist, so reg 89 stays queued in #396. That issue already carries reg 89's module, law, outputs and tests (from #2081). The comment for this PR adds the qualifying-age companion cases and the review_finding sentence for (1)(a). SPCA 2002 s.1 is encoded and its man/woman-same-birthday case passes. Computing the qualifying age from a date of birth needs Pensions Act 1995 Sch 4 para 1, which is queued in #369.

axiom: uk/regulation/uksi/2013/376/89 TheAxiomFoundation/rulespec-uk#396 queued (reg 89(1)(a): https://github.com/TheAxiomFoundation/rulespec-uk/issues/396#issuecomment-6009163278; reg 89 module, law, outputs and tests: https://github.com/TheAxiomFoundation/rulespec-uk/issues/396#issuecomment-5968698564)
axiom: uk/statute/ukpga/2002/16/1 encoded-correct (`uk/statutes/ukpga/2002/16/1.test.yaml` case `man_uses_woman_same_birthday_age`: a man's qualifying age is a woman's pensionable age for the same birthday)
axiom: uk/statute/ukpga/1995/26/schedule/4/paragraph/1 TheAxiomFoundation/rulespec-uk#369 queued (computing the qualifying age from date of birth; https://github.com/TheAxiomFoundation/rulespec-uk/issues/369#issuecomment-6009164647)

Review

  • Full review of 00536b934 (Subfleet 20261006-001159-uc-wrr-spcqa-review, GPT-6.1 Sol, task review): APPROVE, nothing blocking or should-fix. It re-derived the six cases from Sch 4's 2016 text, confirmed the law chain, the non-vacuity of invariant 8 (165, 116, 61, 6 and 0 men moved in 2015-16 to 2019-20 on the grid), the byte-identical impact archives and the Axiom comments. Its five nits, and what was done about them:
    • The 6 November 1953 edge, where the qualifying age falls on the man's 65th birthday: fixed in the code and test comments (84a1a1ce3).
    • Rule (2) at 60 for births before 6 April 1950: fixed in the YAML comment and in this body.
    • Partners are 40 or 41, and the 2019-20 check is test 2's: fixed in this body.
    • Fixes #2161 added.
    • A leading - on the changelog fragment: not taken. Towncrier adds the bullet, and fragments that start with - render as - - in CHANGELOG.md (2.122.2, 2.121.0). This fragment matches Apply the UC minimum income floor only to claimants subject to all work-related requirements #2081's.
  • Delta review of 84a1a1ce3 (Subfleet 20261006-022129-uc-wrr-spcqa-delta, Opus 5.5 on claude-9, task review): APPROVE, nothing blocking or should-fix. It confirmed the delta is comments only (AST, comment-stripped token stream and parsed YAML all equal) and checked the new comments against Sch 4's 2016 XML. Across every birth from 1 January 1880 to 5 December 1953, a man's qualifying day is never after his 65th birthday and falls on it only for 6 November 1953. It also agreed with declining the changelog nit, and re-ran the WRR YAML file (31 passed) and the format check. Its three nits: test 2's year range (fixed above in Locality), and two follow-ups outside this PR (the Use the State Pension Credit qualifying age, and start-of-year status for Class 4 NI #1907 docstring and the changelog example in docs/engineering/skills/github-prs.md), filed with the test-speed item as Follow-ups from #2160: qualifying-age docstring, UC work-related groups property-file speed, changelog fragment example #2162.

🤖 Generated with Claude Code

UC Regs 2013 reg 89(1)(a) puts a claimant who "has reached the qualifying
age for state pension credit" in WRA 2012 s.19 (no work-related
requirements). That is SPCA 2002 s.1(6), the meaning the phrase has in
WRA 2012 s.4(4), carried into the regulations by Interpretation Act 1978
s.11. uc_work_related_group_apart_from_earnings read is_SP_age; it now
reads has_attained_state_pension_credit_qualifying_age (#1907).

The two differ only for men born before 6 December 1953, in 2018-19 and
earlier. Adds 6 YAML cases from Pensions Act 1995 Sch 4 para 1 (as at
6 October 2016), a statute differential over a birth grid against
test_state_pension_age.reference_attainment_day, a check that the routes
differ only before 2019-20, and a Hypothesis differential against the
State Pension age route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A man born on 6 November 1953 reaches the qualifying age on his 65th
birthday, so the comments now say "before, or on the same day if born on
6 November 1953". Women born before 6 April 1950 reach pensionable age at
60 (Pensions Act 1995 Sch. 4 para. 1 rule (2)), so the YAML comment no
longer says every man takes the table 1 day. Comments only: both Python
files are AST-equal to the previous head and the YAML parses identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit 86e34ed into main Oct 6, 2026
6 checks passed
@MaxGhenis
MaxGhenis deleted the uc-wrr-spc-qualifying-age branch October 6, 2026 16:38
MaxGhenis added a commit that referenced this pull request Oct 6, 2026
#2160 reads the State Pension Credit qualifying age for UC reg 89(1)(a) in
uc_work_related_group_apart_from_earnings and extends
test_uc_work_related_groups_properties.py. No conflicts; it touches none of
this PR's files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

UC reg 89(1)(a) reads State Pension age instead of the State Pension Credit qualifying age

1 participant