Skip to content

Place the UC and Pension Credit QYP terminal date from the birth date - #2300

Merged
juaristi22 merged 20 commits into
mainfrom
qyp-terminal-date-19
Nov 6, 2026
Merged

juaristi22 merged 20 commits into
mainfrom
qyp-terminal-date-19

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2291

Reproducer: policyengine_uk/tests/test_qualifying_young_person_terminal_date_properties.py::test_universal_credit_and_pension_credit_agree_on_every_combination

axiom: TheAxiomFoundation/rulespec-uk#531 queued (UC reg 5(1)(b), uk:regulations/uksi/2013/376/5#continued_education_or_training_qualifying_condition; no companion case on main sets the 19th-birthday input false, the issue's cases need a signed re-encode of reg 5, and a billed encoder run needs approval); TheAxiomFoundation/rulespec-uk#360 queued (SPC reg 4A(1)(b); addendum; rulespec-uk's corpus pin holds reg 4A's row, and a billed encoder run needs approval)

Stack. The merge order is #2300, #2301, then #2302. #2302 carries this PR and #2301, so landing #2302 lands both. This PR builds on #2279's Pension Credit qualifying young person, which has merged into main; 25cb007 merges main 47a2817 into this branch. This PR's own commits:

git log -p --no-merges origin/qyp-terminal-date-19 ^origin/main

Summary

A young person in non-advanced education or approved training is a qualifying young person (QYP) "up to, but not including, the 1st September following their 19th birthday". The model treated a 19-year-old three ways:

Where 19-year-old at school, course begun before 19
Universal Credit, household calculation not a QYP: the terminal-date input defaulted to false
Universal Credit, microdata a QYP: policyengine-uk-data sets that input true for every 19-year-old in qualifying education or training
Pension Credit, everywhere a QYP: no terminal-date condition at all

This PR places the date from the date of birth for both programmes. Against its base (main 4d80b29 with #2279 merged), 2026-27, a lone parent aged 45 with a 19-year-old at school (the same on main 9475c95 before #2285):

Variable Base This PR
is_qualifying_young_person_for_universal_credit (the 19-year-old) false true
uc_individual_child_element £0 £4,222.56 (£351.88 × 12)
is_uc_work_allowance_eligible false true
is_qualifying_young_person_for_pension_credit true true

It follows up two of #2279's items: "Pension Credit reg. 4A(1)(b)'s 1 September" and the Universal Credit default.

The law

  • UC Regs 2013 reg. 5(1): "A person who has reached the age of 16 but not the age of 20 is a qualifying young person for the purposes of Part 1 of the Act and these Regulations— (a) up to, but not including, the 1st September following their 16th birthday; and (b) up to, but not including, the 1st September following their 19th birthday, if they are enrolled on, or accepted for, approved training or a course of education [...]".
  • SPC Regs 2002 reg. 4A(1)(b): "up to, but not including, the 1st September following the person's 19th birthday, if the person is enrolled on, or accepted for, approved training or a course of education [...]".
  • UC reg. 5(5): "A person who is receiving universal credit, an employment and support allowance or a jobseeker's allowance is not a qualifying young person." SPC reg. 4A(5) lists those and "income support" too.

Change

  • New date fact is_before_first_september_following_19th_birthday (Person, year): true when the fiscal year starts (6 April) before the 1 September following the 19th birthday. It reads date_of_birth, or age and months_since_last_birthday, through utils.dates.birth_day, and mirrors Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's 16th-birthday fact.
  • Universal Credit. is_before_universal_credit_qualifying_young_person_terminal_date was an input with default_value = False. It now has a formula that returns the date fact. A dataset or situation can still set it.
  • Defaults. The date fact, the Universal Credit variable and the Pension Credit condition all default to true (see "Setting the date for one person" below).
  • Pension Credit. New meets_qualifying_young_person_terminal_date_condition_for_pension_credit (under 19, or the date fact), which the education route in is_qualifying_young_person_for_pension_credit requires, with a new parameter terminal_date_age_limit (19).
  • Own awards, from Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279. Both programmes' formulas exclude a young person whose own-right flag or own reported award shows Universal Credit, ESA or JSA, on either route. Pension Credit's formula also reads is_on_income_support, for reg. 4A(5)'s Income Support. That exclusion applies before and after the date, so the terminal date never brings back a young person on their own award.

The annual approximation

The period ends on a fixed 1 September that an annual period cannot split. As in #2279, a year counts if any of it falls in the period, so a year counts when it starts before that 1 September.

  • Every 19-year-old (age on 6 October) counts. Their 19th birthday falls between 7 October of the year before and 6 October, so the following 1 September falls in that year or the next, always after 6 April.
  • A 20-year-old born from 1 September to 6 October stays before the date until 31 August, but at 20 on 6 October, when the model reads age, they already fail the QYP age limit. The fact is true for them and changes nothing (YAML Case 7).
  • No one newly loses the status. The fact is false only for people aged 20 or over on 6 October.

So the default matches what the microdata and Pension Credit did already. Set either variable to false for a 19-year-old whose 1 September has passed.

Setting the date for one person

An input set for some people fills the others with the variable's default and skips its formula for them. With a false default, setting the date fact for one person gave everyone else in the situation false, and a second 19-year-old student silently lost both statuses.

The fact's formula gives true for everyone under 20 on 6 October, and only they can pass the QYP age limit. So each of the three variables now defaults to true. Setting one for some people leaves everyone else under 20 where the formula puts them. For someone older the default is true where the formula says false, but every reader of these variables applies the QYP age limit first, and they fail it. A property test checks that the formula is true for every age 0 to 19 and every month since the last birthday, in three years, and that each variable defaults to true. It also checks a household of two 19-year-old students with each variable set false for the first only: the second stays a QYP for both programmes. YAML Case 12 pins the same household.

Setting both layers. A situation can cross the two: the date fact set false for one student and a programme variable (Universal Credit's terminal date, or Pension Credit's condition) set false for another. The programme variable then gives the first student its default, true, not their date fact, so the first becomes eligible again for that programme. No default can keep a formula for people left unset, so both programme variables now say so: when setting both, set the programme variable for every person whose status matters. A crossed test shows the hazard and the documented use for each programme, and invariant 5 covers one variable at a time.

Two YAML cases changed with it. Case 3's parent, not set, now shows the default (true) for the Universal Credit variable; nothing reads it for a 45-year-old. And child_qualifying_young_person.yaml's "aged 19 requires being before terminal date" case left its after-the-date student unset and relied on the old false default; it now sets that input to false explicitly. #2279 documents the same partial-input behaviour for its 16th-birthday fact, whose formula is not true for everyone it could default for.

Invariants

  1. The date fact is true exactly when the year starts before the 1 September following the 19th birthday, for every date of birth, against calendar arithmetic that shares no code with the model.
  2. Universal Credit's terminal-date variable follows the fact unless set, and setting it changes Universal Credit only.
  3. Over every combination of age, education, training, apprenticeship, own-right Universal Credit, ESA or JSA, entry age and the two date facts, for people not on Income Support, Universal Credit and Pension Credit agree, and both equal the regulation's conditions. They part only for a young person on their own Income Support. SPC reg. 4A(5) lists Income Support and UC reg. 5(5) doesn't, so that young person, where they otherwise qualify, keeps the Universal Credit status and loses the Pension Credit one, on either route.
  4. With the 19th-birthday fact on, both statuses equal the earlier definition without a terminal date; switching it on never removes the status; it matters only for a 19-year-old in education or training.
  5. The formula is true for everyone under 20 on 6 October, and each terminal-date variable defaults to true, so setting one of them for some people leaves everyone else's status as it would be with nothing set. That holds one variable at a time; a situation that also sets the date fact must set the programme variable for everyone whose status matters (the crossed test).

Tests

  • Properties, new test_qualifying_young_person_terminal_date_properties.py:
    • invariants 1 and 2 for every date of birth from 2004 to 2009 (2,192 people, ages 16 to 22 on 6 October 2026), with the 31 August and 1 September edges asserted, and again on Hypothesis-drawn dates of birth and years;
    • invariants 3 and 4 over 6,144 combinations in 2026-27, when the model pays no Income Support; the test asserts that nobody in the grid receives it;
    • invariant 3's Income Support difference over 128 households in 2025-26 (ages 16 to 19, in or outside education, entry at 17 or 19, both date facts, with and without the young person's own reported Income Support), against the same oracle;
    • invariant 2 for each programme's input set against the fact;
    • invariant 5: every age 0 to 25 and month since the last birthday, in 2022, 2026 and 2030, and the mixed household.
  • YAML, new qualifying_young_person_terminal_date.yaml, fifteen cases hand-computed (2026-27, and 2025-26 for Case 15, when the model still pays Income Support):
Case Facts Expected
1 lone parent 45, 19-year-old at school UC and PC QYP; child element £351.88 × 12 = £4,222.56; work allowance
2 as 1, UC input false UC not a QYP, element £0; PC still a QYP
3 as 1, date fact false neither
4 pensioner 70, 19-year-old at school PC first-child addition £81.07 × 52 = £4,215.64
5 as 4, date fact false addition £0
6 born 31 August 2007 the period ends on the 19th birthday, in the year: QYP
7 born 15 September 2006, aged 20 on 6 October fact true; not a QYP (age)
8 born 31 August 2006 the date fell in 2025: fact false
9 19, course begun at 19 not a QYP (reg. 5(2); reg. 4A(2))
10 19, outside education not a QYP
11 born 1 September 2006: the 1 September following the 19th birthday falls in 2026 fact true, but aged 20 on 6 October: not a QYP
12 pensioner 70, two 19-year-olds at school, the fact set false for the first only the second takes the default (true): a QYP for both programmes; the first for neither
13 as 1, the 19-year-old reports their own Universal Credit fact and UC terminal date true; a QYP for neither (reg. 5(5); reg. 4A(5)); element £0; no work allowance
14 as 4, the 19-year-old reports their own income-related ESA PC terminal condition true; a QYP for neither; addition £0
15 as 4, in 2025-26, the 19-year-old reports their own Income Support a UC QYP but not a PC one (reg. 4A(5); reg. 5(5) doesn't list it); addition £0

Mutation checks, each applied alone in a copy of the branch (YAML cases and property tests that fail):

Mutation YAML cases Property tests
The year compared with > and not >= 1, 2, 4, 6, 7, 10, 11 both calendar tests
A 1 September birthday read as its own "1 September following" 11 the every-date-of-birth test
The 18th birthday in place of the 19th 1, 2, 4, 6, 7, 10, 11 both calendar tests
Pension Credit's terminal condition dropped 3, 5 the agreement grid; the input-override test
The Universal Credit variable back to a false default 1, 6 both calendar tests; the agreement grid

Case 11 pins the 1 September mutation in YAML as well as in the property test.

Mutations at b5cbd2e: each default put back to false fails the two new property tests; the date fact's also fails YAML Cases 3 and 12.

Mutations at d5b8d12, each one change in a copy, run against qualifying_young_person_terminal_date.yaml and test_qualifying_young_person_terminal_date_properties.py:

Mutation YAML cases that fail Property tests that fail Result
Pension Credit's terminal condition dropped from its formula 3, 5, 12 the agreement grid, the input-override test, the one-person test, the crossed test, the Income Support test killed
Universal Credit's terminal condition dropped from its formula 2, 3, 12 the agreement grid, the input-override test, the one-person test, the crossed test, the Income Support test killed
is_on_income_support dropped from Pension Credit's reg. 4A(5) exclusion none of the 14; Case 15, added at ee0482e, fails the Income Support test killed

Merges into this head. 596bf4f merges main 95dfc62, and 25cb007 merges main 47a2817, each as its own commit, without conflicts.

Run locally at 25cb007:

  • policyengine-core test on 53 YAML files (every test YAML file the branch changes against main 47a2817, and every baseline file that names qualifying_young_person, income_support_reported or receives_income_support_in_own_right), 897 passed. Of those, qualifying_young_person_terminal_date.yaml 15 passed and child_qualifying_young_person.yaml 8.
  • pytest -n 4, one file at a time: test_qualifying_young_person_terminal_date_properties.py 8 passed; test_child_and_adult_definitions_properties.py 21; test_benefit_cap_specified_benefits_properties.py 46; test_income_support_eligibility_properties.py 4; test_legacy_award_readers_properties.py 20; test_means_test_income_properties.py 2; test_pension_credit_young_person_income_support.py 10; policyengine_uk/tests/code_health 3,165. No skip, xfail or failure.
  • .github/check_fix_pr.py on this title and body: PASS, 4 changed executable tests.
  • uvx ruff@0.17.2 format --check ., check . and check --select F811,F821 policyengine_uk: clean. git diff --check against main 47a2817: clean. git merge-tree --write-tree with main 47a2817: no conflict.

Local runs at 93d59f0 (this branch with #2279's head c5b9850 and main f4a3170 merged):

  • policyengine-core test on the six test YAML files the branch changes against main f4a3170 (Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's included): qualifying_young_person_terminal_date.yaml 14 passed; child_qualifying_young_person.yaml 8; qualifying_young_person_reported_own_awards.yaml 23; qualifying_young_person_no_education_route.yaml 14; is_uc_work_allowance_eligible.yaml 13; means_test_income_members.yaml 7. 79 passed, none failed.
  • pytest -n 4: test_qualifying_young_person_terminal_date_properties.py 8 passed; test_child_and_adult_definitions_properties.py 21; test_means_test_income_properties.py 2; test_legacy_award_readers_properties.py 20; policyengine_uk/tests/code_health 3,093.

Local runs at b5cbd2e:

The full suites run in CI.

Microsimulation impact

Aggregate impact report (CI)

In the report, CI runs main against this PR's head merged into it, on the certified Enhanced FRS 2024-25 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), one full run per side for each year 2025-26 to 2031-32. With 25cb007 merged into main 040e9a5 (bbac69f), the report passes: no check breaks because of this change, one check fails on both sides ("VAT excluding private school fees is never negative"), and the report flags no moves. No household's net income changes in any year, and no receipt, government balance, poverty, inequality or standard reform figure moves.

Local runs

These cover b5cbd2e only. We ran Enhanced FRS 2024-25 (uk-data 1.58.0, sha256 5ba399ea5ab9a6c7) once for each year 2025-26 to 2030-31 and compared every record with no tolerance. Base: main e01f2f5 with #2279 (9d2fadb) merged (local merge 006c0e813). Branch: b5cbd2e.

No record changes in any year, in any of the Universal Credit, Pension Credit, Housing Benefit, benefit cap, net income or poverty outputs. policyengine-uk-data supplies is_before_universal_credit_qualifying_young_person_terminal_date, true for the 102 records aged 19 in qualifying education in 2026-27, and a supplied input takes precedence over the new formula (on the earlier base). The new date fact is true for all 780 records aged 19 that year, so no one fails the Pension Credit condition either. The PR changes household calculations (the table at the top) and any dataset that does not supply the input.

🤖 Generated with Claude Code

MaxGhenis and others added 5 commits October 10, 2026 20:09
UC Regs 2013 reg. 5(1)(b) and SPC Regs 2002 reg. 4A(1)(b) keep a young
person in education or training a qualifying young person up to, but not
including, the 1 September following their 19th birthday.

- Universal Credit read the date from an input that defaulted to false, so
  a 19-year-old at school was not a qualifying young person in a household
  calculation, while the microdata sets the input true for every
  19-year-old in education.
- Pension Credit did not model the date at all.

Add is_before_first_september_following_19th_birthday, which counts a year
that starts before the date, as the 16th-birthday fact does.
is_before_universal_credit_qualifying_young_person_terminal_date now
follows it unless set, and Pension Credit's new terminal date condition
reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p-terminal-date-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation that read a 19th birthday on 1 September as its own
"1 September following" was caught by the calendar property test only.
Case 11 pins the edge in YAML too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main with #2285 and #2288 merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 12, 2026 08:48
…r 20

Review round 1 of #2300: setting the 19th-birthday fact for one person
gave everyone else in the situation false (the default), so another
19-year-old student silently lost the Pension Credit and Universal Credit
status. The formula gives true for everyone under 20 on 6 October, which
is everyone the age limit lets be a qualifying young person, so true is
now the default for the fact and for the two terminal-date variables that
read it. A partial setting then leaves everyone else where the formula
puts them. Tested from age alone for ages 0 to 25 and all months, and in a
mixed household (property test and YAML Case 12).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Case 3's parent, not set, now takes the default (true): harmless, since
every reader is behind the qualifying young person age limit. The older
child_qualifying_young_person.yaml case left its after-terminal-date
person unset and relied on the old false default; it now sets the input
explicitly. The docstrings now say the default is the formula's value
under 20 and that older people are excluded before it is read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #2279's head 9d2fadb and main e01f2f5 with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main e01f2f5.

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

Copy link
Copy Markdown
Collaborator Author

UK hub takeover for the Budget fix sprint (Max, 21 Oct). The owner session local_1b6fef51 has been idle since mid-October. The hub woke it on 21 Oct at 23:58Z, and it did not start. To land this before the Budget model locks (the first pe-uk release cut by about 12:00Z Thu 22 Oct), the hub has taken it over as build 20261021-211254-sprint-2300-2302, which merges main, re-tests, pushes as a fast-forward and marks it ready. The hub then reviews it and lands it through the merge queue.

If the owner session wakes, please don't push to this branch; comment here instead, so there's only one writer.

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

UK hub: takeover withdrawn. The owner session has been woken (22 Oct 01:25Z) and is finishing this PR itself, so the hub's build is cancelled. One writer: the owner session.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 21, 2026 21:31
Review round 2 of #2300: a programme-level terminal-date variable set for
one person gives everyone else its default (true), not their date fact, so
a student whose date fact was set false became eligible again. No default
can keep the formula for people left unset, so the two programme variables
now say so and tell users setting both to set the programme variable for
everyone whose status matters. Invariant 5 is qualified, and a crossed test
shows both the hazard and the documented use, for each programme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 22, 2026
Brings in #2300's round 2 changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 040e9a5f07 against head bbac69ff83, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

None.

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Household net income by decile

No household's net income changes in any year.

No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles; standard reform costings (change in gov_balance); standard reforms: change in each reform's target.

Checks failing on both base and head (1)
  • VAT excluding private school fees is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-53,888,684 in total; 2026-27: 76 units below zero, a weighted £-55,777,260 in total; 2027-28: 76 units below zero, a weighted £-57,727,917 in total
Counted-once check

Each of 92 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 040e9a5f07 uk 2.175.4 (files 889511591b1d), core 3.34.1, py 3.13.18 aarch64 b1e13a3ca1b7 3bcfb3473960 2498.39s
head #2300 merged into main bbac69ff83 uk 2.175.4 (files d4ae713c6102), core 3.34.1, py 3.13.18 aarch64 b1e13a3ca1b7 3bcfb3473960 2491.14s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Final charge on cash interest in non-cash ISAs £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Lifetime ISA withdrawal charge £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Income tax £294.46bn £315.23bn £335.82bn £351.28bn £366.96bn £385.36bn £399.99bn
National Insurance (employee and self-employed) £51.70bn £54.14bn £56.13bn £57.73bn £59.76bn £61.64bn £63.51bn
National Insurance (employer) £148.71bn £155.37bn £160.88bn £165.09bn £172.06bn £177.36bn £182.57bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.60bn £13.15bn £13.60bn £14.11bn £14.67bn £15.24bn £15.80bn
Student loan repayments £7.82bn £8.13bn £8.39bn £8.58bn £8.79bn £9.04bn £9.29bn
VAT excluding private school fees £249.37bn £256.87bn £266.33bn £272.70bn £278.97bn £285.24bn £291.59bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £1.95bn £2.01bn £2.09bn £2.14bn £2.20bn £2.26bn £2.31bn
Change in private school VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.71bn £24.13bn £26.00bn £26.83bn £26.51bn £25.78bn £24.63bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.26bn £4.40bn £4.41bn £4.42bn £4.43bn £4.44bn £4.45bn
Council tax £49.21bn £52.16bn £54.89bn £57.95bn £61.16bn £64.47bn £67.91bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.50bn £0.51bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.60bn £0.63bn £0.66bn £0.69bn £0.72bn
Business rates £34.68bn £36.20bn £38.00bn £39.46bn £42.27bn £43.25bn £44.29bn
Stamp duty land tax on the household's own purchases £6.88bn £7.32bn £7.66bn £8.06bn £8.47bn £8.91bn £9.35bn
Stamp duty land tax paid by companies £4.72bn £4.57bn £5.29bn £6.03bn £6.37bn £6.92bn £7.45bn
Land transaction tax (Wales) £0.07bn £0.08bn £0.08bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.33bn £0.35bn £0.36bn £0.38bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
State Pension £121.68bn £125.92bn £126.72bn £129.48bn £132.98bn £136.65bn £140.41bn
Pension Credit £6.21bn £6.46bn £6.68bn £6.82bn £6.89bn £7.07bn £7.22bn
Winter Fuel Payment £1.86bn £1.85bn £1.79bn £1.77bn £1.78bn £1.78bn £1.78bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.48bn £81.56bn £84.02bn £86.07bn £87.22bn £88.76bn £89.38bn
Housing Benefit £8.56bn £8.51bn £8.83bn £9.05bn £9.28bn £9.56bn £9.83bn
Council Tax Reduction £2.07bn £2.20bn £2.32bn £2.45bn £2.59bn £2.73bn £2.87bn
Employment and Support Allowance (income-related) £0.79bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employment and Support Allowance (contribution-based) £5.20bn £5.37bn £5.56bn £5.69bn £5.82bn £5.95bn £6.09bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.31bn £0.31bn £0.32bn £0.33bn £0.34bn
Income Support £0.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £26.95bn £28.03bn £29.05bn £30.09bn £30.75bn £31.51bn £32.21bn
Disability Living Allowance £7.49bn £7.79bn £8.07bn £8.36bn £8.55bn £8.76bn £8.95bn
Attendance Allowance £8.68bn £9.03bn £9.36bn £9.69bn £9.90bn £10.15bn £10.38bn
Carer's Allowance £4.48bn £4.66bn £4.83bn £5.00bn £5.11bn £5.27bn £5.42bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.33bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.04bn £0.05bn £0.05bn £0.05bn £0.05bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.50bn £0.52bn £0.54bn £0.55bn £0.56bn £0.58bn £0.59bn
Armed Forces Compensation Scheme £0.61bn £0.63bn £0.65bn £0.66bn £0.68bn £0.69bn £0.71bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.19bn £16.82bn £17.39bn £18.00bn £18.39bn £18.84bn £19.25bn
Scottish Child Payment £0.49bn £0.52bn £0.53bn £0.55bn £0.56bn £0.57bn £0.56bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.30bn £3.41bn £3.54bn £3.62bn £3.70bn £3.78bn £3.87bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.12bn £1.16bn £1.20bn £1.23bn £1.26bn £1.29bn £1.32bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.71bn £0.73bn £0.75bn £0.76bn £0.78bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.56bn £0.57bn £0.57bn £0.58bn £0.60bn £0.61bn £0.59bn
Extended childcare entitlement £2.99bn £3.50bn £3.59bn £3.71bn £3.78bn £3.86bn £3.93bn
Universal childcare entitlement £2.06bn £2.22bn £2.29bn £2.37bn £2.43bn £2.48bn £2.53bn
Targeted childcare entitlement £0.50bn £0.51bn £0.52bn £0.54bn £0.55bn £0.57bn £0.56bn
Childcare Offer for Wales £0.26bn £0.28bn £0.28bn £0.29bn £0.30bn £0.31bn £0.32bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £203.89bn £204.30bn £204.56bn £204.91bn £205.34bn £205.85bn £206.35bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.05bn £25.10bn £25.14bn £25.18bn £25.23bn £25.30bn £25.36bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Household Energy Payment £0.00bn £2.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus 0k 0k 0k 0k 0k 0k 0k
State Pension 11,264k 11,145k 10,819k 10,700k 10,723k 10,750k 10,776k
Pension Credit 1,489k 1,484k 1,476k 1,454k 1,452k 1,457k 1,466k
Winter Fuel Payment 7,844k 7,783k 7,473k 7,375k 7,391k 7,409k 7,427k
Pension Age Winter Heating Payment (Scotland) 803k 802k 800k 797k 799k 801k 802k
Universal Credit 6,342k 6,418k 6,408k 6,369k 6,363k 6,343k 6,295k
Housing Benefit 1,249k 1,182k 1,181k 1,164k 1,164k 1,166k 1,168k
Council Tax Reduction 3,180k 3,199k 3,205k 3,218k 3,233k 3,244k 3,251k
Employment and Support Allowance (income-related) 71k 0k 0k 0k 0k 0k 0k
Employment and Support Allowance (contribution-based) 719k 720k 721k 722k 724k 726k 728k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 67k 67k 67k 68k 68k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,567k 3,575k 3,579k 3,585k 3,593k 3,602k 3,611k
Disability Living Allowance 1,291k 1,294k 1,296k 1,298k 1,300k 1,304k 1,307k
Attendance Allowance 1,695k 1,698k 1,700k 1,703k 1,707k 1,711k 1,715k
Carer's Allowance 1,034k 1,037k 1,038k 1,040k 1,042k 1,060k 1,062k
Carer Support Payment (Scotland) 71k 71k 71k 72k 72k 72k 72k
Carer's Allowance Supplement (Scotland) 0k 71k 71k 72k 72k 72k 72k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 137k 137k 138k 138k 138k 139k
Armed Forces Compensation Scheme 93k 93k 94k 94k 94k 94k 94k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,123k 8,120k 8,120k 8,130k 8,145k 8,161k 8,178k
Scottish Child Payment 351k 352k 352k 353k 350k 348k 337k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 200k 200k 200k 201k 201k 201k 202k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 333k 333k 334k 334k 335k 336k 337k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 181k 182k 182k 182k 182k 183k 183k
Sure Start Maternity Grant 26k 26k 26k 26k 26k 26k 26k
Bereavement Support Payment 98k 98k 98k 98k 98k 99k 99k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,043k 1,027k 1,014k 1,009k 1,011k 1,009k 993k
Extended childcare entitlement 795k 800k 796k 797k 798k 798k 792k
Universal childcare entitlement 1,071k 1,072k 1,072k 1,073k 1,076k 1,079k 1,074k
Targeted childcare entitlement 165k 156k 154k 154k 155k 155k 150k
Childcare Offer for Wales 39k 39k 39k 39k 39k 39k 39k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 98k 98k 99k 99k 99k 99k
Adult Dependants' Grant 18k 18k 12k 12k 12k 12k 12k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Household Energy Payment 0k 21,065k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.89bn
Government spending £528.93bn † £544.94bn † £549.99bn † £558.68bn † £566.30bn † £575.00bn † £582.59bn †
Government balance, each receipt once £363.07bn † £389.72bn † £430.51bn † £457.24bn † £487.94bn † £516.56bn † £542.31bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £892.00bn £934.66bn £980.50bn £1,015.93bn £1,054.24bn £1,091.56bn £1,124.89bn
household_tax £491.97bn £520.41bn £551.21bn £576.00bn £601.00bn £626.69bn £648.42bn
pre_budget_change_household_tax £484.15bn £512.28bn £542.82bn £566.95bn £591.72bn £617.16bn £638.61bn
gov_spending £528.93bn † £544.94bn † £549.99bn † £558.68bn † £566.30bn † £575.00bn † £582.59bn †
household_benefits £528.93bn † £544.94bn † £549.99bn † £558.68bn † £566.30bn † £575.00bn † £582.59bn †
gov_balance £363.07bn † £389.72bn † £430.51bn † £457.24bn † £487.94bn † £516.56bn † £542.31bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,637.96bn £1,702.77bn £1,757.35bn £1,806.41bn £1,855.97bn £1,912.43bn £1,970.44bn
Household net income £1,636.24bn † £1,687.10bn † £1,714.68bn † £1,746.67bn † £1,777.79bn † £1,816.07bn † £1,858.68bn †
Household net income (HBAI definition) £1,449.13bn £1,499.11bn £1,530.09bn £1,564.35bn £1,596.99bn £1,634.64bn £1,675.60bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,245k 69,383k 69,473k 69,592k 69,738k 69,912k 70,080k
Benefit units 35,110k 35,181k 35,226k 35,286k 35,360k 35,449k 35,534k
Households 31,046k 31,108k 31,149k 31,201k 31,267k 31,345k 31,420k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.3% 15.5% 16.4% 16.3% 16.5% 16.5% 16.6%
Absolute poverty, before housing costs: children 24.7% 22.1% 23.3% 22.9% 23.1% 23.2% 23.5%
Absolute poverty, before housing costs: working-age adults 14.1% 13.8% 14.7% 14.7% 14.8% 14.8% 15.0%
Absolute poverty, before housing costs: pensioners 13.7% 13.4% 14.0% 13.8% 14.0% 14.0% 13.9%
Absolute poverty, after housing costs: everyone 21.6% 21.1% 22.0% 22.6% 22.9% 23.1% 23.5%
Absolute poverty, after housing costs: children 33.3% 31.3% 32.3% 32.5% 32.9% 33.4% 34.2%
Absolute poverty, after housing costs: working-age adults 19.9% 19.8% 20.8% 21.6% 21.9% 22.0% 22.3%
Absolute poverty, after housing costs: pensioners 12.7% 12.4% 13.2% 13.2% 13.3% 13.4% 13.6%
Relative poverty, before housing costs: everyone 17.0% 16.0% 16.4% 16.3% 16.4% 16.3% 16.6%
Relative poverty, before housing costs: children 25.9% 22.9% 23.3% 22.9% 23.0% 22.9% 23.5%
Relative poverty, before housing costs: working-age adults 14.5% 14.1% 14.6% 14.7% 14.7% 14.7% 15.0%
Relative poverty, before housing costs: pensioners 14.5% 14.1% 14.0% 13.8% 13.8% 13.9% 13.9%
Relative poverty, after housing costs: everyone 21.5% 21.0% 21.2% 21.3% 21.3% 21.5% 21.7%
Relative poverty, after housing costs: children 33.1% 31.1% 31.1% 31.0% 31.1% 31.2% 31.8%
Relative poverty, after housing costs: working-age adults 19.9% 19.8% 20.2% 20.4% 20.5% 20.6% 20.8%
Relative poverty, after housing costs: pensioners 12.5% 12.4% 12.2% 11.8% 11.7% 11.8% 12.1%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,277k 10,784k 11,408k 11,373k 11,482k 11,521k 11,657k
Absolute poverty, before housing costs: children 3,688k 3,314k 3,496k 3,445k 3,480k 3,502k 3,549k
Absolute poverty, before housing costs: working-age adults 5,965k 5,895k 6,311k 6,366k 6,423k 6,434k 6,524k
Absolute poverty, before housing costs: pensioners 1,624k 1,575k 1,601k 1,562k 1,579k 1,585k 1,584k
Absolute poverty, after housing costs: everyone 14,944k 14,607k 15,318k 15,729k 15,946k 16,150k 16,440k
Absolute poverty, after housing costs: children 4,975k 4,682k 4,837k 4,888k 4,945k 5,040k 5,179k
Absolute poverty, after housing costs: working-age adults 8,463k 8,465k 8,976k 9,348k 9,496k 9,588k 9,715k
Absolute poverty, after housing costs: pensioners 1,506k 1,461k 1,506k 1,494k 1,506k 1,522k 1,546k
Relative poverty, before housing costs: everyone 11,750k 11,088k 11,394k 11,363k 11,422k 11,423k 11,657k
Relative poverty, before housing costs: children 3,872k 3,423k 3,496k 3,443k 3,466k 3,454k 3,549k
Relative poverty, before housing costs: working-age adults 6,159k 6,014k 6,304k 6,361k 6,392k 6,393k 6,524k
Relative poverty, before housing costs: pensioners 1,719k 1,651k 1,594k 1,560k 1,564k 1,576k 1,584k
Relative poverty, after housing costs: everyone 14,864k 14,560k 14,740k 14,811k 14,883k 15,023k 15,241k
Relative poverty, after housing costs: children 4,944k 4,660k 4,660k 4,662k 4,679k 4,707k 4,805k
Relative poverty, after housing costs: working-age adults 8,431k 8,444k 8,687k 8,823k 8,880k 8,974k 9,067k
Relative poverty, after housing costs: pensioners 1,489k 1,456k 1,393k 1,327k 1,325k 1,342k 1,369k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3078 0.3051 0.3057 0.3056 0.3061 0.3067 0.3083
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,681 † £18,010 † £17,918 † £18,160 † £18,301 † £18,547 † £18,732 †
Decile 2: mean household_net_income per household £27,942 † £29,024 † £28,623 † £29,004 † £29,287 † £29,699 † £30,089 †
Decile 3: mean household_net_income per household £34,316 † £35,426 † £36,075 † £36,523 † £36,995 † £37,682 † £38,154 †
Decile 4: mean household_net_income per household £38,934 † £40,687 † £41,716 † £42,770 † £43,423 † £44,111 † £44,446 †
Decile 5: mean household_net_income per household £43,235 † £45,555 † £46,034 † £46,930 † £47,714 † £48,578 † £50,274 †
Decile 6: mean household_net_income per household £47,931 † £49,240 † £50,932 † £51,984 † £52,963 † £53,976 † £54,714 †
Decile 7: mean household_net_income per household £55,659 † £56,577 † £57,445 † £57,649 † £58,750 † £59,668 † £61,096 †
Decile 8: mean household_net_income per household £68,784 † £70,805 † £71,866 † £73,514 † £73,888 † £75,324 † £77,180 †
Decile 9: mean household_net_income per household £78,889 † £81,012 † £80,859 † £82,645 † £85,463 † £86,945 † £89,508 †
Decile 10: mean household_net_income per household £116,861 † £120,160 † £124,545 † £126,419 † £125,752 † £128,367 † £130,693 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.41bn £6.66bn £6.63bn £6.85bn £7.03bn £7.25bn £7.49bn
Raise higher rate by 1pp £3.61bn £3.87bn £4.00bn £4.22bn £4.44bn £4.71bn £4.83bn
Raise personal allowance by ~800GBP/year £-3.81bn £-3.93bn £-3.99bn £-4.05bn £-4.11bn £-4.24bn £-1.95bn
Raise child benefit by 25GBP/week per additional child £-1.99bn £-1.75bn £-1.55bn £-1.38bn £-1.30bn £-1.18bn £-1.07bn
Reduce Universal Credit taper rate to 20% £-20.77bn £-21.32bn £-21.75bn £-22.00bn £-22.35bn £-22.75bn £-22.94bn
Raise Class 1 main employee NICs rate to 10% £10.89bn £11.30bn £11.71bn £12.01bn £12.31bn £12.62bn £13.00bn
Raise VAT standard rate by 2pp £24.89bn £25.71bn £26.63bn £27.24bn £27.87bn £28.50bn £29.14bn
Raise additional rate by 3pp £2.90bn £3.15bn £3.22bn £3.41bn £3.61bn £3.85bn £4.14bn
Raise the Universal Credit standard allowance by 10% £-3.06bn £-3.22bn £-3.30bn £-3.39bn £-3.48bn £-3.53bn £-3.54bn
Raise the State Pension by 10% £-7.76bn £-7.99bn £-8.22bn £-8.44bn £-8.79bn £-8.52bn £-8.86bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.55bn £6.86bn £6.79bn £7.00bn £7.20bn £7.44bn £7.66bn
Raise higher rate by 1pp: change in income_tax £3.61bn £3.92bn £4.01bn £4.23bn £4.45bn £4.72bn £4.84bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.93bn £-4.07bn £-4.13bn £-4.18bn £-4.22bn £-4.40bn £-1.75bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.33bn £2.14bn £1.94bn £1.76bn £1.65bn £1.52bn £1.40bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.74bn £21.31bn £21.79bn £22.05bn £22.37bn £22.78bn £22.99bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.15bn £11.63bn £12.00bn £12.29bn £12.61bn £12.94bn £13.31bn
Raise VAT standard rate by 2pp: change in vat £24.69bn £25.51bn £26.42bn £27.03bn £27.65bn £28.28bn £28.91bn
Raise additional rate by 3pp: change in income_tax £2.90bn £3.15bn £3.22bn £3.42bn £3.62bn £3.86bn £4.12bn
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.06bn £3.22bn £3.32bn £3.41bn £3.47bn £3.53bn £3.55bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.17bn £11.28bn £11.60bn £12.03bn £12.46bn £12.96bn

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Parked until after the 28 October Budget under the UK hub's Budget triage (04:10Z 22 Oct: the QYP set is not Budget-material). Status at c710718:

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed c710718dccbfb6e0b8024062e503e61639f7729c. Review COMPLETE: no confirmed code blocker; one nonblocking finding remains.

A1 — Refresh the resolved Axiom fixture blocker. The PR and #355 addendum say the nineteen-year-old negative companion cases are waiting because #446 has the same file open. #446 merged on 11 October. Please update the description/addendum to the current fixture handoff and actual blocker, if any. The encoded UC terminal-date provision itself is already correct; this is a stale completion record, not a request for a billed encoder run.

Six native final UC/PC scenarios passed. They confirm the documented requirement to supply complete programme-level vectors when composing terminal-date overrides. The local combined tests timed out after 120 seconds with 63 passing progress markers; the 17 successful checks at this exact head, including the frozen all-node Result audit, separately cover the unfinished repository tests. I have not treated the timeout as a full local pass or independently certified the private population-impact claims.

I am leaving a comment rather than approving: A1 remains, the PR is a draft, and the declared #2279 merge-order/integration condition still needs resolving. The resulting merged head will need its own current checks.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Picking this up for the UK hub. Reply here if you're mid-change and I'll stop.


Generated by Claude Code

Keep #2279's own-right documentation for Pension Credit's young person
and this branch's 1 September end; drop the sentence that said the model
did not apply that end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Jra6ok6eCQvZUMMLg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Jra6ok6eCQvZUMMLg

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Followed up at unchanged head c710718dccbfb6e0b8024062e503e61639f7729c. Review COMPLETE, with no open findings or material evidence gaps.

A1 RESOLVED: the description and actual #355 addendum now correctly state that #446 merged and identify the open test-only rulespec-uk#529 for the three companion cases. This closes the stale handoff finding; it does not imply that #529 has merged.

The prior code and policy review still applies at this head. No repeated model runs were needed. Current-head CI passes; approval remains deferred while this PR is a draft.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Place the UC and Pension Credit QYP terminal date from the birth date

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

Fixes #2291

**Stacked on #2279. Do not merge before it.** This PR builds on #2279's Pension Credit qualifying young person. Until #2279 merges, the diff here also shows its commits. This PR's own commits:

```bash
git log -p --no-merges origin/qyp-terminal-date-19 ^origin/main ^origin/uc-pc-qyp-sixteen-route
```

## Summary

A young person in non-advanced education or approved training is a qualifying young person (QYP) "up to, but not including, the 1st September following their 19th birthday". The model treated a 19-year-old three ways:

| Where | 19-year-old at school, course begun before 19 |
|---|---|
| Universal Credit, household calculation | not a QYP: the terminal-date input defaulted to false |
| Universal Credit, microdata | a QYP: policyengine-uk-data sets that input true for every 19-year-old in qualifying education or training |
| Pension Credit, everywhere | a QYP: no terminal-date condition at all |

This PR places the date from the date of birth for both programmes. Against its base (`main` 4d80b2941 with #2279 merged), 2026-27, a lone parent aged 45 with a 19-year-old at school (the same on `main` 9475c95ab before #2285):

| Variable | Base | This PR |
|---|---:|---:|
| `is_qualifying_young_person_for_universal_credit` (the 19-year-old) | false | true |
| `uc_individual_child_element` | £0 | £4,222.56 (£351.88 × 12) |
| `is_uc_work_allowance_eligible` | false | true |
| `is_qualifying_young_person_for_pension_credit` | true | true |

This is a follow-up from #2279 (its items "Pension Credit reg. 4A(1)(b)'s 1 September" and the Universal Credit default).

## The law

- [UC Regs 2013 reg. 5(1)](https://www.legislation.gov.uk/uksi/2013/376/regulation/5): "A person who has reached the age of 16 but not the age of 20 is a qualifying young person for the purposes of Part 1 of the Act and these Regulations— (a) up to, but not including, the 1st September following their 16th birthday; and (b) up to, but not including, the 1st September following their 19th birthday, if they are enrolled on, or accepted for, approved training or a course of education [...]".
- [SPC Regs 2002 reg. 4A(1)(b)](https://www.legislation.gov.uk/uksi/2002/1792/regulation/4A): "up to, but not including, the 1st September following the person's 19th birthday, if the person is enrolled on, or accepted for, approved training or a course of education [...]".
- UC reg. 5(5): "A person who is receiving universal credit, an employment and support allowance or a jobseeker's allowance is not a qualifying young person." SPC reg. 4A(5) lists those and "income support" too.

## Change

- **New date fact** `is_before_first_september_following_19th_birthday` (Person, year): true when the fiscal year starts (6 April) before the 1 September following the 19th birthday. It reads `date_of_birth`, or `age` and `months_since_last_birthday`, through `utils.dates.birth_day`, and mirrors #2279's 16th-birthday fact.
- **Universal Credit.** `is_before_universal_credit_qualifying_young_person_terminal_date` was an input with `default_value = False`. It now has a formula that returns the date fact. A dataset or situation can still set it.
- **Defaults.** The date fact, the Universal Credit variable and the Pension Credit condition all default to true (see "Setting the date for one person" below).
- **Pension Credit.** New `meets_qualifying_young_person_terminal_date_condition_for_pension_credit` (under 19, or the date fact), required by the education route in `is_qualifying_young_person_for_pension_credit`, with a new parameter `terminal_date_age_limit` (19).
- **Own awards, from #2279.** Both programmes' formulas exclude a young person whose own-right flag or own reported award shows Universal Credit, ESA or JSA, on either route. Pension Credit's formula also reads `is_on_income_support`, for reg. 4A(5)'s Income Support. That exclusion applies before and after the date, so the terminal date never brings back a young person on their own award.

## The annual approximation

The period ends on a fixed 1 September that an annual period cannot split. As in #2279, a year counts if any of it falls in the period, which is the same as the year starting before that 1 September.

- **Every 19-year-old (age on 6 October) counts.** Their 19th birthday falls between 7 October of the year before and 6 October, so the following 1 September falls in that year or the next, always after 6 April.
- **A 20-year-old born from 1 September to 6 October** is before the date until 31 August, but the QYP age limit, read on 6 October, already excludes them. The fact is true for them and changes nothing (YAML Case 7).
- **Nobody is newly excluded.** The fact is false only for people aged 20 or over on 6 October.

So the default matches what the microdata and Pension Credit did already. Set either variable to false for a 19-year-old whose 1 September has passed.

## Setting the date for one person

The review of this PR found that setting the date fact for one person gave everyone else in the situation `false`: an input set for some people fills the others with the variable's default and skips its formula for them. A second 19-year-old student then silently lost both statuses.

The fact's formula gives true for everyone under 20 on 6 October, which is everyone the age limit lets be a QYP. So each of the three variables now defaults to true. Setting one for some people leaves everyone else under 20 where the formula puts them. For someone older the default is true where the formula says false, but every reader of these variables sits behind the QYP age limit, which excludes them first. Property test: the formula is true for every age 0 to 19 and every month since the last birthday, in three years, and each variable's default is true; and in a household of two 19-year-old students with each variable set false for the first only, the second stays a QYP for both programmes. YAML Case 12 pins the same household.

**Setting both layers.** A situation can cross the two: the date fact set false for one student and a programme variable (Universal Credit's terminal date, or Pension Credit's condition) set false for another. The programme variable then gives the first student its default, true, not their date fact, so the first becomes eligible again for that programme. No default can keep a formula for people left unset, so both programme variables now say so: when setting both, set the programme variable for every person whose status matters. A crossed test shows the hazard and the documented use for each programme, and invariant 5 covers one variable at a time.

Two YAML cases changed with it. Case 3's parent, not set, now shows the default (true) for the Universal Credit variable; nothing reads it for a 45-year-old. And `child_qualifying_young_person.yaml`'s "aged 19 requires being before terminal date" case left its after-the-date student unset and relied on the old false default; it now sets that input to false explicitly. #2279 documents the same partial-input behaviour for its 16th-birthday fact, whose formula is not true for everyone it could default for.

## Invariants

1. The date fact is true exactly when the year starts before the 1 September following the 19th birthday, for every date of birth, against calendar arithmetic that shares no code with the model.
2. Universal Credit's terminal-date variable follows the fact unless set, and setting it changes Universal Credit only.
3. Over every combination of age, education, training, apprenticeship, own-right Universal Credit, ESA or JSA, entry age and the two date facts, for people not on Income Support, Universal Credit and Pension Credit agree, and both equal the regulation's conditions. Income Support makes the one difference: SPC reg. 4A(5) lists it and UC reg. 5(5) does not, so a young person on their own Income Support keeps the Universal Credit status and loses the Pension Credit one, on either route and on either side of the date.
4. With the 19th-birthday fact on, both statuses are the earlier definition without a terminal date; switching it on never removes the status; it matters only for a 19-year-old in education or training.
5. The formula is true for everyone under 20 on 6 October, and true is each terminal-date variable's default, so setting one of them for some people leaves everyone else's status as it would be with nothing set. That holds one variable at a time; a situation that also sets the date fact must set the programme variable for everyone whose status matters (the crossed test).

## Tests

- **Properties**, new `test_qualifying_young_person_terminal_date_properties.py`:
  - invariants 1 and 2 for every date of birth from 2004 to 2009 (2,192 people, ages 16 to 22 on 6 October 2026), with the 31 August and 1 September edges asserted, and again on Hypothesis-drawn dates of birth and years;
  - invariants 3 and 4 over 6,144 combinations in 2026-27, when Income Support no longer pays; the test asserts nobody in the grid is on it;
  - invariant 3's Income Support difference over 128 households in 2025-26 (ages 16 to 19, in or outside education, entry at 17 or 19, both date facts, with and without the young person's own reported Income Support), against the same oracle;
  - invariant 2 for each programme's input set against the fact;
  - invariant 5: every age 0 to 25 and month since the last birthday, in 2022, 2026 and 2030, and the mixed household.
- **YAML**, new `qualifying_young_person_terminal_date.yaml`, fourteen cases hand-computed (2026-27):

| Case | Facts | Expected |
|---|---|---|
| 1 | lone parent 45, 19-year-old at school | UC and PC QYP; child element £351.88 × 12 = **£4,222.56**; work allowance |
| 2 | as 1, UC input false | UC not a QYP, element £0; PC still a QYP |
| 3 | as 1, date fact false | neither |
| 4 | pensioner 70, 19-year-old at school | PC first-child addition £81.07 × 52 = **£4,215.64** |
| 5 | as 4, date fact false | addition £0 |
| 6 | born 31 August 2007 | the period ends on the 19th birthday, in the year: QYP |
| 7 | born 15 September 2006, aged 20 on 6 October | fact true; not a QYP (age) |
| 8 | born 31 August 2006 | the date was in 2025: fact false |
| 9 | 19, course begun at 19 | not a QYP (reg. 5(2); reg. 4A(2)) |
| 10 | 19, outside education | not a QYP |
| 11 | born 1 September 2006: the 1 September following the 19th birthday is in 2026 | fact true, but aged 20 on 6 October: not a QYP |
| 12 | pensioner 70, two 19-year-olds at school, the fact set false for the first only | the second takes the default (true): a QYP for both programmes; the first for neither |
| 13 | as 1, the 19-year-old reports their own Universal Credit | fact and UC terminal date true; a QYP for neither (reg. 5(5); reg. 4A(5)); element £0; no work allowance |
| 14 | as 4, the 19-year-old reports their own income-related ESA | PC terminal condition true; a QYP for neither; addition £0 |

**Mutation checks**, each applied alone in a copy of the branch (YAML cases and property tests that fail):

| Mutation | YAML cases | Property tests |
|---|---|---|
| The year compared with `>` and not `>=` | 1, 2, 4, 6, 7, 10, 11 | both calendar tests |
| A 1 September birthday read as its own "1 September following" | 11 | the every-date-of-birth test |
| The 18th birthday in place of the 19th | 1, 2, 4, 6, 7, 10, 11 | both calendar tests |
| Pension Credit's terminal condition dropped | 3, 5 | the agreement grid; the input-override test |
| The Universal Credit variable back to a false default | 1, 6 | both calendar tests; the agreement grid |

**Round 2 mutations** at b5cbd2e36 (`mutation/prD_r2.txt`): each default put back to false fails the two new property tests; the date fact's also fails YAML Cases 3 and 12.

**Round 3 mutations** at 93d59f052, each one change in a copy, against `qualifying_young_person_terminal_date.yaml` and `test_qualifying_young_person_terminal_date_properties.py`:

| Mutation | YAML cases that fail | Property tests that fail | Result |
|---|---|---|---|
| Pension Credit's terminal condition dropped from its formula | 3, 5, 12 | the agreement grid, the input-override test, the one-person test, the crossed test, the Income Support test | killed |
| Universal Credit's terminal condition dropped from its formula | 2, 3, 12 | the agreement grid, the input-override test, the one-person test, the crossed test, the Income Support test | killed |
| `is_on_income_support` dropped from Pension Credit's reg. 4A(5) exclusion | none (all 14 pass) | the Income Support test | killed |

Case 11 pins the 1 September mutation in YAML as well as in the property test.

**Run locally at 93d59f052** (this branch with #2279's head c5b9850a3 and `main` f4a317019 merged):

- `policyengine-core test` on the six test YAML files the branch changes against `main` f4a317019 (#2279's included): `qualifying_young_person_terminal_date.yaml` 14 passed; `child_qualifying_young_person.yaml` 8; `qualifying_young_person_reported_own_awards.yaml` 23; `qualifying_young_person_no_education_route.yaml` 14; `is_uc_work_allowance_eligible.yaml` 13; `means_test_income_members.yaml` 7. 79 passed, none failed.
- `pytest -n 4`: `test_qualifying_young_person_terminal_date_properties.py` 8 passed; `test_child_and_adult_definitions_properties.py` 21; `test_means_test_income_properties.py` 2; `test_legacy_award_readers_properties.py` 20; `policyengine_uk/tests/code_health` 3,093.
- `.github/check_fix_pr.py` on this title and body: PASS, 12 changed executable tests.
- `ruff@0.17.2 format --check .`, `check .` and `check --select F811,F821 policyengine_uk`: clean. `git diff --check` against `main` f4a317019: clean.
- `git merge-tree --write-tree` of this head with `main` f4a317019 and with #2279's head c5b9850a3: clean (the tree comes back unchanged).

**Run locally** at b5cbd2e36:
- `policyengine-core test` on the 5 YAML files the branch changes against `main` (#2279's included): 49 passed. At 6f6e7debd, the five YAML files that set these variables: 53 passed.
- `pytest` on the 4 Python test files it changes against `main`: 34 passed.
- The whole stack at 62e93179a (#2387, #2279 and the Child Benefit, CTC, terminal-date and Income Support follow-ups): `policyengine-core test` on the 248 YAML files that mention these programmes, 2,685 passed; `pytest` on the 135 Python test files that mention them, 1,930 passed, 1 xfailed and 6 failed. Four were the benefit cap property oracle (fixed on #2299, 8a5980c99) and two a test generator on #2279 (fixed there, 560005cfa); both files pass at #2302's head 98a473909 (64 passed).
- `ruff format --check` and `ruff check` on `policyengine_uk`: clean.

## Microsimulation impact

### Aggregate impact report (CI)

[The report](https://github.com/PolicyEngine/policyengine-uk/pull/2300#issuecomment-6331537021) runs `main` a8b2848c1 against this PR merged into it (bfb108de1) on the certified Enhanced FRS 2024-25 (policyengine-uk-data 1.58.1), one full run per side for each year 2025-26 to 2031-32. Result: **pass**, 0 checks broken, no moves flagged. **No household's net income changes in any year**, and no receipt, spending, poverty or inequality figure moves. The one moved figure, the standard reform costing "Reduce Universal Credit taper rate to 20%" (−£1.6m to −£2.2m a year), is #2279's: #2279's own report is identical to this one, line for line, so this PR's own change moves nothing. That report predates #2279's own-award and Income Support exclusions and `main` f4a317019; it does not cover 93d59f052.

### Local runs

Real runs on Enhanced FRS 2024-25 (uk-data 1.58.0, sha256 `5ba399ea5ab9a6c7`), one run each for 2025-26 to 2030-31, exact comparison of every record. Base: `main` e01f2f5ec with #2279 (9d2fadbf5) merged (local merge 006c0e813). Branch: b5cbd2e36.

**No record changes in any year**, in any of the Universal Credit, Pension Credit, Housing Benefit, benefit cap, net income or poverty outputs. policyengine-uk-data supplies `is_before_universal_credit_qualifying_young_person_terminal_date`, true for the 102 records aged 19 in qualifying education in 2026-27, and a supplied input takes precedence over the new formula (on the earlier base). The new date fact is true for all 780 records aged 19 that year, so the Pension Credit condition removes no one either. The PR changes household calculations (the table at the top) and any dataset that does not supply the input. Nobody has rerun these at 93d59f052.

Reproducer: policyengine_uk/tests/test_qualifying_young_person_terminal_date_properties.py::test_universal_credit_and_pension_credit_agree_on_every_combination

axiom: `uk:regulations/uksi/2013/376/5#continued_education_or_training_qualifying_condition` encodes UC reg 5(1)(b), but no companion case on `main` sets the 19th-birthday input false; TheAxiomFoundation/rulespec-uk#529 adds the cases the [addendum on #355](https://github.com/TheAxiomFoundation/rulespec-uk/issues/355#issuecomment-6103743732) asks for (test-only). SPC reg 4A(1)(b) TheAxiomFoundation/rulespec-uk#360 queued ([addendum](https://github.com/TheAxiomFoundation/rulespec-uk/issues/360#issuecomment-6103743869); rulespec-uk's corpus pin holds reg 4A's row, and a billed encoder run needs approval)

🤖 Generated with [Claude Code](https://claude.com/claude-code)


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head 93d59f052ae6d5c2a08a5333332544fe78f83992 on claude/merge-2279-2300-qyp-s9hiun, which starts from qyp-terminal-date-19 at c710718. I didn't push to qyp-terminal-date-19 or to #2279's branch, and I opened no PR. The title and body for this PR are in the two comments above.

Commits

  1. 4f7af381 merges Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's head c5b9850. It had one conflict, in the documentation of is_qualifying_young_person_for_pension_credit. The merged text keeps Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's account of who counts as receiving benefits (the own-right flag or their own reported award for UC, ESA and JSA; is_on_income_support for Income Support) and this branch's end at the 1 September following the 19th birthday. It drops "The model does not apply reg. 4A(1)(b)'s end…" (is_qualifying_young_person_for_pension_credit.py:8-18). The auto-merged formula reads both the terminal-date condition (line 38) and is_on_income_support (line 54).
  2. d3a45792 merges main f4a3170. It had no conflicts, unlike the two the brief expected. Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's head had already merged main up to daec84d and settled both docstrings there. I checked the outcome against what the brief asked for:
    • test_child_and_adult_definitions_properties.py keeps the qualifying young person route as invariant 5 and main's supplied-roles invariant as 6. Each invariant has its own number. No test docstring cites an invariant by number except the scope paragraph's "(invariant 5)", which is correct.
    • test_means_test_income_properties.py keeps main's two invariants, and invariant 1 carries this branch's "a 16-year-old outside education and past the 1 September following their 16th birthday".
    • Both are merge commits, so git show --remerge-diff works on each.
  3. d5b8d12d makes the test changes described below.
  4. 93d59f05 removes the leading list marker from changelog.d/qyp-terminal-date-19.fixed.md. check_changelog_fragments.py reports OK.

Findings

  • UC and Pension Credit agreement. No property failed after the merges. The agreement grid runs in 2026-27, after gov.dwp.income_support.active turns false, so nobody in it is on Income Support. The claim of agreement "on every combination" therefore relied on the grid's year. I did three things:

    • Invariant 3 now names the exception: own Income Support keeps the UC status and ends the PC one (test_qualifying_young_person_terminal_date_properties.py:15-22).
    • The grid test asserts that nobody in it is on Income Support (line 213).
    • A new test, test_own_income_support_ends_pension_credit_status_only (line 234), checks 128 households in 2025-26 against the same oracle: UC equals the oracle, and PC equals the oracle with own Income Support removed. A non-vacuity check requires a 19-year-old before the date who loses only the PC status.

    No other assertion changed. The stale scope note in test_child_and_adult_definitions_properties.py:66-72 said "PC's September terminal date is absent". It now says what that file sets and that nobody there reports Income Support.

  • Own reported awards. None of Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's cases pins this at age 19: qualifying_young_person_reported_own_awards.yaml uses ages 16 to 18. I added two hand-worked cases to qualifying_young_person_terminal_date.yaml:

    • Case 13 (line 298): a lone parent aged 45 with a 19-year-old at school who reports their own Universal Credit. The date fact and the UC terminal date are true, but the young person is a QYP for neither programme, the element is £0 and there is no work allowance.
    • Case 14 (line 325): a pensioner aged 70 with a 19-year-old at school who reports their own income-related ESA. The PC terminal condition is true for the student, but the young person is a QYP for neither programme and the addition is 0.

    My first hand value for the pensioner's meets_..._terminal_date_condition_for_pension_credit was true. The formula gives false at 70: the person is not under 19 and the date fact is false at 20 or over. I corrected the case to [false, true].

  • Review A1 (stale Axiom fixture blocker). The live body names neither Remove u in u"string"s in variables #446 nor a wait on it; its axiom: line points to rulespec-uk#529. I kept the axiom: line as the brief asks. The #355 addendum sits in TheAxiomFoundation/rulespec-uk, outside this session's GitHub scope, so I didn't change it. That part of A1 remains.

Mutations

Each mutant is one change in a scratch worktree at d5b8d12, run against qualifying_young_person_terminal_date.yaml and test_qualifying_young_person_terminal_date_properties.py:

Mutation YAML failures Property failures Result
PC terminal condition dropped from PC's formula Cases 3, 5, 12 5 of 8 (agreement grid, input override, one-person, crossed, Income Support) killed
UC terminal condition dropped from UC's formula Cases 2, 3, 12 5 of 8 (same five) killed
is_on_income_support dropped from PC's exclusion none (14 passed) 1 of 8 (test_own_income_support_ends_pension_credit_status_only) killed only by the new test

Tests at 93d59f0

  • YAML (policyengine-core test), the six test YAML files the branch changes against main: qualifying_young_person_terminal_date.yaml 14, child_qualifying_young_person.yaml 8, qualifying_young_person_reported_own_awards.yaml 23, qualifying_young_person_no_education_route.yaml 14, is_uc_work_allowance_eligible.yaml 13, means_test_income_members.yaml 7. 79 passed, 0 failed.
  • pytest -n 4: test_qualifying_young_person_terminal_date_properties.py 8 passed; test_child_and_adult_definitions_properties.py 21; test_means_test_income_properties.py 2; test_legacy_award_readers_properties.py 20; policyengine_uk/tests/code_health 3,093 passed.
    • A combined run of the four property files ended with exit 137 when the container restarted, so I ran each file on its own, still with -n 4.
  • .github/check_fix_pr.py, run as pr_fix_evidence.yaml runs it, on the final title and body: PASS, 12 changed executable tests. The old title also passes.
  • uvx ruff@0.17.2 format --check . (2,177 files formatted), check . and check --select F811,F821 policyengine_uk: all clean.
  • git diff --check c710718dc..HEAD reports a new blank line at the end of three YAML files: couple_carer_award.yaml, housing_benefit_family_premium.yaml and severe_disability_premium_joint_occupiers.yaml. All three came in through the merges, not from this branch's commits. git diff --check f4a317019..HEAD is clean.
  • git merge-tree --write-tree --name-only HEAD f4a317019 and HEAD c5b9850a3: both clean (tree 13bf85b63, no conflicts). They are also clean against the current origin/main, which is still f4a3170.

Skipped

  • I ran no survey-data runs, because there is no private data access. The body says the CI aggregate report and the local microsimulation runs predate this head.
  • I didn't run the full suite; CI covers it.
  • I left shard_durations.toml unchanged: this branch renames and removes no tests, and adds one.

Judgment calls

  • Title. I shortened it to 69 characters ("Place the UC and Pension Credit QYP terminal date from the birth date"), following the rule to keep titles under 70. The old title, at 107 characters, also passes Fix evidence. Revert if the hub prefers it.
  • Changelog. I kept the fragment's slug name qyp-terminal-date-19.fixed.md and only removed the list marker.
  • Income Support. I stated it as an exception in invariant 3 and pinned it in a 2025-26 test, and left the 2026-27 grid unchanged rather than adding Income Support to it.
  • Body. I kept the older runs and mutations labelled by their commits, added the round 3 mutants and the runs at 93d59f0, and updated invariant 3, the Change list, the law section (reg. 5(5) and 4A(5)) and the YAML table. The aggregate-impact paragraph now says the report doesn't cover this head, in place of saying it carries over.

Generated by Claude Code

@MaxGhenis MaxGhenis changed the title Place the Universal Credit and Pension Credit qualifying young person terminal date from the date of birth Place the UC and Pension Credit QYP terminal date from the birth date Nov 3, 2026
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Verdict: APPROVE WITH NITS at 93d59f052ae6d5c2a08a5333332544fe78f83992

The delta (merge 4f7af3818 of #2279's head c5b9850a3, merge d3a457922 of main f4a317019, d5b8d12d8, 93d59f052) introduces no code defect. Both programmes' formulas keep #2279's own-award exclusions and this PR's terminal date. Every test I reran passes at the head and at its merge with today's main 52b30809f, CI is green at the head, and each mutant I ran is killed. The findings are about the PR body and the test text.

Findings

P2

P2-1. The "Aggregate impact report (CI)" section names the wrong commits and says the report doesn't cover this head; it does now. The linked comment (updated 2026-11-03T04:04:10Z, when the Aggregate impact job 125696886564 finished with success) now reads "Base 52b30809ff against head 78ab1def06". gh api repos/PolicyEngine/policyengine-uk/commits/78ab1def06 gives parents=52b30809ff,93d59f052a, so the report covers this head merged into today's main. Before that run the comment named 582b86315c and 7b39e615d2 (which is c710718dc merged into 582b86315c), not the body's a8b2848c1 and bfb108de1. The 2028-29 taper figure is now −£1.8m, not −£1.7m. #2279's own report (comment 6331550239, base 648b112443, head 960b028ea9 = c5b9850a35 merged into it) has the same taper row in every year. It has no NICs row, because that reform costs 0 or is suppressed on both sides, so "line for line" no longer holds exactly.

Replace the section's paragraph with:

In the report, CI runs main 52b3080 against this PR's head 93d59f0 merged into it (78ab1de) on the certified Enhanced FRS 2024-25 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), one full run per side for each year 2025-26 to 2031-32. The report passes. No check breaks because of this change, one check fails on both sides (VAT excluding private school fees is never negative), and the report flags no moves. No household's net income changes in any year, and no receipt, government balance, poverty or inequality figure moves. Only one figure moves: the standard reform costing "Reduce Universal Credit taper rate to 20%" (−£1.6m, −£1.6m, −£1.7m, −£1.8m, −£2.1m, −£2.1m and −£2.2m from 2025-26 to 2031-32). #2279's own report at its head c5b9850 (against main 648b112) shows the same figures in every year, so this PR's own change moves nothing.

P3

P3-1. Invariant 3 says the Income Support split holds "on either side of the date", but after the date nobody has a status to keep. policyengine_uk/tests/test_qualifying_young_person_terminal_date_properties.py:19-23, and invariant 3 in the body. Once the 1 September following the 19th birthday has passed, a 19-year-old meets neither reg. 5(1)(a)/4A(1)(a) nor (b), so they qualify for neither programme, whether or not they get Income Support. The test only shows a split after the date for rows that combine age 19 with the 16th-birthday fact set true, which can't happen in reality. The non-vacuity check (row[0] == 19 and row[4]) only covers the time before the date. In the docstring, replace "so a / young person on their own Income Support keeps the Universal Credit status / and loses the Pension Credit one, on either route and on either side of / the 1 September following the 19th birthday." with:

   one exception: SPC reg. 4A(5) lists it and UC reg. 5(5) does not, so a
   young person on their own Income Support who otherwise qualifies keeps
   the Universal Credit status and loses the Pension Credit one, on either
   route.

In the body's invariant 3, replace the last sentence with: "SPC reg. 4A(5) lists Income Support and UC reg. 5(5) doesn't, so that young person, where they otherwise qualify, keeps the Universal Credit status and loses the Pension Credit one, on either route."

P3-2. A YAML case at 19 would also pin the Income Support exclusion. The body's "none" is right only for the terminal-date file. Dropping | person("is_on_income_support", period) from Pension Credit's formula leaves all 14 cases of qualifying_young_person_terminal_date.yaml passing, as the body says. The mutant is still not "killed only by the new test": #2279's qualifying_young_person_reported_own_awards.yaml fails 4 of 23 cases, including "A 16-year-old reporting their own Income Support does not at…". Neither file pins the exclusion at 19. Here is a case worked by hand from the parameters. In 2025-26, gov.dwp.income_support.active is true (it turns false on 2026-04-01). The student is 19 on 6 October with no date of birth, so the date fact is true. They are not the claimant, so is_on_income_support reads their own report. That excludes them under reg. 4A(5) but not under reg. 5(5). Without the report, the first-child addition is £78.10 a week (first/addition.yaml, 2025-04-01) × 52 = £4,061.20.

- name: Case 15, a pensioner gets no Pension Credit child addition for a 19-year-old at school before the date who reports their own Income Support.
  # SPC reg. 4A(5) lists income support; UC reg. 5(5) does not.
  period: 2025
  absolute_error_margin: 0.01
  input:
    people:
      pensioner:
        age: 70
        is_claimant_or_partner: true
      student:
        age: 19
        current_education: UPPER_SECONDARY
        age_started_or_accepted_current_education_or_training: 17
        income_support_reported: 5_000
        is_claimant_or_partner: false
    benunits:
      benunit:
        members: [pensioner, student]
  output:
    is_before_first_september_following_19th_birthday: [false, true]
    is_on_income_support: [false, true]
    is_qualifying_young_person_for_universal_credit: [false, true]
    is_qualifying_young_person_for_pension_credit: [false, false]
    child_minimum_guarantee_addition: 0

uv run policyengine-core test is_case.yaml -c policyengine_uk, run with this case and a control (the same household without the report, expecting [false, true] and 4_061.20): 2 passed at the head. With the mutant: Case 15 fails and the control passes. If you add the case, add a row 15 to the body's YAML table ("as 4, in 2025-26, the 19-year-old reports their own Income Support | a UC QYP but not a PC one (reg. 4A(5); reg. 5(5) doesn't list it); addition £0"), change "fourteen cases" to "fifteen", and change the mutation row to "15".

P3-3. The scope note says these tests set Universal Credit's terminal-date input false, but two tests set both values. policyengine_uk/tests/test_child_and_adult_definitions_properties.py:67-68. The person template sets it false (line 136), but test_entry_terminal_and_wtc_disability_boundaries and test_programme_definitions_with_generated_entry_and_household_context pass terminal as both true and false (lines 520 and 559). Replace "The tests here set UC's terminal-date input / false;" with "The tests here set UC's terminal-date input, false unless a test varies it;".

P3-4. In the changelog fragment, the programme acts. changelog.d/qyp-terminal-date-19.fixed.md:1: "and Pension Credit applies the date, which it did not model". Replace the second sentence with: "The model now counts a 19-year-old in education for Universal Credit by default in household calculations, as the microdata already did, and applies the date to Pension Credit, which had no terminal date."

P3-5. The axiom: line doesn't take any of the template's forms. .github/PULL_REQUEST_TEMPLATE.md asks for exactly one of <legal id> encoded-correct | <rulespec-uk PR> encoded | <rulespec-uk issue> queued | n/a: <reason>. .github/CONTRIBUTING.md ("Axiom parity") says an encoded-correct claim names "a companion case … that exercises the same situation as your YAML test". The body says main has none for UC, so encoded-correct doesn't fit. The UC half reads as prose, with no keyword. If #529 is still open and test-only, one form that keeps every fact is: "axiom: TheAxiomFoundation/rulespec-uk#529 encoded (UC reg 5(1)(b), uk:regulations/uksi/2013/376/5#continued_education_or_training_qualifying_condition; #529 adds the companion cases that set the 19th-birthday input false, test-only; addendum on #355); TheAxiomFoundation/rulespec-uk#360 queued (SPC reg 4A(1)(b); addendum; …)". I couldn't read #529 (see below), so the hub should confirm its state before choosing the keyword. No CI job parses this line: .github/check_fix_pr.py has no "axiom" string.

Checked and sound

  • The Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279 merge (git show --remerge-diff 4f7af3818). Its only conflict is in the documentation of is_qualifying_young_person_for_pension_credit. The resolution keeps Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's text (UC, ESA, JSA or Income Support in their own right; "the own-right flag or their own reported award shows the first three; is_on_income_support shows Income Support"). It also keeps this branch's "before the 1 September following their 19th birthday", and drops "The model does not apply reg. 4A(1)(b)'s end…". The text matches the formula at the head: …/is_qualifying_young_person_for_pension_credit.py:37-40 requires meets_qualifying_young_person_terminal_date_condition_for_pension_credit on the education route, and lines 47-55 exclude receives_benefits_in_own_right, the five reported awards and is_on_income_support on both routes. Universal Credit's formula (…/is_qualifying_young_person_for_universal_credit.py) has the same blob at c5b9850a3, 4f7af3818 and 93d59f052 (42814033). Its blob at the merge base 560005cfa and at c710718dc (1b1cfd60) already read the terminal condition, so only Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279 changed it. It keeps the terminal condition (lines 34-37) and the own-award exclusions (42-49) without Income Support, and its documentation says the same.
  • Files both sides changed since the merge base 560005cfa (comm -12 of the two git diff --name-only lists): 30 files. 28 differ from the merge base only through main. c710718dc's version equals its main base a8b2848c1's, and Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's base daec84d5 descends from it. Git took Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's side (the newer main) for 18 of them, and both sides were already identical for the other 10. The two real overlaps: test_legacy_award_readers_properties.py, where c710718dc's change (the 16th-birthday fact set false for other members) is contained in Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's version and the merge took Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's version (git diff c5b9850a3 4f7af3818 -- <file> is empty); and the Pension Credit variable above. Each automatic merge keeps both sides' intent.
  • The main merge (git show --remerge-diff d3a457922) is empty: no conflicts. Invariant numbering: test_child_and_adult_definitions_properties.py runs 1-6, with the qualifying young person route as 5 and supplied roles as 6. The only number cited in prose there is "(invariant 5)" at line 67, which is correct. test_means_test_income_properties.py has invariants 1 and 2, and its "Invariant 2" comment (line 195) matches. The terminal-date file's "Invariant 1…5" comments (lines 124, 126, 211, 216, 219, 235, 304, 328, 355 and 396) each match the docstring.
  • Invariant 3 and Income Support. UC reg. 5(5) doesn't list Income Support and SPC reg. 4A(5) does (law-qyp.md), so the split is right on both routes before the date (see P3-1 for after it). The oracle qualifying_young_person encodes reg. 5(1), (2) and (5) without calling the model. PC's expectation, expected & ~own_income_support, follows from reg. 4A(5). The non-vacuity assertion reads the oracle, and with the PC assertion it forces a 19-year-old before the date to be a UC QYP and not a PC one. The hidden-combination check: I ran the 6,144-row GRID in 2024 and 2025 with uv run python grid2025.py. Output: 2024 on IS: 0 UC!=PC: 0 … UC!=oracle: 0 PC!=oracle&~IS: 0 and the same for 2025. Outside Income Support, the 2026-27 year hides no other UC/PC difference. gov.dwp.income_support.active resolves to True for 2025 and False for 2026, so the grid comment's claim holds.
  • Cases 13 and 14, worked by hand. Case 13: the parent is 45 with no date of birth, so the date fact is false, and is_before_universal_credit_qualifying_young_person_terminal_date (not set) follows it: [false, true]. The student's universal_credit_reported excludes them under both 5(5) and 4A(5), so the element is £0. No other child or young person is present, so is_uc_work_allowance_eligible is false. Case 14: the pensioner is 70, which is neither under terminal_date_age_limit (19) nor before the date, so the PC condition is false. The student is 19 and before the date, so it is true: [false, true], as the YAML has it. esa_income_reported excludes the student from both programmes, so the addition is 0. Body table rows 13 and 14 match the YAML.
  • Tests at the head (uv run policyengine-core test <file> -c policyengine_uk): qualifying_young_person_terminal_date.yaml 14 passed, child_qualifying_young_person.yaml 8, qualifying_young_person_reported_own_awards.yaml 23, qualifying_young_person_no_education_route.yaml 14, is_uc_work_allowance_eligible.yaml 13, means_test_income_members.yaml 7: 79 passed. uv run pytest <file> -n 4: terminal-date properties 8 passed, test_child_and_adult_definitions_properties.py 21, test_means_test_income_properties.py 2, test_legacy_award_readers_properties.py 20.
  • Mutants, each one change in a scratch worktree at the head, then restored:
Mutant qualifying_young_person_terminal_date.yaml …reported_own_awards.yaml Terminal-date property tests Result
PC terminal condition dropped 3 of 14 fail 23 pass 5 of 8 fail (input override, one person, agreement grid, Income Support, crossed) killed
UC terminal condition dropped 3 of 14 fail 23 pass 5 of 8 fail (the same five) killed
is_on_income_support dropped from PC 14 pass 4 of 23 fail 1 of 8 fails (Income Support) killed
universal_credit_reported dropped from PC (new) Case 13 fails 4 fail 8 pass killed
universal_credit_reported dropped from UC (new) Case 13 fails 4 fail 8 pass killed
esa_income_reported dropped from PC (new) Case 14 fails 5 fail 8 pass killed

The first three match the body's table. The grid's property tests don't set reported awards, so only YAML kills the last three.

  • git diff --check c710718dc 93d59f052 reports a new blank line at the end of couple_carer_award.yaml, housing_benefit_family_premium.yaml and severe_disability_premium_joint_occupiers.yaml. All three blobs are identical at the head, at f4a317019 and at 52b30809f (2dbd905a, 03498c69, deb68b6a), so they come from main. git diff --check f4a317019 93d59f052 is clean.
  • The merge with main now. git merge-tree --write-tree --name-only 93d59f052 52b30809f exits 0 with tree 4746f414a. Of the 15 commits on main since f4a317019, none touches a file this PR changes (comm -12 is empty). Those commits touch the trading allowance, utils/supplied_inputs.py (three trading-allowance inputs), the test workflow's timeouts and the version. No variable or test this PR reads moved. At a local commit of that tree (not pushed), after uv sync --frozen: the same six YAML files pass 79 of 79, and the four property files pass 8, 21, 2 and 20.
  • The body's voice pass (author's body against live body). It changes no required line: Fixes #2291, Reproducer: and axiom: are unchanged. Its changes of fact are corrections. "Round 3 mutations at 93d59f0" became "Mutations at d5b8d12", which matches the hand-off; git diff --stat d5b8d12d8 93d59f052 touches only the changelog fragment, so the result is the same. It drops a path on another machine (mutation/prD_r2.txt) and the "The review of this PR found…" opening. "fixed on Add Child Benefit qualifying young person routes for school leavers #2299" became "which Add Child Benefit qualifying young person routes for school leavers #2299 fixes", which is right because Add Child Benefit qualifying young person routes for school leavers #2299 is still open. The rest is wording. Every backticked variable, file, parameter and test id in the live body exists at the head: one class each in policyengine_uk/variables, both terminal_date_age_limit.yaml files, utils.dates.birth_day (used by the date fact, line 42), and the reproducer test_universal_credit_and_pension_credit_agree_on_every_combination at line 209. Body counts: len(GRID) is 6,144. 4 × 2 × 2 × 2 × 2 × 2 = 128 households. 2004-2009 has 2,192 days.
  • CI at the head (gh api …/commits/93d59f052…/check-runs --paginate): all 21 check runs completed with success. They include Python shards 1/7 to 7/7, Test / Result, Test / YAML, Lint, Changelog fragments, both Fix evidence runs and Aggregate impact (finished 04:04:15Z). None failed or was cancelled. Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279 is still open at c5b9850a35.

What I couldn't check


Generated by Claude Code

…otes

- Add Case 15: in 2025-26, a 19-year-old at school before the date who
  reports their own Income Support is a UC qualifying young person but not
  a PC one (SPC reg. 4A(5) lists Income Support; UC reg. 5(5) doesn't).
- Invariant 3's docstring: the Income Support split applies to a young
  person who otherwise qualifies, on either route.
- The scope note: the tests set UC's terminal-date input false unless a
  test varies it.
- The changelog fragment: the model applies the date to Pension Credit.

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

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head ee0482e75e0230e01ae55598df8d24e0089c2566. Review COMPLETE, with no open findings or material evidence gaps. A1 remains resolved: the actual fixture handoff correctly identifies the open rulespec-uk #529 work.

The newly merged own-award/QYP handling was checked through final Universal Credit, Pension Credit and household net income. All 66 selected regressions passed in the qualified local environment (core3.33.12 versus lock3.34.1); the focused final-benefit checks also passed in an existing environment matching all checked head-lock versions. The unchanged calendar evidence was retained.

The PR remains a draft, and current-head hosted CI is still running. Approval is withheld until its stated stack/readiness conditions and required checks are satisfied.

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head ee0482e75e0230e01ae55598df8d24e0089c2566 after the source-handoff update. Review COMPLETE, no remaining findings or material evidence gaps; A1 remains resolved.

The actual description now accurately cites rulespec-uk #531 as queued. #529 was closed without merging after its test-only change failed the signed-manifest guard. #531 carries the three source-backed companion cases, pinned module/corpus target and explicit signed-run approval blocker; these cases are queued, rather than merged encoding proof. The 355/360 records and unchanged primary-law expectations were checked.

The model head is unchanged. Valid prior evidence—66 passing selected regressions and the matching-runtime final UC/Pension Credit/income checks—was retained without rerunning it. Current-head CI passes. Approval remains deferred while this is a draft; its declared #2279-first stack condition must also be satisfied.

MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
ee0482e adds Case 15 to qualifying_young_person_terminal_date.yaml (a
19-year-old at school before the date who reports their own Income Support
is a UC qualifying young person but not a PC one), tightens invariant 3's
docstring and the terminal-date changelog fragment, and changes the scope
note of test_child_and_adult_definitions_properties.py. One conflict, in that
scope note: this branch's text, which names the Child Benefit and CTC leaver
inputs and their tests, with ee0482e's change that the tests set UC's
terminal-date input false unless a test varies it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QmsZJYMi6pBmPsiyFLGCQK
No conflicts. main now holds #2279's head c5b9850 (merged as
4499481) and #2855, which starts the cost-of-living payments in 2022.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H3DAwwvSTEwBJa5rD7bHvZ
No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6HKeDniUizUaZcFw4Q

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review status: COMPLETE at 25cb0070ee3db1e69a0f86be1f8a39073cc120dd. No open findings or material evidence gaps.

The 11 feature files match the previously reviewed head byte-for-byte. I reviewed their integration with current main, the updated description and applicable source records, retaining valid earlier evidence. A1 remains resolved.

23/23 current-head checks passed on lock-matched Core 3.34.1. This count is separate from the retained historical validation. The independent source review supports the UC/Pension Credit date distinction and the documented annual-date, partial-input and crossed-input limitations. Queued Axiom work remains queued; historical aggregate results are attributed to their stated heads.

No approval is issued while this PR remains a draft. The last CI capture included pending checks; those can be checked later without repeating this completed review. The described delivery order, or the combined #2302 delivery that carries both parents, remains explicit.

@MaxGhenis
MaxGhenis marked this pull request as ready for review November 5, 2026 13:10
@MaxGhenis
MaxGhenis requested a review from juaristi22 November 5, 2026 13:10

@juaristi22 juaristi22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review status: COMPLETE — approved at 25cb0070ee3db1e69a0f86be1f8a39073cc120dd. No findings or material evidence gaps remain.

The code head is unchanged from the completed review. I verified the retained code/source evidence and resolved findings, and assessed the current description and its primary CI provenance. No tests or model calculations were repeated for this readiness change; the historical validation and documented annual/reporting limitations retain their original qualifications.

The PR is now ready, and applicable required checks pass at this exact head. The head, description, review activity, source guards and checks are refreshed again before this approval.

@juaristi22
juaristi22 added this pull request to the merge queue Nov 6, 2026
MaxGhenis added a commit that referenced this pull request Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged via the queue into main with commit 7e8f345 Nov 6, 2026
21 of 23 checks passed
@juaristi22
juaristi22 deleted the qyp-terminal-date-19 branch November 6, 2026 14:24
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.

The 1 September after a qualifying young person's 19th birthday: false by default for Universal Credit, absent for Pension Credit

3 participants