Skip to content

Apply the Universal Credit education condition - #2806

Draft
MaxGhenis wants to merge 28 commits into
mainfrom
fix-2346-uc-education
Draft

MaxGhenis wants to merge 28 commits into
mainfrom
fix-2346-uc-education

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2346

What and why

Universal Credit has a basic condition that the claimant "is not receiving education" (Welfare Reform Act 2012 s.4(1)(d)). In the UC Regs 2013, the Secretary of State sets out who receives education (reg 12), when a person undertakes a course (reg 13) and who doesn't have to meet the condition (reg 14(1)). is_uc_eligible on main read no education condition, so the model paid a full-time student who meets no exception. The issue's 19-year-old university student in Plymouth gets £6,348.91 on main at 2da212a, the figure the issue reports.

This PR adds:

  • is_in_full_time_education (Person, year), an input that stays false until a user or dataset sets it: whether the person takes a full-time course of study or training. The model reads the level from current_education and in_HE, and no formula derives the input from them. Carer's Allowance is paid to carers in full-time education (SSCBA 1992 s. 70(3), SI 1976/409 reg. 5) #2311 needs the same fact for Carer's Allowance and can read it; this PR doesn't change Carer's Allowance.
  • has_education_maintenance_funding (Person, year), a second input that also stays false until a user or dataset sets it: whether the person has a student loan or grant for their maintenance on the course, the test in reg 12(2)(b). The model doesn't read student_loans or education_grants for it, since it can't tell from either amount what the support pays for. A learner aged 19 or over on a further education course can get Learner Support for travel, accommodation, materials and equipment, a laptop or childcare, and only support for their maintenance meets reg 12(2)(b). In case 30, a student on a full-time course that doesn't lead to advanced education, with a £100 equipment grant, a £3,000 student loan and no maintenance funding, keeps an award of £5,098.80.
  • is_qualifying_young_person_for_uc_education: the qualifying young person status the model reads for the condition. Under SI 2014/1230 reg 28, a person entitled to an existing benefit doesn't count as a qualifying young person for the UC Regs. The Secretary of State lists the existing benefits in reg 2(1): income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit. The model reads entitlement from the year's reported receipt, the evidence it reads for legacy closure. A claimant or partner on a couple's tax credit award counts as entitled, since the couple claims jointly (Tax Credits Act 2002 s.3(3)(a)). A person outside the definition under reg 28 takes the ordinary route under reg 12(2). This PR leaves is_qualifying_young_person_for_universal_credit as it stands: it doesn't apply reg 28, and the model still reads it for the child element, the benefit cap and Council Tax Reduction. Apply SI 2014/1230 reg 28 in is_qualifying_young_person_for_universal_credit #2804 tracks the same rule in is_qualifying_young_person_for_universal_credit.
  • uc_receiving_education (reg 12). A person on a full-time course receives education where:
    • they take a course of advanced education under reg 12(2)(a) and (3), which the model reads from tertiary current_education or in_HE;
    • they have a student loan or grant for their maintenance under reg 12(2)(b), which the model reads from has_education_maintenance_funding;
    • or they count as a qualifying young person after reg 28, whom the decision maker treats as receiving education under reg 12(1A).
  • uc_is_excepted_from_education_condition (reg 14(1), and SI 2014/1230 reg 60), with one sourced condition for each limb the model can express:
    • (b): entitlement to Attendance Allowance (including Armed Forces Independence Payment, which DWP counts as Attendance Allowance under reg 2), DLA or PIP, with limited capability for work (uc_limited_capability_for_work);
    • (c): responsibility for a child or qualifying young person in the benefit unit;
    • (d): a single foster parent, whom the model identifies from uc_is_responsible_foster_parent_of_child_under_one or uc_is_foster_parent_or_new_friend_or_family_carer;
    • (e)(ii): a member of a couple, both of whom receive education, whose partner fosters a child;
    • (f): the older member of a mixed-age couple;
    • SI 2014/1230 reg 60(1): a member of a family that claims when DWP moves it off its legacy benefits (claims_uc_at_legacy_closure), which the model takes as the qualifying claim under reg 48. A person who receives education under reg 12(2) gets the protection; a qualifying young person under reg 12(1A) doesn't, while a person outside the definition under reg 28 takes reg 12(2) and gets it.
    • The model needs no rule of its own for limb (e)(i): it places responsibility for a child on the benefit unit, so both members meet (c).
  • meets_uc_education_condition (s.4(1)(d)): not receiving education, or having an exception.
  • is_uc_eligible needs one claimant (is_uc_single_or_joint_claimant) to meet the age condition and the education condition together, on every route.

The couple rule

Under reg 3(2), a couple can claim jointly where one member has reached the qualifying age for State Pension Credit "if the other member does meet that condition", or where one member receives education and has no exception "if the other member does meet that condition or is excepted from it". In ADM E2016, DWP accepts a joint claim where one member meets all the basic conditions. Under reg 14(1)(f), an older student can claim jointly with a younger partner who meets them. The model follows E2016, so one claimant must meet the age and education conditions together. A member of a couple who claims as a single person under reg 3(3) has no joint claimant beside them, so they must meet both conditions themselves.

For a couple aged 70 and 60 with no income in 2026-27, the model gives these awards:

Who studies full-time Award Why
Neither £8,003.64 The 60-year-old meets every basic condition (reg 3(2)(a))
The 70-year-old £8,003.64 The 70-year-old has the reg 14(1)(f) exception, and the 60-year-old meets both conditions (reg 3(2)(a))
The 60-year-old nil The 70-year-old meets the education condition and the 60-year-old the age condition, but neither meets both (ADM E2016)
Both nil The 70-year-old has the reg 14(1)(f) exception but still doesn't meet the age condition, so again neither meets both

Each positive award comes to 12 × £666.97 = £8,003.64, DWP's joint rate where one or both claimants are 25 or over, from Benefit and pension rates 2026 to 2027. Since 15 May 2019 the couple can't claim Pension Credit instead (SPCA 2002 s.4(1A)).

The model keeps the condition on every route into is_uc_eligible:

Route, 2026-27 unless stated Neither studies Older studies Younger studies Both study
Ordinary: aged 70 and 60 £8,003.64 £8,003.64 nil nil
Closure claim: aged 70 and 60, and under reg 60(1) neither member has to meet the condition £8,003.64 £8,003.64 £8,003.64 £8,003.64
Reg 60A: aged 72 and 70 on Working Tax Credit, with a closure claim £8,003.64 £8,003.64 £8,003.64 £8,003.64
Aged 72 and 70 on Working Tax Credit, no closure claim: the Pension Credit route nil nil nil nil
Pension Credit route: aged 70 and 60 in 2019-20, before 15 May 2019 nil nil nil nil
The same, with a closure claim: 12 × £498.89 £5,986.68 £5,986.68 £5,986.68 £5,986.68

For a couple aged 30 where one member takes a full-time degree and the other doesn't, the pair claim jointly at the couple rate, £8,003.64 a year with no income. Under reg 68, DWP counts a student's loan and some grants as student income, after statutory exclusions and disregards. The model leaves student_loans and education_grants out of its Universal Credit unearned income list (gov.dwp.universal_credit.means_test.income_definitions.unearned), so it counts no student income and the couple gets the full joint amount. If both members study and neither has an exception, the couple gets nothing.

Young people on legacy benefits

An 18-year-old on a full-time course that doesn't lead to advanced education, who reports Working Tax Credit or Housing Benefit, keeps the award they get on main. Under reg 28 they don't count as qualifying young people, and with no maintenance funding they don't receive education under reg 12(2). One with maintenance funding receives education under reg 12(2)(b), and on a closure claim keeps the award under reg 60(1) (cases 34 and 42).

18-year-old, social rent £6,000, claims when DWP moves them off legacy benefits, 2026-27 main This PR Arithmetic
Reports Working Tax Credit of £1,000 and earns £9,000 £5,112.96 £5,112.96 12 × £338.58 + £6,000 − 55% × £9,000
Reports Housing Benefit of £6,000, with Housing Benefit awards closed £10,062.96 £10,062.96 12 × £338.58 + £6,000

Limited capability for work

Under reg 14(1)(b), for a claim on or after 15 December 2021, the decision maker must determine limited capability for work (LCW) before the course starts. The model reads uc_limited_capability_for_work and holds no other LCW fact. The model can derive the LCW variable from disability status without an assessment, so it can keep students who have never had an LCW determination, as well as students with an assessment after their course began. A full-time student aged 30 on the standard daily living rate of PIP, with is_disabled_for_benefits true and no limited capability for work and work-related activity, gets £5,098.80 (12 × £424.90) in the model. The model doesn't infer LCW from an ESA award. A user who wants an ESA assessment to count sets uc_limited_capability_for_work or uc_limited_capability_for_WRA, and a user or dataset can set is_disabled_for_benefits, from which the model derives LCW.

What the model can't express

The model can't express the parts of regs 12 to 14 and SI 2014/1230 regs 28 and 60 below.

  • Reg 14(1)(a): a person under 21, or 21 having reached that age during the course, on a full-time course that doesn't lead to advanced education, without parental support (reg 8(3)). The model has no input for parental support. Where a user flags such a student and they count as a qualifying young person, or have maintenance funding, the model gives nil where DWP must pay the award. Otherwise the model doesn't treat them as receiving education, and they keep the award.
  • Reg 14(1)(b) timing: for a claim on or after 15 December 2021, the decision maker must determine LCW before the course starts (SI 2021/1224). For a claim from 5 August 2020 to 14 December 2021, SI 2020/827 required the determination by the date of claim, or by the course start where the course starts after the claim. The model reads any LCW, so a student with a late assessment keeps the award, where DWP must pay nil.
  • Reg 14(1)(b) benefits: child disability payment, adult disability payment, pension age disability payment, Scottish adult disability living allowance, and the constant attendance and war pension payments that DWP counts as attendance allowance under reg 2. The model has no variables for them. A student whose one listed benefit is among these gets nil, where DWP must pay the award.
  • Foster parents (reg 14(1)(d) and (e)(ii)): if a user sets neither input for a foster parent, the model gives nil where DWP must pay the award. A user can also set the second input for a foster parent between placements, who keeps the award in the model, where DWP must pay nil.
  • Reg 12(1A): if a user doesn't set the input for a qualifying young person, the model doesn't treat them as receiving education, and they keep the award. A flagged one in a relevant training scheme (reg 12(1B)) gets nil, where DWP must pay the award. Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279 adds a group to this gap: under reg 5(1)(a), it counts a 16-year-old before the 1 September after their 16th birthday as a qualifying young person whatever their education. With both PRs merged, a 16-year-old carer outside education who claims alone with no income in 2026-27 counts as a qualifying young person, doesn't receive education in the model and gets £6,575.04, where DWP must pay nil under reg 12(1A). The author of whichever PR lands second must apply reg 12(1A) to that route without the full-time flag and add the 16-year-old carer case as a regression test.
  • Reg 12(4): the Secretary of State treats a claimant as receiving education when their course conflicts with a work-related requirement the Secretary of State imposes. The model keeps their award.
  • Reg 13(4): a person who leaves a course with the establishment's consent because of illness or caring, recovered or stopped caring within the past year, hasn't resumed and can't get a grant or loan doesn't undertake the course in that time. Set the input false only when all of these hold.
  • SI 2014/1230 reg 28: the model reads the year's reported receipt of an existing benefit in place of entitlement on a given day. The model removes the status for the whole year when a person reports receipt for part of it, or reports a tax credit in 2025-26 or later although no award exists, and keeps the status for a person on a nil-rate award.

Where the annual closure proxy departs from reg 60

The model applies the reg 60(1) protection to a claimant or partner who doesn't count as a qualifying young person, in a family that claims at closure (claims_uc_at_legacy_closure), in every model year in which that flag holds. Unless a user or dataset sets the flag, the model derives it from legacy_benefits_closed and would_claim_uc_at_legacy_closure, and unless a user or dataset sets legacy_benefits_closed, it takes that closure from the active parameters. Against reg 60, and SR 2016/226 reg 61 in Northern Ireland, the model departs in the ways below. Each gain or loss concerns a full-time student with no reg 14(1) exception whose family has no other claimant who meets both the age and the education conditions, such as the single claimant of cases 36 to 41 or the younger member of case 25's mixed-age couple.

  • Migration day. Under reg 60(1), DWP lifts the condition for a student on the course on the migration day, the day before the Universal Credit award starts (reg 49). With the closure taken from the active parameters, the model protects a student only from the model year in which their reported benefit closes: 2025-26 for tax credits, 2026-27 for Income Support and income-based JSA. A tax credit family whose Universal Credit award started in 2024-25 has its migration day in that year, where, on the parameters alone, the model protects nobody (case 40). So for 2024-25 the model gives no Universal Credit to a student in that family whom DWP protects under reg 60(1). Likewise, on the parameters alone, the model gives no Universal Credit before 2026-27 to a student whom DWP protects under reg 60(1) after an earlier migration from Income Support or income-based JSA. The model also never applies the text in force before 27 January 2025, under which DWP lifted the condition for a notified person who was undertaking a full-time course "on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit" (SI 2025/3 reg 6(9)). Where, for a year before 2025-26, a user or dataset sets claims_uc_at_legacy_closure true, as test_education_condition_invariants does in 2019, 2021 and 2024, or sets legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds, the model protects every claimant and partner in the family who doesn't count as a qualifying young person. DWP protected no one under reg 60 before 24 July 2019, when SI 2019/1152 inserted it, then only a notified person until 26 January 2025, and from 27 January 2025 only a person who had made a qualifying claim (reg 48); in Northern Ireland the Department for Communities could issue no migration notice before 25 July 2022 (SR 2016/226 reg 45(7)). So a partner whom DWP never notified gains, and so does everyone the model protects before 24 July 2019, or in Northern Ireland before 25 July 2022.

  • The same course. Under reg 60(1), DWP lifts the condition only "while they are continuing to undertake that course", the course the student undertook on the migration day. The model reads is_in_full_time_education year by year, keeps no record of which course a student takes, and keeps the exception whatever the course, or none, as case 38's comment notes and test_reg_60_protection_follows_the_closure_dates checks. A student who finishes that course and starts another full-time course keeps the award in the model, where DWP applies the condition again and pays nil. So does a claimant who wasn't on a full-time course on the migration day and starts one later, to whom DWP never applies reg 60(1).

  • Reg 60(2) (NI reg 61(2)), which the model never applies. Under it, DWP (in Northern Ireland, the Department for Communities) stops the protection in any assessment period in which a transitional element would have stopped under reg 56 or reg 57 (NI regs 57 and 58), and under reg 56(1) in every later one:

    • from the assessment period after three in a row of earned income below the administrative earnings threshold, where the first assessment period's earned income reached it, counting a claimant to whom the minimum income floor applies, or would apply but for a start-up period, as reaching it (reg 56(2) and (3));
    • for an award under reg 60A (NI reg 61A), from an assessment period after the first 12 that follows three in a row of earned income below 16 hours a week at the national minimum wage (reg 56(3A));
    • when a single claimant joins a couple, unless they can still claim as a single person under UC Regs reg 3(3) (NI UC Regs reg 3(2)), or when joint claimants separate or join another couple (reg 56(4));
    • once the award ends, unless earnings ended it and a new award starts within three months (reg 57).

    The model keeps the student's exception through each, so the student keeps the award in the model where DWP applies the condition again. Case 37 works through these events for a claimant with no earnings, who stays single and keeps the award.

  • The claim. The model has no deadline day. It treats a claim at closure (claims_uc_at_legacy_closure, which it derives from legacy_benefits_closed and would_claim_uc_at_legacy_closure unless a user or dataset sets it) as the qualifying claim under reg 48, and any other claim (would_claim_uc alone) as a claim outside the migration, as cases 36 and 39 show. A family that claimed of its own accord by the final deadline after its migration notice made a qualifying claim, so where a user or dataset sets would_claim_uc_at_legacy_closure false for it, its student loses the award in the model, where DWP pays it. A family that claimed before DWP issued it a migration notice didn't claim as a notified person (reg 44(6)), so its claim doesn't count as a qualifying claim (reg 48). Yet if it still reports the closed benefit and keeps the default would_claim_uc_at_legacy_closure, its student gains the award in the model, where DWP pays nil. Nor does the model check that DWP issued a migration notice to both joint claimants (reg 48), so a couple whose second member never got one gains the protection.

  • Reported receipt. As for reg 28, the model reads the year's report in place of entitlement to an existing benefit on a given day (reg 44(1)), and it keeps reading a legacy benefit report after the award closed. With the flag derived, a student who reports a closed benefit for part of a year counts as migrated for the whole year, and gains the award for the months before their migration day (reg 49). A student whose report stops in a later year, as the report of an award that ended would, loses the protection in the model from that year, where DWP keeps it while they continue the course (reg 60(1)). A student who doesn't report the award DWP moved them off gets no protection in the model. Cases 37 and 38 keep the report in 2026 and 2027 to stand for the award that closed, and where a user or dataset sets claims_uc_at_legacy_closure, the student keeps the protection without a report.

Northern Ireland

Claimants in Northern Ireland meet the same condition under the Welfare Reform (Northern Ireland) Order 2015 art. 9(1)(d). The Universal Credit Regulations (Northern Ireland) 2016, which the Secretary of State made under Article 4(1)(a) of the 2015 Order, have GB reg 3(2) as their reg 3(1), and GB reg 13 and reg 14(1) under the same numbers. Under their reg 12(1), the decision maker treats every qualifying young person as receiving education, with no exception for a training scheme, which the model doesn't apply in Great Britain either. So the model's one rule covers Northern Ireland, and case 21 tests it there.

The Department for Communities applies the transitional provisions this PR follows under the Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016 (SR 2016/226), with these dates from legislation.gov.uk:

GB, SI 2014/1230 NI, SR 2016/226 In operation in NI
reg 28, meaning of "qualifying young person" reg 29 From 27 September 2017. SR 2016/226 came into operation immediately after the NI Universal Credit Regulations (reg 1), which come into operation on the day on which Article 6 of the Welfare Reform (NI) Order 2015 "comes into force for all purposes in respect of the whole or any part of Northern Ireland" (their reg 1); legislation.gov.uk dates both from 27 September 2017, when Article 6 came into force for claims in the first districts (SR 2017/190 art 4(3) to (5) and Schedule 1).
reg 48, meaning of "qualifying claim" reg 49 From 24 July 2019, when Part 4 came in (SR 2019/152).
reg 60, protection for full-time students reg 61 From 24 July 2019 (SR 2019/152). The Department for Communities has applied paragraph (2) since 1 August 2022 (SR 2022/194 reg 11). Since 27 January 2025 it has applied paragraph (1) to a person who has made a qualifying claim and undertakes the course on the migration day; before then it applied it to a notified person undertaking the course on the day on which all their existing awards were to terminate as a consequence of a claim for universal credit (SR 2025/3 reg 6(9)).

NI regs 29, 49 and 61 match GB regs 28, 48 and 60, with NI cross-references: Article 9(1)(d) of the 2015 Order for section 4(1)(d) of the 2012 Act, reg 6 of the NI Universal Credit Regulations for reg 5, reg 47(4) for reg 46(4), and regs 57 and 58 for regs 56 and 57. So the model's GB routes carry over to Northern Ireland unchanged, and its departures from reg 60 above apply to reg 61 too. Case 41 tests reg 61(1): the student of case 36, in Northern Ireland, keeps 12 × £400.14 = £4,801.68 in 2025-26, at the monthly standard allowance of £400.14 that the Department for Communities set in the Social Security Benefits Up-rating Order (Northern Ireland) 2025 art 32(2) and Schedule 13, as made.

Which years

The change reaches every year the model calculates Universal Credit eligibility. No parameter or date limits it: regs 12 and 14 have applied since 29 April 2013, reg 28 since 16 June 2014 with no amendment, and the dated changes to reg 14(1)(b) concern its timing and the Scottish benefits, which the model doesn't read. In model year 2014 the model raises ParameterNotFoundError for gov.dwp.pension_credit.mixed_age_couples.saving_latest_birth_date, on main too. Case 22 tests 2016.

Where the model takes the closure from the active parameters, it applies the reg 60(1) protection only from 2025-26 for tax credits and from 2026-27 for Income Support and income-based JSA, in Great Britain and Northern Ireland alike. So on the parameters alone, the model protects no one in 2024-25 or earlier, and never reaches a year in which only the text in force before 27 January 2025 applied. A user or dataset can also set claims_uc_at_legacy_closure true for any year, or legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds (its default), and the model then applies the protection in that year: cases 19, 25, 26, 34 and 35 set the first for 2026, and test_education_condition_invariants draws it for 2019, 2021, 2024 and 2026. Cases 36 to 42 test 2024 to 2027 with the closure taken from the parameters, and test_reg_60_protection_follows_the_closure_dates draws its model year from 2020 to 2028, besides its grids for 2024 to 2026.

Data

This repository holds no survey-backed record of full-time study or maintenance funding. git grep finds:

  • current_education, the enrolment stage;
  • in_HE and in_FE;
  • employment_status, with a STUDENT category;
  • student_loans and education_grants, amounts that don't say what they pay for;
  • childcare_grant_full_time_student, an input no dataset supplies.

Both new inputs stay false on current data until a dataset supplies them, so aggregates don't move. With neither input set, the new variables still evaluate: they give everyone meets_uc_education_condition true, so is_uc_eligible gives main's result. On two hand-built datasets with neither input, every variable both versions define gives main's values.

Overlapping work

git merge-tree merges this branch cleanly with main at 2da212a, 52b3080 and 1d4d020, #1969 (b4e8722), #2279 (c5b9850), #2688 (0d760fc), #2845 (f379377), fix-2120-uc-carer-awards (260f6a7) and fix-2562-esa-abolition (#2813, dc69f3a).

  • Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279 reads raw reports of Universal Credit, ESA of either kind and JSA of either kind, and excludes a young person who reports any of them from qualifying young person status. Under UC Regs reg 5(5), DWP excludes a young person on Universal Credit, contributory ESA or contribution-based JSA, and under SI 2014/1230 reg 28 one on income-related ESA or income-based JSA. Merged with this branch, an 18-year-old on full-time A levels who reports Universal Credit or contributory ESA no longer counts as a qualifying young person, and gets £4,062.96 and £1,062.96 respectively. This branch alone gives nil, since it reads reg 5(5) only from receives_benefits_in_own_right. Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's reports of income-related ESA and income-based JSA overlap this branch's reg 28 list, and applying both gives the same result. The reg 5(1)(a) route needs the change in the reg 12(1A) bullet above.
  • Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845 applies SI 2014/1230 reg 28 in is_qualifying_young_person_for_universal_credit and reads a report only while the model pays that benefit. With both merged, cases 31, 33 and 34 fail on that variable, and whichever PR lands second makes is_qualifying_young_person_for_uc_education return it. On its own, that change stops the reg 60 gate reading a closed report. An 18-year-old on a full-time non-advanced course with maintenance funding, whose closure the model derives from a Working Tax Credit report, would then count as a qualifying young person and lose the reg 60(1) protection, although on the migration day reg 28 still applied and the student received education under reg 12(2)(b). Case 42 tests that migrant, and fails under that change alone. So the author of whichever PR lands second keeps the gate on the migration-day status, which reads the report of the award that the claim ended, and keeps case 42 passing. With that gate, the model still gives £5,112.96 in the 2026-27 Working Tax Credit row of "Young people on legacy benefits", but through the reg 60(1) exception rather than reg 28. Whether DWP pays that young person once the tax credit award has ended turns on UC Regs reg 5(5), which this PR leaves open.
  • fix-2562-esa-abolition widens claims_uc_at_legacy_closure to families whose income-related ESA award the abolition reaches (esa_income_abolition_reached). This branch then gives reg 60 protection to every claimant and partner in those families who doesn't count as a qualifying young person. DWP gives reg 60(1) protection only to a person "who has made a qualifying claim", which the Secretary of State defines in reg 48 as "a claim for universal credit by a single claimant who is a notified person or by joint claimants, both of whom are notified persons, where the claim is made on or before the final deadline". Abolition alone does not make their claim qualifying: under regs 44(6) and 48, DWP must have issued a migration notice to each claimant, and they must claim by the final deadline. The Abolish income-related ESA from 1 July 2026 (NI 1 October 2026) #2562 author should distinguish that qualification from the abolition-triggered claim proxy before the model gives reg 60 protection. The author of whichever PR lands second carries that integration fix.

Invariants

These hold for every input:

  1. Never raises. Setting is_in_full_time_education never raises a benefit unit's Universal Credit. A family in a household of its own gets either its main amount or nil.
  2. Unflagged households don't change. In a household where no one has the input set, every output equals main's.
    • When the household head's family includes a student and can't claim, its non-dependants count in the next claim by a family liable for the rent, whose award then falls by the housing cost contribution. Under Sch 4 para 9(2)(f), DWP doesn't count a person as a non-dependant in a claim if it already counts them in another claim by someone liable for the rent. The model assigns non-dependants to the household head's eligible claim first.
    • In the cross-family test, a joint tenant whose own family has no one flagged loses 12 × £96.55 = £1,158.60 in 2026-27 (Sch 4 para 14(1), as SI 2026/148 art. 32(3) sets it).
  3. Exceptions keep the claimant in. A claimant who meets a modelled reg 14 exception, or reg 60, meets the condition, and a family whose claimants all meet it gets its main Universal Credit.
  4. The couple decides. The input on a child or qualifying young person in the benefit unit never changes its Universal Credit.
  5. One member meets both. Every eligible family that makes no closure claim has a claimant who meets the minimum age, hasn't reached the qualifying age for State Pension Credit and meets the education condition.
  6. Maintenance funding comes from the input. Removing every student loan and education grant changes no one's receiving education and no Universal Credit output.
  7. Reg 28. No one who reports an existing benefit counts as a qualifying young person for the condition.
  8. Reference agreement. Receiving education, the exceptions, the reg 28 status and is_uc_eligible equal a reference implementation, which reads the inputs and applies regs 3(2), 12 and 14 as DWP reads them in ADM E2016, and SI 2014/1230 regs 28 and 60. Without the education condition, the reference gives main's eligibility.
  9. Closure dates. For single people aged 25 to 59 with no child, no listed disability benefit, no foster role, no capital and no other benefit report, where the model derives claims_uc_at_legacy_closure (no user or dataset sets it or legacy_benefits_closed), uc_is_excepted_from_education_condition holds if and only if, in that model year, they report a legacy benefit whose active parameter reads false and their family claims at closure (would_claim_uc_at_legacy_closure). The model then finds a full-time degree student among them eligible if and only if the exception holds, and finds anyone among them who isn't on a full-time course eligible either way.

We check the first eight with Hypothesis in policyengine_uk/tests/test_uc_receiving_education_properties.py, for families in households of their own:

  • 15 drawn cases plus 4 explicit ones;
  • up to four families each, in 2019, 2021, 2024 or 2026, drawing Attendance Allowance, Armed Forces Independence Payment, DLA, PIP, the maintenance input and reported Housing Benefit and Working Tax Credit;
  • one simulation that calculates each case five ways: as drawn, with no one flagged, with the non-members' flags reversed, with no loans or grants, and under main's is_uc_eligible formula, which the test copies with its comments removed; the formatter re-wrapped two lines.

test_student_head_moves_non_dependant_to_joint_tenant pins the cross-family effect.

test_reg_60_protection_follows_the_closure_dates checks invariant 9:

  • 40 drawn cases plus explicit grids of 48 people for 2024, 2025 and 2026, the years either side of each closure: in North East England and in Northern Ireland, no legacy report, each alone, and Child Tax Credit with Income Support, which the model closes in different years, with would_claim_uc_at_legacy_closure true or false (and would_claim_uc the opposite), on a full-time degree or not;
  • a model year from 2020 to 2028 and up to 24 single people, for each of whom Hypothesis draws the four legacy reports (Child Tax Credit, Working Tax Credit, Income Support and income-based JSA), both claim flags, a region, Northern Ireland included, and a full-time degree;
  • one simulation for each case. The test reads the closure dates from the active parameters, not from constants, and test_closure_grids_straddle_each_closure checks that the grid years, which it reads from the constants the grids use, still fall either side of each closure.

Test plan

  • The YAML tests use legislation and DWP rates for their expected values: the 2026-27 rates from DWP's Benefit and pension rates 2026 to 2027, the 2025-26 rate from DWP's Benefit and pension rates 2025 to 2026 and, for Northern Ireland, the Social Security Benefits Up-rating Order (Northern Ireland) 2025 Schedule 13, and the taper from reg 22(1)(b); test_student_head_moves_non_dependant_to_joint_tenant takes the non-dependant contribution from SI 2026/148 art. 32(3). Case 2's housing costs element comes from the model's Universal Credit LHA rate for Plymouth (£400 a month), and its total matches main.
  • make format equivalent on the changed files: uvx ruff@0.17.2 format --check, uvx ruff@0.17.2 check, uvx ruff@0.17.2 check --select F811,F821 policyengine_uk and git diff --check, all clean.
  • Changelog fragment changelog.d/2346.fixed.md.

Results, each run alone without survey data (HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset):

Run Branch main (1d4d020)
tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml (new) 42 passed 42 failed (the variables don't exist)
tests/test_uc_receiving_education_properties.py::test_examples_reach_the_cases 1 passed
tests/test_uc_receiving_education_properties.py (new, all five tests), with test_uc_legacy_closure_properties.py (4) and test_legacy_closure_scope.py (21), -n 4 30 passed in 246 s this file: 4 failed (the input doesn't exist); test_closure_grids_straddle_each_closure, which reads only parameters, passed
tests/test_parameter_descriptions.py 24 passed
Differential, every variable, two hand-built datasets with neither input (380 people, 240 benefit units of which 125 get Universal Credit, 180 households), at an earlier head; no formula has changed since 1,387 shared variables × 2 datasets = 2,774 identical, none raising
Existing tests/policy/baseline/finance/benefit/family/universal_credit/ (directory) 563 passed in 43 s
tests/policy/baseline/gov/dwp/universal_credit/ (directory, the 42 new cases and 16 existing ones) 58 passed
person_legacy_benefits_closed.yaml and the three legacy_closure_*.yaml files under tests/policy/baseline/ 3, 1, 4 and 9 passed
tests/code_health (-n 4) 3,095 passed

We ran each mutant on a copy of the code, first against the YAML cases and then against the property file. For the first nine mutants, the YAML runs, against the 35 cases, and the ninth mutant's property run used db140e0; their other property runs used 13bbf82, and the two with shrinking off used 27e004a. At all three commits every formula matches this head, and the property file's code matches it apart from docstrings, documentation and the closure tests, which these runs predate. At db140e0 the YAML file differs from this head only in comments and cases 36 to 41. The last two rows ran at f06a9c6, on all 41 cases, person_legacy_benefits_closed.yaml, the three legacy_closure_*.yaml files and the property files; the legacy closure YAML files pass under both.

Mutant YAML cases failing Property tests
Keep PIP alone in reg 14(1)(b), dropping Attendance Allowance, AFIP and DLA 3 of 35 (cases 27, 28 and 29) no failure before the 1,500 s cap with -x; with shrinking off, fails at the reference differential after 1,055 s
Drop the maintenance funding limb of reg 12(2)(b) 3 of 35 (cases 15, 32 and 34) fails (287 s)
Test the age and education conditions on any claimant separately, so no member need meet both 2 of 35 (cases 7 and 8) fails on an explicit example (137 s)
Drop reg 28, reading is_qualifying_young_person_for_universal_credit 4 of 35 (cases 31, 32, 33 and 34) fails (131 s)
Read student_loans and education_grants in place of has_education_maintenance_funding 4 of 35 (cases 15, 30, 32 and 34) fails (322 s)
Drop reg 14(1)(f) 2 of 35 (cases 7 and 23) fails (573 s)
Apply reg 28 to the person's own reports, without the joint tax credit award 1 of 35 (case 33) no failure before the 1,500 s cap with -x; with shrinking off, passes all 19 examples
Drop the education condition from the reg 60A route 1 of 35 (case 35) no failure before the 1,500 s cap with -x
Read is_qualifying_young_person_for_universal_credit, before reg 28, for the reg 60 protection 1 of 35 (case 34) test_examples_reach_the_cases fails (64 s)
Drop migrated_student, the reg 60(1) limb, from uc_is_excepted_from_education_condition 7 of 41 (cases 19, 25, 26, 34, 36, 37 and 41) 3 of 5 fail (24 s): test_reg_60_protection_follows_the_closure_dates on its 2025 and 2026 grids, test_education_condition_invariants on an explicit example, and test_examples_reach_the_cases
Make legacy_benefits_closed close any reported legacy benefit, ignoring the active parameters 1 of 41 (case 40) 1 of 5 fails (77 s): test_reg_60_protection_follows_the_closure_dates on its 2024 and 2025 grids; in the legacy closure property files, 3 of 4 tests in test_uc_legacy_closure_properties.py and 1 of 21 in test_legacy_closure_scope.py fail (442 s)

The YAML cases kill all eleven mutants. A property run that reaches its cap without a failure only shows that the cases it reached in time don't kill that mutant; several runs shared the host, so the times also vary with load.

Case 35 pins the reg 60A gate. With a closure claim, every claimant who isn't a qualifying young person meets the condition under reg 60(1), and a claimant or partner on the couple's tax credit award loses that status under reg 28. A claimant responsible for a child or qualifying young person in the benefit unit meets reg 14(1)(c). The gate changes the result only where the claimant is a qualifying young person and an adult in the benefit unit who is neither claimant, partner, child nor qualifying young person reports Working Tax Credit, as in case 35. The model keeps the gate because under reg 60A the Secretary of State waives the age condition and nothing else. The model's reg 60A route reads Working Tax Credit from every member of the benefit unit, where under reg 60A(1) the single claimant, or both joint claimants, must be "entitled to an award of working tax credit"; that predates this PR, and we set it out in #2805.

No existing test's expected value changes.

Reproducer

Reproducer: policyengine_uk/tests/test_uc_receiving_education_properties.py::test_examples_reach_the_cases; policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml

axiom: TheAxiomFoundation/rulespec-uk#514 queued

🤖 Generated with Claude Code

MaxGhenis and others added 20 commits October 31, 2026 18:49
Add the person input is_in_full_time_education (false unless set), the
reg 12 test uc_receiving_education, the reg 14(1) and SI 2014/1230 reg 60
exceptions in uc_is_excepted_from_education_condition, and the WRA 2012
s.4(1)(d) basic condition meets_uc_education_condition. is_uc_eligible
needs one claimant to meet it, as reg 3(2)(b) provides for a couple.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Twenty-two cases: the issue's Plymouth student with the input true (nil)
and false (£6,348.91), a single parent student, couples with one or two
students, a student couple with a child or a foster parent, mixed-age
couples, PIP with and without limited capability for work, a qualifying
young person, loans and grants on non-advanced courses, a reg 3(3)
single claim, a legacy closure claim, Northern Ireland and 2016.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drawn families, each in a household of its own, check that the input
never raises Universal Credit, that unflagged families match main's
eligibility formula (kept verbatim), that eligibility matches a
reference implementation of regs 3(2)(b), 12 and 14, that excepted
claimants keep their award, and that only the couple's flags count. A
further test pins the intended effect through non-dependants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewords docstrings and test comments; no formula or expected value
changes.

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

- is_uc_eligible: one claimant must meet the age condition and the
  education condition together, as DWP's ADM E2016 reads reg 3(2), on the
  ordinary, closure-claim and reg 60A routes.
- has_education_maintenance_funding: a person input for reg 12(2)(b),
  read in place of student_loans and education_grants.
- is_qualifying_young_person_for_uc_education: applies SI 2014/1230
  reg 28 before regs 12(1A) and 60 read qualifying young person status.
- uc_is_excepted_from_education_condition: states the limited capability
  for work approximation and corrects the limb (a) and (b) wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and reg 28

Cases 7 and 8 now expect no award under ADM E2016, cases 15 and 16 use
has_education_maintenance_funding, and cases 23 to 33 cover the other two
mixed-age patterns, the closure-claim and reg 60A routes, Attendance
Allowance, Armed Forces Independence Payment and DLA alone, an equipment
grant without maintenance funding, and reg 28.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference implementation now applies ADM E2016's common-member rule,
reg 28 and the maintenance funding input, and the generator draws
Attendance Allowance, AFIP, DLA, the new input and reported Housing
Benefit and Working Tax Credit. New invariants: one member meets both
conditions, loans and grants don't decide receiving education, and no one
reporting an existing benefit counts as a qualifying young person.

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>
A reform keeps this branch's is_uc_eligible and gives benefit units
flagged uses_main_eligibility main's formula, so each drawn case needs one
simulation instead of two, and the explicit examples share one.

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>
Case 34 gives case 32's 18-year-old a closure claim: reg 28 sends them
to reg 12(2), so reg 60(1) lifts the condition. Case 35 puts Working Tax
Credit on another adult in a qualifying young person's benefit unit:
reg 60A waives only s.4(1)(b), so the claimant still fails s.4(1)(d).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uc_limited_capability_for_work takes uc_limited_capability_for_WRA as
well as is_disabled_for_benefits, so a user can set either assessment
input for an ESA determination. The documentation also names the actor
in each sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The copy drops main's comments, and the formatter re-wrapped two of its
statements, so "verbatim" overstated it. The module docstring also names
the actor for regs 12 and 14 and for Schedule 4 para 9(2)(f).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply the voice review to the documentation of the condition, receiving
education, the reg 28 status, both new inputs and the changelog fragment.
The maintenance input no longer says education_grants holds childcare or
disability grants: the FRS build allocates those to their own variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewords the YAML case comments and the property file's docstrings so that
DWP, the decision maker or the Secretary of State acts under each
provision, and replaces be-verb identities with verbs. No test input,
expected value or executable line changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Case 6 names the reg. 14(1)(c) exception, case 26 puts reg. 60(1) first,
case 28 says reg. 66 doesn't list Armed Forces Independence Payment, case
30 states when reg. 12(2)(b) treats a claimant as receiving education, and
case 32 drops "no longer". Comments only.

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

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 1d4d020aa1 against head 4accdc3113, 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

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise Class 1 main employee NICs rate to 10% 0 0 0 0 0 suppressed suppressed
Raise additional rate by 3pp 0 0 0 0 0 suppressed suppressed

The report suppresses a reform's costing and target change in these years, for the reasons below (privacy.py):

  • Raise Class 1 main employee NICs rate to 10%, 2030-31 to 2031-32 (base and head): the reform changes income_tax in fewer than 10 records.
  • Raise additional rate by 3pp, 2030-31 to 2031-32 (base and head): the reform changes universal_credit in fewer than 10 records.

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise Class 1 main employee NICs rate to 10%: change in national_insurance 0 0 0 0 0 suppressed suppressed
Raise additional rate by 3pp: change in income_tax 0 0 0 0 0 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.

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 1d4d020aa1 uk 2.172.1 (files 27900fb6fb5f), core 3.34.1, py 3.13.18 aarch64 f421d4d66ebc 7cb61324a46b 2030.12s
head #2806 merged into main 4accdc3113 uk 2.172.1 (files e5b9a2bf09f9), core 3.34.1, py 3.13.18 aarch64 f421d4d66ebc 7cb61324a46b 2010.62s

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 £400.00bn
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.49bn £132.99bn £136.66bn £140.42bn
Pension Credit £6.66bn £7.03bn £7.29bn £7.48bn £7.60bn £7.84bn £8.03bn
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 £80.52bn £82.95bn £84.96bn £86.11bn £87.61bn £88.21bn
Housing Benefit £8.56bn £8.89bn £9.23bn £9.46bn £9.69bn £9.99bn £10.27bn
Council Tax Reduction £2.07bn £2.20bn £2.31bn £2.45bn £2.58bn £2.72bn £2.86bn
Employment and Support Allowance (income-related) £0.79bn £0.82bn £0.84bn £0.86bn £0.88bn £0.90bn £0.92bn
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,486k 1,480k 1,472k 1,452k 1,450k 1,455k 1,464k
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,347k 6,336k 6,297k 6,292k 6,271k 6,223k
Housing Benefit 1,249k 1,245k 1,244k 1,227k 1,227k 1,229k 1,232k
Council Tax Reduction 3,176k 3,195k 3,200k 3,214k 3,224k 3,235k 3,242k
Employment and Support Allowance (income-related) 71k 71k 71k 71k 71k 71k 71k
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 £529.38bn † £545.66bn † £550.77bn † £559.51bn † £567.20bn † £575.95bn † £583.60bn †
Government balance, each receipt once £362.62bn † £389.00bn † £429.74bn † £456.42bn † £487.04bn † £515.61bn † £541.29bn †
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.01bn £601.00bn £626.70bn £648.42bn
pre_budget_change_household_tax £484.15bn £512.28bn £542.82bn £566.95bn £591.72bn £617.16bn £638.61bn
gov_spending £529.38bn † £545.66bn † £550.77bn † £559.51bn † £567.20bn † £575.95bn † £583.60bn †
household_benefits £529.38bn † £545.66bn † £550.77bn † £559.51bn † £567.20bn † £575.95bn † £583.60bn †
gov_balance £362.62bn † £389.00bn † £429.74bn † £456.42bn † £487.04bn † £515.61bn † £541.29bn †
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.69bn † £1,687.82bn † £1,715.45bn † £1,747.49bn † £1,778.69bn † £1,817.02bn † £1,859.69bn †
Household net income (HBAI definition) £1,449.57bn £1,499.83bn £1,530.86bn £1,565.17bn £1,597.89bn £1,635.60bn £1,676.61bn
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% 13.9% 13.9% 13.9%
Absolute poverty, after housing costs: everyone 21.6% 21.0% 22.0% 22.6% 22.9% 23.1% 23.4%
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.0% 13.9% 13.8% 13.8% 13.9% 13.9%
Relative poverty, after housing costs: everyone 21.4% 21.0% 21.3% 21.3% 21.3% 21.5% 21.7%
Relative poverty, after housing costs: children 33.1% 31.1% 31.2% 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.7% 11.7% 11.8% 12.0%
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,278k 10,779k 11,402k 11,365k 11,476k 11,513k 11,651k
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,893k 6,308k 6,362k 6,420k 6,430k 6,521k
Absolute poverty, before housing costs: pensioners 1,624k 1,572k 1,598k 1,558k 1,576k 1,580k 1,581k
Absolute poverty, after housing costs: everyone 14,944k 14,600k 15,310k 15,721k 15,937k 16,141k 16,426k
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,461k 8,971k 9,344k 9,491k 9,584k 9,706k
Absolute poverty, after housing costs: pensioners 1,506k 1,457k 1,502k 1,490k 1,501k 1,517k 1,542k
Relative poverty, before housing costs: everyone 11,750k 11,084k 11,388k 11,364k 11,416k 11,415k 11,651k
Relative poverty, before housing costs: children 3,872k 3,423k 3,496k 3,445k 3,466k 3,454k 3,549k
Relative poverty, before housing costs: working-age adults 6,159k 6,013k 6,301k 6,362k 6,389k 6,389k 6,521k
Relative poverty, before housing costs: pensioners 1,720k 1,648k 1,590k 1,557k 1,562k 1,572k 1,581k
Relative poverty, after housing costs: everyone 14,849k 14,552k 14,776k 14,809k 14,874k 15,014k 15,234k
Relative poverty, after housing costs: children 4,942k 4,660k 4,673k 4,663k 4,679k 4,707k 4,806k
Relative poverty, after housing costs: working-age adults 8,426k 8,440k 8,711k 8,823k 8,873k 8,967k 9,062k
Relative poverty, after housing costs: pensioners 1,482k 1,452k 1,392k 1,323k 1,322k 1,339k 1,365k
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.3080 0.3052 0.3058 0.3058 0.3063 0.3069 0.3085
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 † £17,997 † £17,915 † £18,152 † £18,307 † £18,531 † £18,717 †
Decile 2: mean household_net_income per household £27,939 † £29,023 † £28,711 † £29,013 † £29,276 † £29,691 † £30,150 †
Decile 3: mean household_net_income per household £34,320 † £35,485 † £36,073 † £36,611 † £37,197 † £37,737 † £38,187 †
Decile 4: mean household_net_income per household £38,933 † £40,687 † £41,723 † £42,727 † £43,334 † £44,132 † £44,432 †
Decile 5: mean household_net_income per household £43,239 † £45,567 † £46,014 † £46,959 † £47,712 † £48,584 † £50,282 †
Decile 6: mean household_net_income per household £47,927 † £49,210 † £50,892 † £51,939 † £52,945 † £53,942 † £54,680 †
Decile 7: mean household_net_income per household £55,662 † £56,571 † £57,442 † £57,670 † £58,729 † £59,657 † £61,082 †
Decile 8: mean household_net_income per household £68,789 † £70,803 † £71,858 † £73,466 † £73,880 † £75,320 † £77,178 †
Decile 9: mean household_net_income per household £78,890 † £81,013 † £80,861 † £82,646 † £85,464 † £86,946 † £89,509 †
Decile 10: mean household_net_income per household £117,005 † £120,339 † £124,742 † £126,632 † £125,971 † £128,600 † £130,938 †
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.80bn £-3.92bn £-3.98bn £-4.04bn £-4.10bn £-4.23bn £-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 £-21.99bn £-22.34bn £-22.75bn £-22.94bn
Raise Class 1 main employee NICs rate to 10% £10.89bn £11.30bn £11.71bn £12.01bn £12.31bn suppressed suppressed
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 suppressed suppressed
Raise the Universal Credit standard allowance by 10% £-3.06bn £-3.18bn £-3.26bn £-3.35bn £-3.44bn £-3.49bn £-3.50bn
Raise the State Pension by 10% £-7.78bn £-8.01bn £-8.24bn £-8.47bn £-8.82bn £-8.55bn £-8.89bn
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.78bn £22.04bn £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 suppressed suppressed
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 suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.06bn £3.19bn £3.28bn £3.37bn £3.43bn £3.49bn £3.51bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.17bn £11.28bn £11.60bn £12.03bn £12.47bn £12.97bn

@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 exact head 4d363920686117ae58ce23fa284918964ab86bbc. Review PARTIAL: no confirmed implementation finding, but policy checks remain incomplete, so this is not an approval.

All 164 selected tests passed, including final-UC cases for the full-time student exclusion, parent and partner exceptions, mixed-age couples and legacy-closure routes. The core GB and NI education provisions, DWP's couple guidance and 2026–27 standard rates were checked. The actual rulespec-uk#514 handoff also states its signed-encoder/corpus blockers.

Before completing the policy review, please provide or identify:

  • The operative Northern Ireland transitional provisions corresponding to GB SI 2014/1230 regs 28, 48 and 60, including their application dates. The NI education regulations alone do not establish managed-migration parity.
  • Source-backed temporal cases for the unversioned reg 60 protection, including the qualifying migration claim, course continuity and cessation. The annual closure proxy and the documented missing parental-support, LCW-date and placement facts do not establish every statutory outcome merely because represented-case tests pass.

The existing passing tests are retained; no broad suite was repeated during recovery. The declared input/data limitations remain explicit, and I am not treating them as verified legal entitlement rules.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

The change whichever of #2845 and #2806 lands second needs

#2845 (branch fix-2804-reg-28-qyp, head d5d32074d569331db3cda217701cdee5ed152d10) and #2806 (fix-2346-uc-education, head 4d363920686117ae58ce23fa284918964ab86bbc) merge cleanly. #2806's last push merged main into its branch and left the three files below unchanged from a5d1662666253304de5d3245903f097791b91950, so the diff applies at either head.

#2845 applies SI 2014/1230 reg. 28 in is_qualifying_young_person_for_universal_credit. Since the Secretary of State ties the exclusion to entitlement, that predicate reads an existing benefit report only in a year the model pays that benefit, and only if no reform neutralises it. #2806's is_qualifying_young_person_for_uc_education applies the exclusion again from the raw reports in every year, so in a year after a benefit closes it disagrees with is_qualifying_young_person_for_universal_credit. The change makes it return that variable, and touches three of #2806's files, none of which #2845 changes:

  • policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py: the formula returns is_qualifying_young_person_for_universal_credit. In the docstring, "It equals is_qualifying_young_person_for_universal_credit, which applies reg. 28." replaces the sentence that began "The model reads entitlement from the year's reported receipt". The docstring also drops two clauses the change makes false: the opening "they meet … and have no entitlement to an existing benefit", and the last sentence, "The model's other rules read is_qualifying_young_person_for_universal_credit, which leaves reg. 28 out." The unused EXISTING_BENEFITS and TAX_CREDITS constants go.
  • policyengine_uk/tests/test_uc_receiving_education_properties.py: the oracle (reference) and property 8 read only the reports paying_reports returns: those of benefits the model pays in the year (gov.dwp.income_support.active, gov.dwp.JSA.income.active, gov.dwp.tax_credits.active; the model holds no closure date for Housing Benefit or income-related ESA) whose variables no reform neutralises (income_support, jsa_income, esa_income, housing_benefit, child_tax_credit or tax_credits, working_tax_credit or tax_credits). The file runs under the with_main_eligibility reform, which neutralises none of them, so the neutralisation check reads the simulation's own system. Line 701 (test_examples_reach_the_cases, people 9 and 10) expects [False, False].
  • policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml: cases 31 (line 824) and 34 (line 908) expect is_qualifying_young_person_for_universal_credit: [false], and case 33 (line 879) [false, false]. Cases 31 and 34 rest on Housing Benefit in 2026 and case 33 on Working Tax Credit in 2024, all in years the model pays them, so the predicate excludes each.

Test runs on a tree that merges both heads

uv sync --frozen --extra dev:

Run Result
uc_receiving_education.yaml, no change 3 failed, 32 passed (cases 31, 33, 34)
uc_receiving_education.yaml, with the change 35 passed
test_uc_receiving_education_properties.py, with the change 3 passed (89.6 s)
the same file with the variable change and line 701, but without the oracle filter 1 failed, 2 passed: a claimant reporting working_tax_credit_reported: 1000 in 2026 falsifies test_education_condition_invariants
#2845's three YAML files 17/17, 23/23, 13/13

On the merged tree with the change, we ran a single claimant aged 18 on an upper-secondary course (North East, owned outright) who reports £3,000 of one benefit:

Report UC predicate #2806 variable uc_receiving_education UC
Income Support, 2026 true true false £4,062.96
income-based JSA, 2026 true true false £4,062.96
Child Tax Credit, 2025 true true false £3,803.76
Income Support, 2025 false false false £3,803.76

The two reg. 28 readings now agree in every row; before the change, #2806's variable gave false in the first three.

Diff against #2806's head (4d363920)

diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml
index d97b8b71..a3e5c0b5 100644
--- a/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml
+++ b/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml
@@ -821,7 +821,7 @@
         members: [person1]
         region: NORTH_EAST
   output:
-    is_qualifying_young_person_for_universal_credit: [true]
+    is_qualifying_young_person_for_universal_credit: [false]
     is_qualifying_young_person_for_uc_education: [false]
     uc_receiving_education: [false]
     is_uc_eligible: true
@@ -876,7 +876,7 @@
         members: [person1, person2]
         region: NORTH_EAST
   output:
-    is_qualifying_young_person_for_universal_credit: [true, true]
+    is_qualifying_young_person_for_universal_credit: [false, false]
     is_qualifying_young_person_for_uc_education: [false, false]
     uc_receiving_education: [false, false]
 
@@ -905,7 +905,7 @@
         members: [person1]
         region: NORTH_EAST
   output:
-    is_qualifying_young_person_for_universal_credit: [true]
+    is_qualifying_young_person_for_universal_credit: [false]
     is_qualifying_young_person_for_uc_education: [false]
     uc_receiving_education: [true]
     uc_is_excepted_from_education_condition: [true]
diff --git a/policyengine_uk/tests/test_uc_receiving_education_properties.py b/policyengine_uk/tests/test_uc_receiving_education_properties.py
index df051277..a22d9117 100644
--- a/policyengine_uk/tests/test_uc_receiving_education_properties.py
+++ b/policyengine_uk/tests/test_uc_receiving_education_properties.py
@@ -88,16 +88,25 @@ PROPERTY_SETTINGS = settings(
 YEARS = [2019, 2021, 2024, 2026]
 REGIONS = ["NORTH_EAST", "LONDON", "SCOTLAND", "NORTHERN_IRELAND"]
 EDUCATION = ["TERTIARY", "POST_SECONDARY", "NOT_IN_EDUCATION"]
-# SI 2014/1230 reg. 2(1), as reported receipt.
-EXISTING_BENEFITS = [
-    "jsa_income_reported",
-    "esa_income_reported",
-    "income_support_reported",
-    "housing_benefit_reported",
-    "child_tax_credit_reported",
-    "working_tax_credit_reported",
-]
 TAX_CREDITS = ["child_tax_credit_reported", "working_tax_credit_reported"]
+# SI 2014/1230 reg. 2(1), as reported receipt. Reg. 28 turns on entitlement,
+# so a report counts only in a year the model pays that benefit (the
+# parameter, or None where the model has no closure date) and only if no
+# reform neutralises one of its variables.
+REPORT_BENEFITS = {
+    "jsa_income_reported": ("JSA.income.active", ["jsa_income"]),
+    "esa_income_reported": (None, ["esa_income"]),
+    "income_support_reported": ("income_support.active", ["income_support"]),
+    "housing_benefit_reported": (None, ["housing_benefit"]),
+    "child_tax_credit_reported": (
+        "tax_credits.active",
+        ["child_tax_credit", "tax_credits"],
+    ),
+    "working_tax_credit_reported": (
+        "tax_credits.active",
+        ["working_tax_credit", "tax_credits"],
+    ),
+}
 # Compare eligibility, maximum entitlement, income reduction and Universal
 # Credit awards.
 OUTPUTS = [
@@ -289,6 +298,23 @@ DRAWN = (None, False)
 BEFORE = (None, True)
 
 
+def paying_reports(simulation, year):
+    """The existing benefit reports reg. 28 reads in the year: those of
+    benefits the model pays then and no reform neutralises."""
+    system = simulation.tax_benefit_system
+    dwp = system.parameters(f"{year}-01-01").gov.dwp
+    paying = []
+    for report, (active, variables) in REPORT_BENEFITS.items():
+        node = dwp
+        for key in active.split(".") if active else []:
+            node = getattr(node, key)
+        if (active is None or bool(node)) and not any(
+            system.get_variable(v).is_neutralized for v in variables
+        ):
+            paying.append(report)
+    return paying
+
+
 def run(case, variants=(DRAWN,)):
     """One simulation of every variant; returns one result per variant."""
     simulation = Simulation(situation=situation(case, variants))
@@ -301,6 +327,7 @@ def run(case, variants=(DRAWN,)):
         np.array([uses_main for _, uses_main in variants for _ in case["families"]]),
     )
     values = {name: simulation.calculate(name, year) for name in VARIABLES}
+    paying = paying_reports(simulation, year)
     limit = simulation.tax_benefit_system.parameters(
         f"{year}-01-01"
     ).gov.dwp.universal_credit.means_test.capital.limit
@@ -311,7 +338,7 @@ def run(case, variants=(DRAWN,)):
     }
     results = []
     for v in range(len(variants)):
-        result = {"capital_limit": limit}
+        result = {"capital_limit": limit, "paying": paying}
         for name, array in values.items():
             entity = simulation.tax_benefit_system.variables[name].entity.key
             size = sizes[entity]
@@ -340,13 +367,14 @@ def reference(case, result):
         members = range(start, start + len(fam["people"]))
         person_of = dict(zip(members, fam["people"]))
         couple = [i for i in members if result["is_uc_assessed_claimant"][i]]
+        tax_credits = [name for name in TAX_CREDITS if name in result["paying"]]
         couple_tax_credits = any(
-            person_of[j].get(name, 0) > 0 for j in couple for name in TAX_CREDITS
+            person_of[j].get(name, 0) > 0 for j in couple for name in tax_credits
         )
         qyp, recv = {}, {}
         for i, person in person_of.items():
             existing_benefit = any(
-                person.get(name, 0) > 0 for name in EXISTING_BENEFITS
+                person.get(name, 0) > 0 for name in result["paying"]
             ) or (i in couple and couple_tax_credits)
             qyp[i] = (
                 bool(result["is_qualifying_young_person_for_universal_credit"][i])
@@ -650,11 +678,12 @@ def check(case):
             atol=0.005,
             err_msg=f"{name}: {context}",
         )
-    # 8. Reg. 28: no one who reports an existing benefit counts as a
-    # qualifying young person for the condition.
+    # 8. Reg. 28: no one who reports an existing benefit the model pays that
+    # year, and no reform neutralises, counts as a qualifying young person
+    # for the condition.
     reports = np.array(
         [
-            any(p.get(name, 0) > 0 for name in EXISTING_BENEFITS)
+            any(p.get(name, 0) > 0 for name in drawn["paying"])
             for fam in case["families"]
             for p in fam["people"]
         ]
@@ -698,7 +727,7 @@ def test_examples_reach_the_cases():
     # People 9 and 10: the young Housing Benefit claimants.
     young = slice(9, 11)
     np.testing.assert_array_equal(
-        drawn["is_qualifying_young_person_for_universal_credit"][young], [True, True]
+        drawn["is_qualifying_young_person_for_universal_credit"][young], [False, False]
     )
     np.testing.assert_array_equal(
         drawn["is_qualifying_young_person_for_uc_education"][young], [False, False]
diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py b/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py
index 4f124e88..e982b8da 100644
--- a/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py
+++ b/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py
@@ -1,16 +1,5 @@
 from policyengine_uk.model_api import *
 
-# Universal Credit (Transitional Provisions) Regulations 2014 reg. 2(1).
-EXISTING_BENEFITS = [
-    "jsa_income_reported",
-    "esa_income_reported",
-    "income_support_reported",
-    "housing_benefit_reported",
-    "child_tax_credit_reported",
-    "working_tax_credit_reported",
-]
-TAX_CREDITS = ["child_tax_credit_reported", "working_tax_credit_reported"]
-
 
 class is_qualifying_young_person_for_uc_education(Variable):
     value_type = bool
@@ -19,21 +8,16 @@ class is_qualifying_young_person_for_uc_education(Variable):
     documentation = (
         "Whether this person counts as a qualifying young person for the "
         "Universal Credit condition that a claimant is not receiving "
-        "education: they meet is_qualifying_young_person_for_universal_credit "
-        "and have no entitlement to an existing benefit. Under the Universal "
-        "Credit (Transitional Provisions) Regulations 2014 reg. 28, a person "
-        "entitled to an existing benefit doesn't count as a qualifying young "
-        "person, and the Secretary of State lists the existing benefits in "
-        "reg. 2(1): income-based Jobseeker's Allowance, income-related "
-        "Employment and Support Allowance, Income Support, Housing Benefit, "
-        "Child Tax Credit and Working Tax Credit. The model reads entitlement "
-        "from the year's reported receipt: the person's own report of any of "
-        "them, or, for a claimant or partner, either member's report of tax "
-        "credits, which a couple claims jointly. A person outside the "
-        "definition under reg. 28 takes the ordinary route under the Universal "
-        "Credit Regulations 2013 reg. 12(2). The model's other rules read "
-        "is_qualifying_young_person_for_universal_credit, which leaves reg. 28 "
-        "out."
+        "education. Under the Universal Credit (Transitional Provisions) "
+        "Regulations 2014 reg. 28, a person entitled to an existing benefit "
+        "doesn't count as a qualifying young person, and the Secretary of "
+        "State lists the existing benefits in reg. 2(1): income-based "
+        "Jobseeker's Allowance, income-related Employment and Support "
+        "Allowance, Income Support, Housing Benefit, Child Tax Credit and "
+        "Working Tax Credit. It equals "
+        "is_qualifying_young_person_for_universal_credit, which applies reg. "
+        "28. A person outside the definition under reg. 28 takes the ordinary "
+        "route under the Universal Credit Regulations 2013 reg. 12(2)."
     )
     definition_period = YEAR
     reference = (
@@ -44,19 +28,4 @@ class is_qualifying_young_person_for_uc_education(Variable):
     )
 
     def formula(person, period, parameters):
-        qualifying_young_person = person(
-            "is_qualifying_young_person_for_universal_credit", period
-        )
-        # SI 2014/1230 reg. 28: where a person who would be a qualifying
-        # young person "is entitled to an existing benefit", "that person is
-        # not a qualifying young person for the purposes of the Universal
-        # Credit Regulations".
-        own_award = add(person, period, EXISTING_BENEFITS) > 0
-        # Tax Credits Act 2002 s. 3(3)(a): the members of a couple claim a
-        # tax credit jointly, so the award belongs to both.
-        claimant_or_partner = person("is_claimant_or_partner", period)
-        joint_tax_credits = claimant_or_partner & (
-            person.benunit.sum(claimant_or_partner * add(person, period, TAX_CREDITS))
-            > 0
-        )
-        return qualifying_young_person & ~(own_award | joint_tax_credits)
+        return person("is_qualifying_young_person_for_universal_credit", period)

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Thanks. These are the Northern Ireland provisions you asked for, with their dates from legislation.gov.uk:

GB, SI 2014/1230 NI, SR 2016/226 In operation in NI
reg 28, meaning of "qualifying young person" reg 29 From 27 September 2017. SR 2016/226 came into operation immediately after the NI Universal Credit Regulations (reg 1), which came into operation when Article 6 of the Welfare Reform (NI) Order 2015 first came into force for any part of NI (their reg 1). Article 6 first came into force for claims made from 27 September 2017 in the first districts (SR 2017/190 art 4(3) to (5) and Schedule 1).
reg 48, meaning of "qualifying claim" reg 49 From 24 July 2019, when Part 4 came in (SR 2019/152).
reg 60, protection for full-time students reg 61 From 24 July 2019 (SR 2019/152). Paragraph (2) applies from 1 August 2022 (SR 2022/194 reg 11). From 27 January 2025, paragraph (1) turns on a person who has made a qualifying claim and on the migration day, in place of a notified person and the day the existing awards end (SR 2025/3 reg 6(9)).

The NI text matches the GB text, with NI cross-references: Article 9(1)(d) of the 2015 Order for section 4(1)(d) of the 2012 Act, reg 6 of the NI Universal Credit Regulations for reg 5, reg 47(4) for reg 46(4), and regs 57 and 58 for regs 56 and 57. So the model's GB routes carry over to NI unchanged.

The model protects a student only where claims_uc_at_legacy_closure is true, and legacy_benefits_closed turns true only once a reported legacy benefit closes. In the active parameters, tax credits close from 6 April 2025, and Income Support and income-based JSA from 1 April 2026. So the protection applies only in model years from 2025-26, under the text in force from 27 January 2025, in GB and NI alike. It never applies reg 60(2) or 61(2), as the variable's documentation says.

The next push adds the NI references to the three variables, plus temporal YAML cases worked from the law by hand:

  • a migrated full-time student in the closure year;
  • the same student the next year on the same course;
  • the same person after the course ends;
  • a family that doesn't claim by the deadline;
  • a family that claimed Universal Credit before the closure.

The body will say where the annual proxy departs from the law, 60(2) included.

@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.

Source follow-up at unchanged head 4d363920686117ae58ce23fa284918964ab86bbc: review COMPLETE, with no findings or material evidence gaps in this bounded review. The prior NI and dated-GB source questions are resolved.

I independently checked the NI counterparts, including reg 29, reg 49 and reg 61, their commencement/amendment chain, and the dated historical GB reg 60 text. The applicable NI provisions substantively support the reviewed GB premises. The actual Axiom #514 record remains valid for the queued work.

One qualification to the reply: the claim that protection is reachable only from 2025 applies to default-derived closures. The public input contract also accepts explicit historical closure flags, and the existing properties use them in 2019/2021/2024. Please preserve that distinction in the planned wording.

The body already discloses the annual proxy's course/history/cessation limitations. With the operative law now verified, those known limits and the absence of the five proposed additional tests do not leave a material source question unresolved. This review validates the stated annual model contract; it does not certify exact longitudinal statutory outcomes or the promised next push.

The prior 164 passing checks are retained at the identical head/body/tree; no model tests were repeated. Current exact-head CI passes. This remains a draft, so I am posting the completed review without approving it yet.

@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

…and 60

Add SR 2016/226 regs 29, 49 and 61 to the references of the education
condition variables, with GB reg 48, and name the Northern Ireland
regulation beside each GB one in their documentation. No formula changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WR8LHfDXcSTTbY2JgCMXBk
Cases 36 to 41 follow one full-time student through the closure of tax
credits: the claim at closure in 2025, the same course in 2026, the end of
the course in 2027, a claim outside the migration, a claim before the
closure in 2024, and the Northern Ireland protection under SR 2016/226 reg
61. Each case works its result from SI 2014/1230 regs 44 to 60 and DWP's
rates, and none sets claims_uc_at_legacy_closure, so the closure runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WR8LHfDXcSTTbY2JgCMXBk
For single people aged 25 to 59 with no other exception, in model years
2020 to 2028, uc_is_excepted_from_education_condition holds exactly when
they report a legacy benefit whose active parameter reads false and their
family claims at closure. The test reads the closure dates from the
parameters and batches each example into one simulation, with explicit
grids for 2024, 2025 and 2026.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WR8LHfDXcSTTbY2JgCMXBk
Ground each case comment in the regulation text: the notice DWP issued,
the claim after the final deadline in case 39, the text in force before
27 January 2025 in case 40, the minimum income floor in case 37, and the
end of the course in case 38. Name the Universal Credit Regulations 2013
reg 5 and the 2014 Regulations' reg 48 in the documentation. Put every
grid combination in North East England and in Northern Ireland, scope the
property's invariants to closures the model derives, describe the 30 April
parameter snapshot, and check that the grid years straddle each closure.

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

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Apply the Universal Credit education condition

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

Fixes #2346

## What and why

Universal Credit has a basic condition that the claimant "is not receiving education" ([Welfare Reform Act 2012 s.4(1)(d)](https://www.legislation.gov.uk/ukpga/2012/5/section/4)). In the UC Regs 2013, the Secretary of State sets out who receives education ([reg 12](https://www.legislation.gov.uk/uksi/2013/376/regulation/12)), when a person undertakes a course ([reg 13](https://www.legislation.gov.uk/uksi/2013/376/regulation/13)) and who doesn't have to meet the condition ([reg 14(1)](https://www.legislation.gov.uk/uksi/2013/376/regulation/14)). `is_uc_eligible` on `main` read no education condition, so the model paid a full-time student who meets no exception. The issue's 19-year-old university student in Plymouth gets £6,348.91 on `main` at 2da212abc, the figure the issue reports.

This PR adds:

- **`is_in_full_time_education`** (Person, year), an input that stays false until a user or dataset sets it: whether the person takes a full-time course of study or training. The model reads the level from `current_education` and `in_HE`, and no formula derives the input from them. #2311 needs the same fact for Carer's Allowance and can read it; this PR doesn't change Carer's Allowance.
- **`has_education_maintenance_funding`** (Person, year), a second input that also stays false until a user or dataset sets it: whether the person has a student loan or grant for their maintenance on the course, the test in reg 12(2)(b). The model doesn't read `student_loans` or `education_grants` for it, since it can't tell from either amount what the support pays for. A learner aged 19 or over on a further education course can get [Learner Support](https://www.gov.uk/learner-support) for travel, accommodation, materials and equipment, a laptop or childcare, and only support for their maintenance meets reg 12(2)(b). In case 30, a student on a full-time course that doesn't lead to advanced education, with a £100 equipment grant, a £3,000 student loan and no maintenance funding, keeps an award of £5,098.80.
- **`is_qualifying_young_person_for_uc_education`**: the qualifying young person status the model reads for the condition. Under [SI 2014/1230 reg 28](https://www.legislation.gov.uk/uksi/2014/1230/regulation/28), a person entitled to an existing benefit doesn't count as a qualifying young person for the UC Regs. The Secretary of State lists the existing benefits in [reg 2(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/2): income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit. The model reads entitlement from the year's reported receipt, the evidence it reads for legacy closure. A claimant or partner on a couple's tax credit award counts as entitled, since the couple claims jointly ([Tax Credits Act 2002 s.3(3)(a)](https://www.legislation.gov.uk/ukpga/2002/21/section/3)). A person outside the definition under reg 28 takes the ordinary route under reg 12(2). This PR leaves `is_qualifying_young_person_for_universal_credit` as it stands: it doesn't apply reg 28, and the model still reads it for the child element, the benefit cap and Council Tax Reduction. #2804 tracks the same rule in `is_qualifying_young_person_for_universal_credit`.
- **`uc_receiving_education`** (reg 12). A person on a full-time course receives education where:
  - they take a course of advanced education under reg 12(2)(a) and (3), which the model reads from tertiary `current_education` or `in_HE`;
  - they have a student loan or grant for their maintenance under reg 12(2)(b), which the model reads from `has_education_maintenance_funding`;
  - or they count as a qualifying young person after reg 28, whom the decision maker treats as receiving education under reg 12(1A).
- **`uc_is_excepted_from_education_condition`** (reg 14(1), and [SI 2014/1230 reg 60](https://www.legislation.gov.uk/uksi/2014/1230/regulation/60)), with one sourced condition for each limb the model can express:
  - (b): entitlement to Attendance Allowance (including Armed Forces Independence Payment, which DWP counts as Attendance Allowance under reg 2), DLA or PIP, with limited capability for work (`uc_limited_capability_for_work`);
  - (c): responsibility for a child or qualifying young person in the benefit unit;
  - (d): a single foster parent, whom the model identifies from `uc_is_responsible_foster_parent_of_child_under_one` or `uc_is_foster_parent_or_new_friend_or_family_carer`;
  - (e)(ii): a member of a couple, both of whom receive education, whose partner fosters a child;
  - (f): the older member of a mixed-age couple;
  - SI 2014/1230 reg 60(1): a member of a family that claims when DWP moves it off its legacy benefits (`claims_uc_at_legacy_closure`), which the model takes as the qualifying claim under [reg 48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48). A person who receives education under reg 12(2) gets the protection; a qualifying young person under reg 12(1A) doesn't, while a person outside the definition under reg 28 takes reg 12(2) and gets it.
  - The model needs no rule of its own for limb (e)(i): it places responsibility for a child on the benefit unit, so both members meet (c).
- **`meets_uc_education_condition`** (s.4(1)(d)): not receiving education, or having an exception.
- **`is_uc_eligible`** needs one claimant (`is_uc_single_or_joint_claimant`) to meet the age condition and the education condition together, on every route.

### The couple rule

Under [reg 3(2)](https://www.legislation.gov.uk/uksi/2013/376/regulation/3), a couple can claim jointly where one member has reached the qualifying age for State Pension Credit "if the other member does meet that condition", or where one member receives education and has no exception "if the other member does meet that condition or is excepted from it". In [ADM E2016](https://assets.publishing.service.gov.uk/media/6a0474b6ee62840dba48a1eb/adme2.pdf), DWP accepts a joint claim where one member meets all the basic conditions. Under reg 14(1)(f), an older student can claim jointly with a younger partner who meets them. The model follows E2016, so one claimant must meet the age and education conditions together. A member of a couple who claims as a single person under reg 3(3) has no joint claimant beside them, so they must meet both conditions themselves.

For a couple aged 70 and 60 with no income in 2026-27, the model gives these awards:

| Who studies full-time | Award | Why |
|---|---:|---|
| Neither | £8,003.64 | The 60-year-old meets every basic condition (reg 3(2)(a)) |
| The 70-year-old | £8,003.64 | The 70-year-old has the reg 14(1)(f) exception, and the 60-year-old meets both conditions (reg 3(2)(a)) |
| The 60-year-old | nil | The 70-year-old meets the education condition and the 60-year-old the age condition, but neither meets both (ADM E2016) |
| Both | nil | The 70-year-old has the reg 14(1)(f) exception but still doesn't meet the age condition, so again neither meets both |

Each positive award comes to 12 × £666.97 = £8,003.64, DWP's joint rate where one or both claimants are 25 or over, from [Benefit and pension rates 2026 to 2027](https://www.gov.uk/government/publications/benefit-and-pension-rates-2026-to-2027/proposed-benefit-and-pension-rates-2026-to-2027). Since 15 May 2019 the couple can't claim Pension Credit instead (SPCA 2002 s.4(1A)).

The model keeps the condition on every route into `is_uc_eligible`:

| Route, 2026-27 unless stated | Neither studies | Older studies | Younger studies | Both study |
|---|---:|---:|---:|---:|
| Ordinary: aged 70 and 60 | £8,003.64 | £8,003.64 | nil | nil |
| Closure claim: aged 70 and 60, and under reg 60(1) neither member has to meet the condition | £8,003.64 | £8,003.64 | £8,003.64 | £8,003.64 |
| Reg 60A: aged 72 and 70 on Working Tax Credit, with a closure claim | £8,003.64 | £8,003.64 | £8,003.64 | £8,003.64 |
| Aged 72 and 70 on Working Tax Credit, no closure claim: the Pension Credit route | nil | nil | nil | nil |
| Pension Credit route: aged 70 and 60 in 2019-20, before 15 May 2019 | nil | nil | nil | nil |
| The same, with a closure claim: 12 × £498.89 | £5,986.68 | £5,986.68 | £5,986.68 | £5,986.68 |

For a couple aged 30 where one member takes a full-time degree and the other doesn't, the pair claim jointly at the couple rate, £8,003.64 a year with no income. Under reg 68, DWP counts a student's loan and some grants as student income, after statutory exclusions and disregards. The model leaves `student_loans` and `education_grants` out of its Universal Credit unearned income list (`gov.dwp.universal_credit.means_test.income_definitions.unearned`), so it counts no student income and the couple gets the full joint amount. If both members study and neither has an exception, the couple gets nothing.

### Young people on legacy benefits

An 18-year-old on a full-time course that doesn't lead to advanced education, who reports Working Tax Credit or Housing Benefit, keeps the award they get on `main`. Under reg 28 they don't count as qualifying young people, and with no maintenance funding they don't receive education under reg 12(2). One with maintenance funding receives education under reg 12(2)(b), and on a closure claim keeps the award under reg 60(1) (case 34).

| 18-year-old, social rent £6,000, claims when DWP moves them off legacy benefits, 2026-27 | `main` | This PR | Arithmetic |
|---|---:|---:|---|
| Reports Working Tax Credit of £1,000 and earns £9,000 | £5,112.96 | £5,112.96 | 12 × £338.58 + £6,000 − 55% × £9,000 |
| Reports Housing Benefit of £6,000, with Housing Benefit awards closed | £10,062.96 | £10,062.96 | 12 × £338.58 + £6,000 |

### Limited capability for work

Under reg 14(1)(b), for a claim on or after 15 December 2021, the decision maker must determine limited capability for work (LCW) before the course starts. The model reads `uc_limited_capability_for_work` and holds no other LCW fact. The model can derive the LCW variable from disability status without an assessment, so it can keep students who have never had an LCW determination, as well as students with an assessment after their course began. A full-time student aged 30 on the standard daily living rate of PIP, with `is_disabled_for_benefits` true and no limited capability for work and work-related activity, gets £5,098.80 (12 × £424.90) in the model. The model doesn't infer LCW from an ESA award. A user who wants an ESA assessment to count sets `uc_limited_capability_for_work` or `uc_limited_capability_for_WRA`, and a user or dataset can set `is_disabled_for_benefits`, from which the model derives LCW.

### What the model can't express

The model can't express the parts of regs 12 to 14 and SI 2014/1230 regs 28 and 60 below.

- **Reg 14(1)(a)**: a person under 21, or 21 having reached that age during the course, on a full-time course that doesn't lead to advanced education, without parental support (reg 8(3)). The model has no input for parental support. Where a user flags such a student and they count as a qualifying young person, or have maintenance funding, the model gives nil where DWP must pay the award. Otherwise the model doesn't treat them as receiving education, and they keep the award.
- **Reg 14(1)(b) timing**: for a claim on or after 15 December 2021, the decision maker must determine LCW before the course starts (SI 2021/1224). For a claim from 5 August 2020 to 14 December 2021, SI 2020/827 required the determination by the date of claim, or by the course start where the course starts after the claim. The model reads any LCW, so a student with a late assessment keeps the award, where DWP must pay nil.
- **Reg 14(1)(b) benefits**: child disability payment, adult disability payment, pension age disability payment, Scottish adult disability living allowance, and the constant attendance and war pension payments that DWP counts as attendance allowance under reg 2. The model has no variables for them. A student whose one listed benefit is among these gets nil, where DWP must pay the award.
- **Foster parents (reg 14(1)(d) and (e)(ii))**: if a user sets neither input for a foster parent, the model gives nil where DWP must pay the award. A user can also set the second input for a foster parent between placements, who keeps the award in the model, where DWP must pay nil.
- **Reg 12(1A)**: if a user doesn't set the input for a qualifying young person, the model doesn't treat them as receiving education, and they keep the award. A flagged one in a relevant training scheme (reg 12(1B)) gets nil, where DWP must pay the award. #2279 adds a group to this gap: under reg 5(1)(a), it counts a 16-year-old before the 1 September after their 16th birthday as a qualifying young person whatever their education. With both PRs merged, a 16-year-old carer outside education who claims alone with no income in 2026-27 counts as a qualifying young person, doesn't receive education in the model and gets £6,575.04, where DWP must pay nil under reg 12(1A). The author of whichever PR lands second must apply reg 12(1A) to that route without the full-time flag and add the 16-year-old carer case as a regression test.
- **Reg 12(4)**: the Secretary of State treats a claimant as receiving education when their course conflicts with a work-related requirement the Secretary of State imposes. The model keeps their award.
- **Reg 13(4)**: a person who leaves a course with the establishment's consent because of illness or caring, recovered or stopped caring within the past year, hasn't resumed and can't get a grant or loan doesn't undertake the course in that time. Set the input false only when all of these hold.
- **SI 2014/1230 reg 28**: the model reads the year's reported receipt of an existing benefit in place of entitlement on a given day. The model removes the status for the whole year when a person reports receipt for part of it, or reports a tax credit in 2025-26 or later although no award exists, and keeps the status for a person on a nil-rate award.

#### Where the annual closure proxy departs from reg 60

The model applies the [reg 60(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/60) protection to a claimant or partner who doesn't count as a qualifying young person, in a family that claims at closure (`claims_uc_at_legacy_closure`), in every model year in which that flag holds. Unless a user or dataset sets the flag, or `legacy_benefits_closed`, the model derives it from `legacy_benefits_closed` and `would_claim_uc_at_legacy_closure`, and takes the closure from the `active` parameters. Against reg 60, and SR 2016/226 reg 61 in Northern Ireland, the model departs in the ways below. Each gain or loss concerns a full-time student with no reg 14(1) exception whose family has no other claimant who meets the condition, such as the single claimant of cases 36 to 41.

- **Migration day.** Under reg 60(1), DWP lifts the condition for a student on the course on the migration day, the day before the Universal Credit award starts ([reg 49](https://www.legislation.gov.uk/uksi/2014/1230/regulation/49)). With the closure taken from the `active` parameters, the model protects a student only from the model year in which their reported benefit closes: 2025-26 for tax credits, 2026-27 for Income Support and income-based JSA. A tax credit family whose Universal Credit award started in 2024-25 has its migration day in that year, where, on the parameters alone, the model protects nobody (case 40). So for 2024-25 the model gives no Universal Credit to a student in that family whom DWP protects under reg 60(1). Likewise, on the parameters alone, the model gives no Universal Credit before 2026-27 to a student whom DWP protects under reg 60(1) after an earlier migration from Income Support or income-based JSA. The model also never applies the text in force before 27 January 2025, under which DWP applied reg 60(1) to a notified person on the day the existing awards were to end ([SI 2025/3 reg 6(9)](https://www.legislation.gov.uk/uksi/2025/3/regulation/6/made)). Where a user or dataset sets `claims_uc_at_legacy_closure` or `legacy_benefits_closed` for a year before 2025-26, as `test_education_condition_invariants` does in 2019, 2021 and 2024, the model protects every claimant and partner in the family, where DWP then protected only a notified person, so a partner whom DWP never notified gains.
- **The same course.** Under reg 60(1), DWP lifts the condition only "while they are continuing to undertake that course", the course the student undertook on the migration day. The model reads `is_in_full_time_education` year by year, keeps no record of which course a student takes, and keeps the exception whatever the course, or none (case 38). A student who finishes that course and starts another full-time course keeps the award in the model, where DWP applies the condition again and pays nil. So does a claimant who wasn't on a full-time course on the migration day and starts one later, to whom DWP never applies reg 60(1).
- **Reg 60(2)** (NI reg 61(2)), which the model never applies. Under it, DWP (in Northern Ireland, the Department for Communities) stops the protection in any assessment period in which a transitional element would have stopped under [reg 56](https://www.legislation.gov.uk/uksi/2014/1230/regulation/56) or [reg 57](https://www.legislation.gov.uk/uksi/2014/1230/regulation/57) (NI regs 57 and 58), and under reg 56(1) in every later one:
  - from the assessment period after three in a row of earned income below the administrative earnings threshold, where the first assessment period's earned income reached it, counting a claimant on the minimum income floor as reaching it (reg 56(2) and (3));
  - for an award under reg 60A (NI reg 61A), from an assessment period after the first 12 that follows three in a row of earned income below 16 hours a week at the national minimum wage (reg 56(3A));
  - when a single claimant joins a couple, unless they can still claim as a single person under UC Regs reg 3(3) (NI UC Regs reg 3(2)), or when joint claimants separate or join another couple (reg 56(4));
  - once the award ends, unless earnings ended it and a new award starts within three months (reg 57).

  The model keeps the student's exception through each, so the student keeps the award in the model where DWP applies the condition again. Case 37 works through these events for a claimant with no earnings, who stays single and keeps the award.
- **The claim.** The model has no deadline day. It treats a claim at closure (`claims_uc_at_legacy_closure`, which it derives from `legacy_benefits_closed` and `would_claim_uc_at_legacy_closure` unless a user or dataset sets it) as the qualifying claim under [reg 48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48), and any other claim (`would_claim_uc` alone) as a claim outside the migration, as cases 36 and 39 show. A family that claimed of its own accord by the final deadline after its migration notice made a qualifying claim, so where a user or dataset sets `would_claim_uc_at_legacy_closure` false for it, its student loses the award in the model, where DWP pays it. A family that claimed before DWP issued it a migration notice didn't claim as a notified person (reg 44(6)), so its claim doesn't count as a qualifying claim (reg 48). Yet if it still reports the closed benefit and keeps the default `would_claim_uc_at_legacy_closure`, its student gains the award in the model, where DWP pays nil. Nor does the model check that DWP issued a migration notice to both joint claimants (reg 48), so a couple whose second member never got one gains the protection.
- **Reported receipt.** As for reg 28, the model reads the year's report in place of entitlement to an existing benefit on a given day (reg 44(1)), and it keeps reading a legacy benefit report after the award closed. With the flag derived, a student who reports a closed benefit for part of a year counts as migrated for the whole year, and gains the award for the months before their migration day (reg 49). A student whose report stops in a later year, as the report of an award that ended would, loses the protection in the model from that year, where DWP keeps it while they continue the course (reg 60(1)). A student who doesn't report the award DWP moved them off gets no protection in the model. Cases 37 and 38 keep the report in 2026 and 2027 to stand for the award that closed, and a user or dataset that sets `claims_uc_at_legacy_closure` keeps the protection without a report.

### Northern Ireland

Claimants in Northern Ireland meet the same condition under the [Welfare Reform (Northern Ireland) Order 2015 art. 9(1)(d)](https://www.legislation.gov.uk/nisi/2015/2006/article/9). The Department for Communities made the Universal Credit Regulations (Northern Ireland) 2016 with GB reg 3(2) as their [reg 3(1)](https://www.legislation.gov.uk/nisr/2016/216/regulation/3), and GB [reg 13](https://www.legislation.gov.uk/nisr/2016/216/regulation/13) and [reg 14(1)](https://www.legislation.gov.uk/nisr/2016/216/regulation/14) under the same numbers. Under their [reg 12(1)](https://www.legislation.gov.uk/nisr/2016/216/regulation/12), the decision maker treats every qualifying young person as receiving education, with no exception for a training scheme, which the model doesn't apply in Great Britain either. So the model's one rule covers Northern Ireland, and case 21 tests it there.

The Department for Communities applies the transitional provisions this PR follows under the [Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016](https://www.legislation.gov.uk/nisr/2016/226) (SR 2016/226), with these dates from legislation.gov.uk:

| GB, SI 2014/1230 | NI, [SR 2016/226](https://www.legislation.gov.uk/nisr/2016/226) | In operation in NI |
|---|---|---|
| reg 28, meaning of "qualifying young person" | [reg 29](https://www.legislation.gov.uk/nisr/2016/226/regulation/29) | From 27 September 2017. SR 2016/226 came into operation immediately after the NI Universal Credit Regulations (reg 1), which came into operation when Article 6 of the Welfare Reform (NI) Order 2015 first came into force for any part of NI (their reg 1). Article 6 first came into force for claims made from 27 September 2017 in the first districts ([SR 2017/190](https://www.legislation.gov.uk/nisr/2017/190) art 4(3) to (5) and Schedule 1). |
| reg 48, meaning of "qualifying claim" | [reg 49](https://www.legislation.gov.uk/nisr/2016/226/regulation/49) | From 24 July 2019, when Part 4 came in ([SR 2019/152](https://www.legislation.gov.uk/nisr/2019/152)). |
| reg 60, protection for full-time students | [reg 61](https://www.legislation.gov.uk/nisr/2016/226/regulation/61) | From 24 July 2019 (SR 2019/152). Paragraph (2) applies from 1 August 2022 ([SR 2022/194](https://www.legislation.gov.uk/nisr/2022/194) reg 11). From 27 January 2025, paragraph (1) turns on a person who has made a qualifying claim and on the migration day, in place of a notified person and the day the existing awards end ([SR 2025/3](https://www.legislation.gov.uk/nisr/2025/3) reg 6(9)). |

NI regs 29, 49 and 61 match GB regs 28, 48 and 60, with NI cross-references: Article 9(1)(d) of the 2015 Order for section 4(1)(d) of the 2012 Act, reg 6 of the NI Universal Credit Regulations for reg 5, reg 47(4) for reg 46(4), and regs 57 and 58 for regs 56 and 57. So the model's GB routes carry over to Northern Ireland unchanged, and its departures from reg 60 above apply to reg 61 too. Case 41 tests reg 61(1): the student of case 36, in Northern Ireland, keeps 12 × £400.14 = £4,801.68 in 2025-26, at the monthly standard allowance of £400.14 that the Department for Communities set in the [Social Security Benefits Up-rating Order (Northern Ireland) 2025](https://www.legislation.gov.uk/nisr/2025/64) art 32(2) and Schedule 13.

### Which years

The change reaches every year the model calculates Universal Credit eligibility. No parameter or date limits it: regs 12 and 14 have applied since 29 April 2013, reg 28 since 16 June 2014 with no amendment, and the dated changes to reg 14(1)(b) concern its timing and the Scottish benefits, which the model doesn't read. In model year 2014 the model raises `ParameterNotFoundError` for `gov.dwp.pension_credit.mixed_age_couples.saving_latest_birth_date`, on `main` too. Case 22 tests 2016.

Where the model takes the closure from the `active` parameters, it applies the reg 60(1) protection only from 2025-26 for tax credits and from 2026-27 for Income Support and income-based JSA, in Great Britain and Northern Ireland alike. So on the parameters alone, the model protects no one in 2024-25 or earlier, and never reaches a year in which only the text in force before 27 January 2025 applied. A user or dataset can also set `claims_uc_at_legacy_closure`, or `legacy_benefits_closed`, for any year, and the model then applies the protection in that year: cases 19, 25, 26, 34 and 35 set the first for 2026, and `test_education_condition_invariants` draws it for 2019, 2021, 2024 and 2026. Cases 36 to 41 test 2024 to 2027 with the closure taken from the parameters, and `test_reg_60_protection_follows_the_closure_dates` tests 2020 to 2028.

### Data

This repository holds no survey-backed record of full-time study or maintenance funding. `git grep` finds:

- `current_education`, the enrolment stage;
- `in_HE` and `in_FE`;
- `employment_status`, with a `STUDENT` category;
- `student_loans` and `education_grants`, amounts that don't say what they pay for;
- `childcare_grant_full_time_student`, an input no dataset supplies.

Both new inputs stay false on current data until a dataset supplies them, so aggregates don't move. With neither input set, the new variables still evaluate: they give everyone `meets_uc_education_condition` true, so `is_uc_eligible` gives `main`'s result. On two hand-built datasets with neither input, every variable both versions define gives `main`'s values.

### Overlapping work

`git merge-tree` merges this branch cleanly with `main` at 2da212abc and at 52b30809f, #1969 (3a3241cf6), #2279 (c5b9850a3), `fix-2120-uc-carer-awards` (466519e49) and `fix-2562-esa-abolition` (68ccc0856). With #2688 (798491cb7) it conflicts in `policyengine_uk/data/uprating_indices.yaml`, as #2688 does with `main` alone; this branch doesn't touch that file.

- **#2279** reads raw reports of Universal Credit, ESA of either kind and JSA of either kind, and excludes a young person who reports any of them from qualifying young person status. Under UC Regs reg 5(5), DWP excludes a young person on Universal Credit, contributory ESA or contribution-based JSA, and under SI 2014/1230 reg 28 one on income-related ESA or income-based JSA. Merged with this branch, an 18-year-old on full-time A levels who reports Universal Credit or contributory ESA no longer counts as a qualifying young person, and gets £4,062.96 and £1,062.96 respectively. This branch alone gives nil, since it reads reg 5(5) only from `receives_benefits_in_own_right`. #2279's reports of income-related ESA and income-based JSA overlap this branch's reg 28 list, and applying both gives the same result. The reg 5(1)(a) route needs the change in the reg 12(1A) bullet above.
- **`fix-2562-esa-abolition`** widens `claims_uc_at_legacy_closure` to families whose income-related ESA award the abolition reaches (`esa_income_abolition_reached`). This branch then gives reg 60 protection to every claimant and partner in those families who doesn't count as a qualifying young person. DWP gives reg 60(1) protection only to a person "who has made a qualifying claim", which the Secretary of State defines in [reg 48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48) as "a claim for universal credit by a single claimant who is a notified person or by joint claimants, both of whom are notified persons, where the claim is made on or before the final deadline". Abolition alone does not make their claim qualifying: under regs [44(6)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/44) and [48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48), DWP must have issued a migration notice to each claimant, and they must claim by the final deadline. The #2562 author should distinguish that qualification from the abolition-triggered claim proxy before the model gives reg 60 protection. The author of whichever PR lands second carries that integration fix.

## Invariants

These hold for every input:

1. **Never raises.** Setting `is_in_full_time_education` never raises a benefit unit's Universal Credit. A family in a household of its own gets either its `main` amount or nil.
2. **Unflagged households don't change.** In a household where no one has the input set, every output equals `main`'s.
   - When the household head's family includes a student and can't claim, its non-dependants count in the next claim by a family liable for the rent, whose award then falls by the housing cost contribution. Under Sch 4 para 9(2)(f), DWP doesn't count a person as a non-dependant in a claim if it already counts them in another claim by someone liable for the rent. The model assigns non-dependants to the household head's eligible claim first.
   - In the cross-family test, a joint tenant whose own family has no one flagged loses 12 × £96.55 = £1,158.60 in 2026-27 (Sch 4 para 14(1), as SI 2026/148 art. 32(3) sets it).
3. **Exceptions keep the claimant in.** A claimant who meets a modelled reg 14 exception, or reg 60, meets the condition, and a family whose claimants all meet it gets its `main` Universal Credit.
4. **The couple decides.** The input on a child or qualifying young person in the benefit unit never changes its Universal Credit.
5. **One member meets both.** Every eligible family that makes no closure claim has a claimant who meets the minimum age, hasn't reached the qualifying age for State Pension Credit and meets the education condition.
6. **Maintenance funding comes from the input.** Removing every student loan and education grant changes no one's receiving education and no Universal Credit output.
7. **Reg 28.** No one who reports an existing benefit counts as a qualifying young person for the condition.
8. **Reference agreement.** Receiving education, the exceptions, the reg 28 status and `is_uc_eligible` equal a reference implementation, which reads the inputs and applies regs 3(2), 12 and 14 as DWP reads them in ADM E2016, and SI 2014/1230 regs 28 and 60. Without the education condition, the reference gives `main`'s eligibility.
9. **Closure dates.** For single people aged 25 to 59 with no child, no listed disability benefit, no foster role, no capital and no other benefit report, where the model derives `claims_uc_at_legacy_closure` (no user or dataset sets it or `legacy_benefits_closed`), `uc_is_excepted_from_education_condition` holds exactly when, in that model year, they report a legacy benefit whose `active` parameter reads false and their family claims at closure (`would_claim_uc_at_legacy_closure`). The model then finds a full-time degree student among them eligible exactly when the exception holds, and anyone else eligible either way.

We check the first eight with Hypothesis in `policyengine_uk/tests/test_uc_receiving_education_properties.py`, for families in households of their own:

- 15 drawn cases plus 4 explicit ones;
- up to four families each, in 2019, 2021, 2024 or 2026, drawing Attendance Allowance, Armed Forces Independence Payment, DLA, PIP, the maintenance input and reported Housing Benefit and Working Tax Credit;
- one simulation that calculates each case five ways: as drawn, with no one flagged, with the non-members' flags reversed, with no loans or grants, and under `main`'s `is_uc_eligible` formula, which the test copies with its comments removed; the formatter re-wrapped two lines.

`test_student_head_moves_non_dependant_to_joint_tenant` pins the cross-family effect.

`test_reg_60_protection_follows_the_closure_dates` checks invariant 9:

- 40 drawn cases plus explicit grids of 40 people for 2024, 2025 and 2026, the years either side of each closure, with every combination in North East England and in Northern Ireland;
- a model year from 2020 to 2028 and up to 24 single people, for each of whom Hypothesis draws the four legacy reports (Child Tax Credit, Working Tax Credit, Income Support and income-based JSA), both claim flags, a region, Northern Ireland included, and a full-time degree;
- one simulation for each case. The test reads the closure dates from the `active` parameters, not from constants, and `test_closure_grids_straddle_each_closure` checks that the grid years still fall either side of each closure.

## Test plan

- [x] The YAML tests use legislation and DWP rates for their expected values: the 2026-27 rates from DWP's Benefit and pension rates 2026 to 2027, the 2025-26 rate from DWP's Benefit and pension rates 2025 to 2026 and, for Northern Ireland, the Social Security Benefits Up-rating Order (Northern Ireland) 2025 Schedule 13, the taper from reg 22(1)(b), and the non-dependant contribution from SI 2026/148 art. 32(3). Case 2's housing costs element comes from the model's Universal Credit LHA rate for Plymouth (£400 a month), and its total matches `main`.
- [x] `make format` equivalent on the changed files: `uvx ruff@0.17.2 format --check`, `uvx ruff@0.17.2 check`, `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk` and `git diff --check`, all clean.
- [x] Changelog fragment `changelog.d/2346.fixed.md`.

Results, each run alone without survey data (`HUGGING_FACE_TOKEN` and `POLICYENGINE_UK_DEFAULT_DATASET` unset):

| Run | Branch | `main` (2da212abc) |
|---|---|---|
| `tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml` (new) | 41 passed | 41 failed (the variables don't exist) |
| `tests/test_uc_receiving_education_properties.py::test_examples_reach_the_cases` | 1 passed | |
| `tests/test_uc_receiving_education_properties.py` (new, all five tests), with `test_uc_legacy_closure_properties.py` (4) and `test_legacy_closure_scope.py` (21), `-n 4` | 30 passed in 202 s | this file: 4 failed (the input doesn't exist); `test_closure_grids_straddle_each_closure`, which reads only parameters, passed |
| `tests/test_parameter_descriptions.py` | 24 passed | |
| Differential, every variable, two hand-built datasets with neither input (380 people, 240 benefit units of which 125 get Universal Credit, 180 households), at an earlier head; no formula has changed since | 1,387 shared variables × 2 datasets = 2,774 identical, none raising | |
| Existing `tests/policy/baseline/finance/benefit/family/universal_credit/` (directory) | 563 passed in 36 s | |
| `tests/policy/baseline/gov/dwp/universal_credit/` (directory, the 41 new cases and 16 existing ones) | 57 passed | |
| `person_legacy_benefits_closed.yaml` and the three `legacy_closure_*.yaml` files under `tests/policy/baseline/` | 3, 1, 4 and 9 passed | |
| `tests/code_health` (`-n 4`) | 3,095 passed | |

Mutants, in scratch copies: first the 35 YAML cases, then the property file. The YAML runs and the last mutant's property run used db140e0ca. The other property runs used 13bbf82bf, and the two with shrinking off used 27e004ace. At all three commits every formula matches this head, and the property file's code matches it apart from docstrings, documentation and the closure tests, which these runs predate. At db140e0ca the YAML file differs from this head only in comments and cases 36 to 41. The last two rows ran at f06a9c636, on all 41 cases, `person_legacy_benefits_closed.yaml`, the three `legacy_closure_*.yaml` files and the property files; the legacy closure YAML files pass under both.

| Mutant | YAML cases failing | Property tests |
|---|---|---|
| Keep PIP alone in reg 14(1)(b), dropping Attendance Allowance, AFIP and DLA | 3 of 35 (cases 27, 28 and 29) | no failure before the 1,500 s cap with `-x`; with shrinking off, fails at the reference differential after 1,055 s |
| Drop the maintenance funding limb of reg 12(2)(b) | 3 of 35 (cases 15, 32 and 34) | fails (287 s) |
| Test the age and education conditions on any claimant separately, so no member need meet both | 2 of 35 (cases 7 and 8) | fails on an explicit example (137 s) |
| Drop reg 28, reading `is_qualifying_young_person_for_universal_credit` | 4 of 35 (cases 31, 32, 33 and 34) | fails (131 s) |
| Read `student_loans` and `education_grants` in place of `has_education_maintenance_funding` | 4 of 35 (cases 15, 30, 32 and 34) | fails (322 s) |
| Drop reg 14(1)(f) | 2 of 35 (cases 7 and 23) | fails (573 s) |
| Apply reg 28 to the person's own reports, without the joint tax credit award | 1 of 35 (case 33) | no failure before the 1,500 s cap with `-x`; with shrinking off, passes all 19 examples |
| Drop the education condition from the reg 60A route | 1 of 35 (case 35) | no failure before the 1,500 s cap with `-x` |
| Read `is_qualifying_young_person_for_universal_credit`, before reg 28, for the reg 60 protection | 1 of 35 (case 34) | `test_examples_reach_the_cases` fails (64 s) |
| Drop `migrated_student`, the reg 60(1) limb, from `uc_is_excepted_from_education_condition` | 7 of 41 (cases 19, 25, 26, 34, 36, 37 and 41) | 3 of 5 fail (24 s): `test_reg_60_protection_follows_the_closure_dates` on its 2025 and 2026 grids, `test_education_condition_invariants` on an explicit example, and `test_examples_reach_the_cases` |
| Make `legacy_benefits_closed` close any reported legacy benefit, ignoring the `active` parameters | 1 of 41 (case 40) | 1 of 5 fails (77 s): `test_reg_60_protection_follows_the_closure_dates` on its 2024 and 2025 grids; in the legacy closure property files, 3 of 4 tests in `test_uc_legacy_closure_properties.py` and 1 of 21 in `test_legacy_closure_scope.py` fail (442 s) |

The YAML cases kill all eleven mutants. A property run that reaches its cap without a failure only shows that the cases it reached in time don't kill that mutant; several runs shared the host, so the times also vary with load.

Case 35 pins the reg 60A gate. With a closure claim, every claimant who isn't a qualifying young person meets the condition under reg 60(1), and a claimant or partner on the couple's tax credit award loses that status under reg 28. A claimant responsible for a child or qualifying young person in the benefit unit meets reg 14(1)(c). The gate changes the result only where the claimant is a qualifying young person and an adult in the benefit unit who is neither claimant, partner, child nor qualifying young person reports Working Tax Credit, as in case 35. The model keeps the gate because under reg 60A the Secretary of State waives the age condition and nothing else. The model's reg 60A route reads Working Tax Credit from every member of the benefit unit, where under reg 60A(1) the single claimant, or both joint claimants, must be "entitled to an award of working tax credit"; that predates this PR, and we set it out in #2805.

No existing test's expected value changes.

## Reproducer

Reproducer: `policyengine_uk/tests/test_uc_receiving_education_properties.py::test_examples_reach_the_cases`; `policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml`

axiom: TheAxiomFoundation/rulespec-uk#514 queued

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

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

AXIOM_514_NI.md

## Northern Ireland addendum: SR 2016/226 regs 29, 49, 50 and 61

Above, we asked for SI 2014/1230 regs 28 and 60 and quoted reg 48, and our Northern Ireland section covered SR 2016/216 alone. In Northern Ireland, the Department for Communities applies the same transitional rules under the Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016 (SR 2016/226). Below, we give their text, their dates and companion cases that we work from that text. In policyengine-uk, `is_qualifying_young_person_for_uc_education` and `uc_receiving_education` cite reg 29, and `uc_is_excepted_from_education_condition` cites regs 29, 49 and 61; its formula applies reg 61(1) through `claims_uc_at_legacy_closure`, with no rule of its own for Northern Ireland, and doesn't apply reg 61(2). Case 41 of `policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml` tests reg 61(1).

### Verbatim law

legislation.gov.uk published this text, in force on 3 November 2026, with these amendment notes, in each regulation's data.xml; the SHA-256 identifies that file.

#### SR 2016/226 reg 29, meaning of "qualifying young person"

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/29 (data.xml SHA-256 `6f41b7bc49cf735a78f424b02487c5d66fcde4f035deb156922cf6b830b33109`)

```text
PART 2 CHAPTER 2
EFFECT OF TRANSITION TO UNIVERSAL CREDIT
Meaning of “qualifying young person”
 (29) Where a person who would (apart from the provision made by this regulation) be a “qualifying young person” within the meaning of regulation 6 of the Universal Credit Regulations is entitled to an existing benefit—
 (a) that person is not a qualifying young person for the purposes of the Universal Credit Regulations; and
 (b) regulation 6(5) of those Regulations applies as if, after “a person who is receiving” there were inserted “an existing benefit (within the meaning of the Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016),”.

Amendment notes (legislation.gov.uk):
- Reg. 29 coming into operation in accordance with reg. 1
```

#### SR 2016/226 reg 49, meaning of "qualifying claim"

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/49 (data.xml SHA-256 `d90f94900f1293627e4adc8e917630eaefe67e92254a45309b67ee8040dbb9ef`)

```text
PART 4
MANAGED MIGRATION TO UNIVERSAL CREDIT
Transitional Protection
Meaning of “qualifying claim”
 (49) A “qualifying claim” is a claim for universal credit by a single claimant who is a notified person or by joint claimants, both of whom are notified persons, where the claim is made on or before the final deadline (see regulation 47(4)).

Amendment notes (legislation.gov.uk):
- Pt. 4 added (24.7.2019) by The Universal Credit (Managed Migration and Miscellaneous Amendments) Regulations (Northern Ireland) 2019 (S.R. 2019/152), regs. 1(2), 2(8)
```

#### SR 2016/226 reg 50, meaning of "migration day"

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/50 (data.xml SHA-256 `ee2e793edc167e7d33768b1c6b239f362d0aed913b86acad6289d191b48ba20c`)

```text
PART 4
MANAGED MIGRATION TO UNIVERSAL CREDIT
Transitional Protection
Meaning of “migration day”
 (50) “Migration day”, in relation to a qualifying claim, means the day before the first day on which the claimant is entitled to universal credit in connection with that claim.

Amendment notes (legislation.gov.uk):
- Pt. 4 added (24.7.2019) by The Universal Credit (Managed Migration and Miscellaneous Amendments) Regulations (Northern Ireland) 2019 (S.R. 2019/152), regs. 1(2), 2(8)
```

#### SR 2016/226 reg 61, protection for full-time students

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/61 (data.xml SHA-256 `bd5fdb8a6243da0ff008c5f3afdb367f7f3d117147771b032132153ffb195c40`)

```text
PART 4
MANAGED MIGRATION TO UNIVERSAL CREDIT
Miscellaneous
Protection for full-time students until course completed
 (61)
 (1) Where a person who has made a qualifying claim does not meet the basic condition in Article 9(1)(d) of the Order (not receiving education) on the migration day because the person is undertaking a full-time course (see regulation 12(2) and 13 of the Universal Credit Regulations), that condition is not to apply in relation to the person while they are continuing to undertake that course.
 (2) Paragraph (1) does not apply to any assessment period in respect of which a transitional element or transitional capital disregard would (if the claimant had been entitled to that element or that disregard) have ceased to apply by virtue of regulation 57 (circumstances in which transitional protection ceases) or regulation 58 (application of transitional protection to a subsequent award).”.

Amendment notes (legislation.gov.uk):
- Pt. 4 added (24.7.2019) by The Universal Credit (Managed Migration and Miscellaneous Amendments) Regulations (Northern Ireland) 2019 (S.R. 2019/152), regs. 1(2), 2(8)
- Reg. 61 renumbered as reg. 61(1) (1.8.2022) by The Universal Credit (Transitional Provisions) (Amendment) Regulations (Northern Ireland) 2022 (S.R. 2022/194), regs. 1(1), 11
- Reg. 61(2) inserted (1.8.2022) by The Universal Credit (Transitional Provisions) (Amendment) Regulations (Northern Ireland) 2022 (S.R. 2022/194), regs. 1(1), 11
- Words in reg. 61(1) substituted (27.1.2025) by The Social Security (Miscellaneous Amendments) Regulations (Northern Ireland) 2025 (S.R. 2025/3), regs. 1(2), 6(9)(a)
- Words in reg. 61(1) substituted (27.1.2025) by The Social Security (Miscellaneous Amendments) Regulations (Northern Ireland) 2025 (S.R. 2025/3), regs. 1(2), 6(9)(b)
- Word in reg. 61(1) substituted (27.1.2025) by The Social Security (Miscellaneous Amendments) Regulations (Northern Ireland) 2025 (S.R. 2025/3), regs. 1(2), 6(9)(c)
```

#### SR 2025/3 reg 6(9), the amendment of reg 61(1) from 27 January 2025

Source: https://www.legislation.gov.uk/nisr/2025/3/regulation/6/made (paragraph (9), as made; data.xml SHA-256 `97ee2227c8400010f52aed80aa068f250398ae1369a33a41875ed4deb2ea3246`)

```text
(9) In regulation 61(1) (protection for full-time students until course completed) — (a) for “notified person”, in the first place it occurs, substitute “person who has made a qualifying claim” ; (b) for “on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit” substitute “on the migration day” ; (c) for “notified person,” in the second place it occurs, substitute “person” .
```

### Dates and cross-references

| GB, SI 2014/1230 | NI, [SR 2016/226](https://www.legislation.gov.uk/nisr/2016/226) | In operation in NI |
|---|---|---|
| reg 28, meaning of "qualifying young person" | [reg 29](https://www.legislation.gov.uk/nisr/2016/226/regulation/29) | From 27 September 2017. SR 2016/226 came into operation immediately after the NI Universal Credit Regulations (reg 1), which came into operation when Article 6 of the Welfare Reform (NI) Order 2015 first came into force for any part of NI (their reg 1). Article 6 first came into force for claims made from 27 September 2017 in the first districts ([SR 2017/190](https://www.legislation.gov.uk/nisr/2017/190) art 4(3) to (5) and Schedule 1). |
| reg 48, meaning of "qualifying claim" | [reg 49](https://www.legislation.gov.uk/nisr/2016/226/regulation/49) | From 24 July 2019, when Part 4 came in ([SR 2019/152](https://www.legislation.gov.uk/nisr/2019/152)). |
| reg 60, protection for full-time students | [reg 61](https://www.legislation.gov.uk/nisr/2016/226/regulation/61) | From 24 July 2019 (SR 2019/152). Paragraph (2) applies from 1 August 2022 ([SR 2022/194](https://www.legislation.gov.uk/nisr/2022/194) reg 11). From 27 January 2025, paragraph (1) turns on a person who has made a qualifying claim and on the migration day, in place of a notified person and the day the existing awards end ([SR 2025/3](https://www.legislation.gov.uk/nisr/2025/3) reg 6(9)). |

The NI text matches the GB text, with NI cross-references: Article 9(1)(d) of the 2015 Order for section 4(1)(d) of the 2012 Act, reg 6 of the NI Universal Credit Regulations for reg 5, reg 47(4) for reg 46(4), and regs 57 and 58 for regs 56 and 57. The Department has applied reg 50, meaning of "migration day", which matches GB reg 49 word for word, since 24 July 2019, when Part 4 came in (SR 2019/152).

### Other provisions the cases use

We also work the cases below from these paragraphs, as legislation.gov.uk published them in force on 3 November 2026; `[...]` marks each paragraph we leave out.

SR 2016/226 reg 45, migration notice. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/45 (data.xml SHA-256 `db60c3d0b80da8afadead5447ba3a9c60fb6f9ff02feab6b92f208887027c214`)

```text
 (45)
 (1) The Department may, at any time, issue a notice (“a migration notice”) to a person who is entitled to an award of an existing benefit—
 (a) informing the person that all awards of any existing benefits to which they are entitled are to terminate and that they will need to make a claim for universal credit; and
 (b) specifying a day (“the deadline day”) by which a claim for universal credit must be made.
[...]
 (3B) Where a migration notice is—
 (a) issued to a person who is entitled to an award of tax credit; and
 (b) issued on or after 27th January 2025 and before 6th April 2025, the deadline day may be 6th April 2025.
[...]
 (6) A “notified person” is a person to whom a migration notice has been issued.
[...]
```

SR 2016/226 reg 47, termination of existing benefits if no claim before the deadline. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/47 (data.xml SHA-256 `36c0fcacab3b68c23cdee1241fe87cdb20540653abef0db2a089421a8d1a9fb9`)

```text
 (47)
[...]
 (4) The final deadline is the day that would be the last day of the first assessment period in relation to an award commencing on the deadline day.
[...]
```

SR 2016/226 reg 57, circumstances in which transitional protection ceases. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/57 (data.xml SHA-256 `33bcd90d67c66e415bb529a83e7f6342ff669f57a511cf9576951de6d7c3b0f2`)

```text
 (57)
 (1) A transitional capital disregard or a transitional element does not apply in any assessment period to which paragraph (2) , (3A) or (4) applies, or in any subsequent assessment period.
Cessation of employment or sustained drop in earnings
 (2) This paragraph applies to an assessment period other than an assessment period in relation to an award of universal credit mentioned in regulation 61A(1) (waiver of upper age limit for claimants migrated from tax credits) if the following condition is met—
 (a) in the case of a single claimant—
 (i) it is the assessment period after the third consecutive assessment period in which the claimant’s earned income is less than the amount specified in regulation 97(14)(a) of the Universal Credit Regulations (“the single administrative threshold”), and
 (ii) in the first assessment period of the award, the claimant’s earned income was equal to or more than that threshold; or
[...]
 (3) For the purposes of paragraph (2)—
[...]
 (b) a claimant is to be treated as having earned income that is equal to or more than the single administrative threshold and the couple administrative threshold respectively in any assessment period in respect of which regulation 63 (minimum income floor) of the Universal Credit Regulations (Northern Ireland) 2016 applies to that claimant or would apply but for regulation 63(6) of those Regulations (minimum income floor not to apply in a start-up period).
 (3A) This paragraph applies to an assessment period in relation to an award of universal credit mentioned in regulation 61A(1) (waiver of upper age limit for claimants migrated from tax credits) which—
 (a) is not one of the first 12 assessment periods; and
 (b) is the assessment period after the third consecutive assessment period in which the claimant’s earned income, (or, if the claimant is a member of a couple the couple’s combined earned income) is less than the amount that a person would be paid at the hourly rate set out in regulation 4 of the National Minimum Wage Regulations 2015 for 16 hours a week converted to a monthly amount by multiplying by 52 and dividing by 12.
Couple separating or forming
 (4) This paragraph applies to an assessment period in which—
 (a) joint claimants cease to be a couple or become members of a different couple; or
 (b) a single claimant becomes a member of a couple, unless it is a case where the person may, by virtue of regulation 3(2) of the Universal Credit Regulations (claimant with an ineligible partner), claim as a single person.
```

SR 2016/226 reg 58, application of transitional protection to a subsequent award. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/58 (data.xml SHA-256 `3ecb1bbc3d868acacd325e55358e8d7a39b37312a0af72bdae8c07463a63a5b2`)

```text
 (58)
 (1) Where—
 (a) a transitional capital disregard is applied, or a transitional element is included, in the calculation of an award, and that award terminates; or
 (b) the Department determines (in accordance with regulation 51) that a transitional capital disregard is to apply, or a transitional element is to be included in the calculation of an award, but the decision on the qualifying claim is that there is no entitlement to an award,
no transitional capital disregard is to apply and no transitional element is to be included in the calculation of any subsequent award unless paragraph (2) applies.
 (2) This paragraph applies if—
 (a) the reason for the previous award terminating or, as the case may be, there being no entitlement to an award, was that the claimant (or joint claimants) had earned income on account of which the financial condition in Article 10(1)(b) or (2)(b) of the Order (income is such that the amount payable is at least 1p) was not met; and
 (b) the claimant becomes entitled to an award within the period of three months beginning with—
 (i) where paragraph (1)(a) applies, the last day of the month that would have been the final assessment period of the previous award (had it not terminated), or
 (ii) where paragraph (1)(b) applies, the day that would have been the last day of the first assessment period had there been entitlement to an award.
[...]
```

The Universal Credit Regulations (Northern Ireland) 2016 (SR 2016/216) reg 3, couples. Source: https://www.legislation.gov.uk/nisr/2016/216/regulation/3 (data.xml SHA-256 `195ff2ed6fbdf1aeadf0924581254bfdd84657ad35e5666a429fc4797ca3c9e7`; no amendment notes)

```text
Couples
 (3)
 (1) A couple may be entitled to universal credit as joint claimants where—
 (a) one member does not meet the basic condition in Article 9(1)(b) of the Order (under the qualifying age for state pension credit) if the other member does meet that condition, or
 (b) one member does not meet the basic condition in Article 9(1)(d) of the Order (not receiving education) and is not excepted from that condition if the other member does meet that condition or is excepted from it.
 (2) A person who is a member of a couple may make a claim as a single person if the other member of the couple—
 (a) does not meet the basic condition in Article 9(1)(a) of the Order (at least 18 years old) and is not a person in respect of whom the minimum age specified in regulation 8 applies,
 (b) does not meet the basic condition in Article 9(1)(c) of the Order,
 (c) is a prisoner,
 (d) is a person other than a prisoner in respect of whom entitlement does not arise by virtue of regulation 19 (restrictions on entitlement), or
 (e) is a person to whom section 115 of the Immigration and Asylum Act 1999 (exclusion from benefits) applies,
and regulations 18 (capital), 38 (amounts of elements) and 23 (deduction of income and work allowance) provide for the calculation of the award in such cases.
[...]
```

### Companion cases

We work each case from the text above. The amounts come from the Department for Communities' standard allowance for a single claimant aged 25 or over: £400.14 for each assessment period that starts on or after 7 April 2025 ([SR 2025/64](https://www.legislation.gov.uk/nisr/2025/64) art 1(1)(n), art 32(2) and Schedule 13), and £424.90 for each that starts on or after 6 April 2026 ([SR 2026/54](https://www.legislation.gov.uk/nisr/2026/54) regs 1(4) and 3(3)(a)).

Every case has one claimant in Northern Ireland: aged 30, single unless the case says otherwise, with no child, none of the disability benefits that the Department for Communities lists in reg 14(1)(b) of the Universal Credit Regulations (Northern Ireland) 2016, no foster child and no earned income, so the Department applies no reg 14(1) exception to them. The claimant undertakes a full-time degree, so they receive education under reg 12(2)(a) of those Regulations. Before the migration they had an award of Working Tax Credit, and the Department may issue a migration notice to a person entitled to an award of tax credit (reg 45(1) and (3B)).

1. **A qualifying claim in 2025-26** (policyengine-uk case 41). The Department issued the claimant a migration notice while they had the Working Tax Credit award (reg 45(1)), so they became a notified person (reg 45(6)). They claim Universal Credit on or before the final deadline (reg 47(4)), so they make a qualifying claim (reg 49). On the migration day, the day before their entitlement starts (reg 50), they undertake the degree, so the Department doesn't apply the condition in Article 9(1)(d) of the Welfare Reform (Northern Ireland) Order 2015 to them while they continue it (reg 61(1)). We assume an award whose 12 assessment periods all start on or after 7 April 2025, with no transitional element.
   - Expected: notified person, yes; qualifying claim, yes; reg 61(1) applies, yes; Article 9(1)(d) applies, no; entitled to Universal Credit, yes; standard allowance £400.14 for each assessment period; 12 assessment periods come to 12 × £400.14 = £4,801.68.
2. **The same course in 2026-27** (the NI version of policyengine-uk case 37). The claimant continues the degree. Under reg 61(2), the Department stops the protection only in an assessment period in which a transitional element would have stopped under reg 57 or 58, and none of those events has happened. The claimant has no earned income, and the Department applies no minimum income floor to them, so even under reg 57(3)(b) their earned income didn't reach the single administrative threshold in the first assessment period, and the Department can't apply reg 57(2) (reg 57(2)(a)(ii)). The Department applies reg 57(3A) only to an award mentioned in reg 61A(1), and the claimant has no such award. They haven't become a member of a couple (reg 57(4)(b)), and their award continues, so the Department doesn't apply reg 58 either. We assume 12 assessment periods that all start on or after 6 April 2026, with no transitional element.
   - Expected: reg 61(2) applies, no; reg 61(1) applies, yes; entitled, yes; standard allowance £424.90 for each assessment period; 12 assessment periods come to 12 × £424.90 = £5,098.80.
3. **A claim after the final deadline in 2025-26** (the NI version of policyengine-uk case 39). The Department issued the claimant a migration notice, but they claim Universal Credit after the final deadline (reg 47(4)), so their claim doesn't count as a qualifying claim (reg 49), and the Department doesn't apply reg 61(1). They receive education with no reg 14(1) exception, so they don't meet Article 9(1)(d).
   - Expected: qualifying claim, no; reg 61(1) applies, no; Article 9(1)(d) met, no; entitled, no; award £0.
4. **A claim before any migration notice in 2024-25** (the NI version of policyengine-uk case 40). The claimant claims Universal Credit before the Department issues them a migration notice, so they don't claim as a notified person (reg 45(6)) and have no deadline day or final deadline (regs 45(1)(b) and 47(4)). Their claim doesn't count as a qualifying claim (reg 49), and the Department doesn't apply reg 61(1). Since 27 January 2025 the Department has applied reg 61(1) to a person who has made a qualifying claim (SR 2025/3 reg 6(9)(a) and (c)); before then it applied reg 61(1) only to a notified person, so the claimant gets no protection under either text. They receive education with no reg 14(1) exception.
   - Expected: notified person when they claim, no; qualifying claim, no; reg 61(1) applies, no; entitled, no; award £0.
5. **The Department stops the protection under reg 61(2) when the claimant joins a couple.** The claimant of case 1, protected under reg 61(1) and continuing the degree in 2026-27, becomes a member of a couple in an assessment period. Their partner, aged 30, also undertakes a full-time degree, has no reg 14(1) exception and never received a migration notice, and falls under none of sub-paragraphs (a) to (e) of reg 3(2) of the Universal Credit Regulations (Northern Ireland) 2016, so the claimant can't claim as a single person. So the Department applies reg 57(4)(b) to that assessment period, under reg 57(1) a transitional element would stop in it and in every later one, and under reg 61(2) the Department doesn't apply reg 61(1) to any of them. Both members then receive education with no exception, so neither meets Article 9(1)(d). Under reg 3(1)(b), a couple in which one member doesn't meet that condition can claim jointly only "if the other member does meet that condition or is excepted from it", and neither does here, so the couple has no joint entitlement.
   - Expected, from the assessment period in which they become a couple: reg 57(4)(b) applies, yes; reg 61(2) applies, yes; reg 61(1) applies, no; Article 9(1)(d) met, no for each member; entitled as joint claimants, no; award £0.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: f06a9c6362bc03b4247f275e9062d532296451f5 on claude/ni-refs-temporal-reg60-x6b1q8, five commits on #2806's head 4d363920686117ae58ce23fa284918964ab86bbc, so moving fix-2346-uc-education to it fast-forwards. The branch has no PR, so no CI has run on it. The hub documents sit above: PR_TITLE.txt (unchanged), PR_BODY.md and AXIOM_514_NI.md.

Before starting. fix-2346-uc-education last moved to 4d363920 (its CI started at 20:15Z on 2 November), and no comment said anyone was mid-change, so I posted the pick-up notice at 09:33Z. juaristi22 posted a second review at 09:26Z, after the brief: COMPLETE, no findings, with one qualification. "Protection is reachable only from 2025" holds for closures the model derives, while an explicit claims_uc_at_legacy_closure input reaches any year, as the existing properties do in 2019, 2021 and 2024. I carried that distinction into the body (the departures intro, Migration day, The claim, Reported receipt, Which years and invariant 9) and into the new property's docstrings. Apart from the notice and the hub documents, I posted nothing on the PR.

What the push does

  1. NI references, with no formula change:
    • policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py:36-40 names NI reg 29, with NI UC Regs reg 6 in place of UC Regs 2013 reg 5; :48 adds nisr/2016/226/regulation/29.
    • policyengine_uk/variables/gov/dwp/universal_credit/uc_receiving_education.py:29-34 names NI reg 29 in place of reg 28; :44 adds the reference.
    • policyengine_uk/variables/gov/dwp/universal_credit/uc_is_excepted_from_education_condition.py:23-25 says the model takes claims_uc_at_legacy_closure as the qualifying claim under reg 48; :52-58 names NI regs 61, 29 and 49 and says the model doesn't apply reg 61(2); :65-70 add GB reg 48 and NI regs 29, 49 and 61.
  2. Temporal cases in policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml: header :17-25 (the 2025-26 rate, £400.14, from DWP's Benefit and pension rates 2025 to 2026, which I read on gov.uk, and the NI amount from SR 2025/64 Schedule 13); cases 36 (:966), 37 (:1009), 38 (:1049), 39 (:1083), 40 (:1118) and 41 (:1162). Each uses the brief's claimant, sets both claim flags, sets no claims_uc_at_legacy_closure, and works its result from the regulation text in its comment. Cases 1 to 35 don't change.
  3. Property in policyengine_uk/tests/test_uc_receiving_education_properties.py: test_reg_60_protection_follows_the_closure_dates (:877, helpers :776-875), test_closure_grids_straddle_each_closure (:928), and the module docstring :66-76.
  4. PR_BODY.md: the subsection "Where the annual closure proxy departs from reg 60" in place of the old bullet, with Migration day, The same course, Reg 60(2), The claim and Reported receipt, each naming its provision and who gains or loses; the reply's table and the NI cross-references in "Northern Ireland"; the closure years in "Which years"; invariant 9; this head's numbers in the Test plan, with two new mutant rows. The Fixes, Reproducer: and axiom: lines don't change, and neither does changelog.d/2346.fixed.md, which names no provision this push changes.
  5. AXIOM_514_NI.md: the verbatim text of NI regs 29, 49, 50 and 61 and SR 2025/3 reg 6(9), each with its URL and the brief's SHA-256; the reply's dates and cross-references; verbatim excerpts of NI regs 45, 47, 57 and 58 and NI UC Regs reg 3, which the cases use; and five companion cases (the NI versions of cases 36, 37, 39 and 40, and one where reg 61(2) ends the protection when the claimant joins a couple). I checked every quoted line, word for word, against the data.xml files.

Independent review

Four reviewers, each blind to my reasoning, read the GB law in the cases, the NI addendum and docstrings, the body against the code, and the property test. They raised 44 findings; none touched a formula or an expected value. I fixed all but one in c960b384 and f06a9c63; I kept the brief's regulation-number style in the addendum (judgment call 7). The main ones:

  • Case 40 (:1118): "never becomes a notified person" went beyond reg 44(6), and "kept the protection from then" applied the text in force from 27 January 2025 to all of 2024-25. The comment now says the claimant doesn't claim as a notified person and has no deadline day or final deadline, and it limits the 2024-25 point to the text in force from 27 January 2025.
  • Case 39 (:1083): the comment never said the family claimed after the final deadline; it now does, and the case name follows.
  • Case 37 (:1009): reg 56(3)(b) counts a claimant on the minimum income floor as reaching the threshold, so the comment now states that DWP applies no minimum income floor, and that the award isn't a reg 60A award.
  • Cases 36 and 41: reg 44(1) and NI reg 45(1) give a power ("may"), so the comments now say DWP or the Department issued the notice while the claimant had a Working Tax Credit award.
  • Addendum: it said the Department for Communities paid Working Tax Credit, which nothing supports; it now says the claimant had an award. Companion case 5 rested on NI UC Regs reg 3, which no one had quoted; I fetched and quoted it. The rates now carry their start dates (SR 2025/64 art 1(1)(n); SR 2026/54 reg 1(4)), and the intro no longer says the model applies regs 50 or 61(2).
  • Property: the grid's region cycle put every protected student in Northern Ireland, so the grids now hold every combination in North East England and in Northern Ireland (40 people). The docstrings now limit the invariant to closures the model derives, and the comment on the parameter instant now describes the 30 April snapshot (:894).
  • Body: "The claim" named would_claim_uc_at_legacy_closure as the qualifying claim where the model uses claims_uc_at_legacy_closure; invariant 9 now names its population and the derived closure; the departures intro scopes every gain and loss to a student with no other exception.
  • Writing: several sentences made a regulation, a protection or a document the actor; each now names DWP, the Department for Communities, the model or us.

Tests at f06a9c63

Each from a clean run, without survey data:

Run Result
uc_receiving_education.yaml 41 passed
person_legacy_benefits_closed.yaml 3 passed
legacy_closure_ineligible_partner.yaml, legacy_closure_pension_route.yaml, legacy_closure_migrant_nations.yaml 1, 4 and 9 passed
pytest -n 4 on test_uc_receiving_education_properties.py, test_uc_legacy_closure_properties.py and test_legacy_closure_scope.py, in one run 30 passed (5, 4 and 21) in 202 s; no run reached the 40-minute cap
pytest policyengine_uk/tests/code_health -n 4 3,095 passed
uvx ruff@0.17.2 format --check ., ruff check ., ruff check --select F811,F821 policyengine_uk clean (2,177 files formatted)
git diff --check 4d3639206..HEAD clean
.github/check_fix_pr.py (main's copy) on the final title and body, with the changes since the merge base with main, as pr_fix_evidence.yaml runs it PASS, 2 changed executable tests
git merge-tree --write-tree --name-only HEAD origin/main (52b30809f) clean, no conflict
Scratch merge of this head with #2845's d5d32074d, uc_receiving_education.yaml 3 failed, 38 passed: cases 31, 33 and 34, each on is_qualifying_young_person_for_universal_credit, as composition.md expects; no new case fails

I also ran, for the body's table: tests/policy/baseline/gov/dwp/universal_credit/ 57 passed; tests/policy/baseline/finance/benefit/family/universal_credit/ 563 passed; test_parameter_descriptions.py 24 passed. On main at 2da212abc, the new YAML file gives 41 failed, and the property file 4 failed and 1 passed (the grid guard reads only parameters). At c8767e3c, the new property also passed on the #2845 merge (137 s).

Revert checks at f06a9c63

Each in a scratch worktree, which I removed afterwards; the working tree stayed clean.

  • Drop migrated_student from the return of uc_is_excepted_from_education_condition: 7 of 41 cases fail (19, 25, 26, 34, 36, 37 and 41), so 36, 37 and 41 fail as the brief expects. The legacy closure YAML files pass. In the property file, 3 of 5 fail (24 s): test_reg_60_protection_follows_the_closure_dates on its 2025 and 2026 grids, test_education_condition_invariants on an explicit example, and test_examples_reach_the_cases.
  • Make legacy_benefits_closed ignore the active parameters, by passing legacy_award_is_closed a stand-in with all three closed in that variable alone: case 40 alone fails, as the brief expects. The legacy closure YAML files pass. In the property file, 1 of 5 fails (77 s): test_reg_60_protection_follows_the_closure_dates on its 2024 and 2025 grids. In the legacy closure property files, 4 of 25 fail (442 s): test_uc_legacy_closure_properties.py::test_closure_claims_and_the_end_of_legacy_awards, ::test_the_closure_flag_only_decides_closure_claims and ::test_the_tax_credit_closure_ends_awards_paid_the_year_before, and test_legacy_closure_scope.py::test_preclosure_report_does_not_end_outside_own_award.

The same runs at c8767e3c, before the review fixes, gave the same failures.

Skipped

  • Survey-data tests: none of this work needs them.
  • The body's differential row (two hand-built datasets): I didn't re-run it, and the body now says it comes from an earlier head; no formula has changed since.
  • The full suite: CI covers it once the hub moves the PR branch.
  • rulespec-uk#514 lies outside this session's GitHub access, so I couldn't read it. The addendum names no module path, and it opens "Above, we asked for…", on the reading that PolicyEngine wrote Remove y axis title from validation chart #514.

Judgment calls

  1. gov.uk and legislation.gov.uk resolved from this session, against the brief's note. I used them to check £400.14 (DWP's 2025 to 2026 table: "Single 25 or over 393.45 400.14"), to find the NI amounts (SR 2025/64 Schedule 13, £400.14; SR 2026/54 reg 3(3)(a), £424.90 from 6 April 2026), to fetch NI UC Regs reg 3, and to fetch every data.xml again. Every hash matched the brief's except NI reg 47 (36c0fcac… today, 73e9c123… in the brief), whose text and notes matched; the addendum quotes reg 47(4) only as a supporting provision, under the hash of the file I fetched.
  2. Case 41 asserts £4,801.68, from SR 2025/64, not DWP's GB table.
  3. The cases assert a few outputs beyond the brief's list: uc_receiving_education in 36, 37 and 41; legacy_benefits_closed and claims_uc_at_legacy_closure in 37, 39 and 40; claims_universal_credit, uc_receiving_education and meets_uc_education_condition in 39 and 40.
  4. Cases 36, 37 and 41 state their assumptions: 12 assessment periods at the year's rate and no transitional element (reg 55). The model has no transitional element, and the brief doesn't hold regs 50 to 55, so I state it as an assumption rather than a result.
  5. The property draws a full-time flag beyond the brief's list and also asserts eligibility: the model finds a degree student eligible exactly when the exception holds. Its exception assertion pins the model's proxy for people off a course (the "same course" departure), and its docstring says so.
  6. With 40 derandomised examples, Hypothesis drew no 2025 case, so explicit grids for 2024, 2025 and 2026 cover each closure boundary, and test_closure_grids_straddle_each_closure fails if the active dates move off those years.
  7. In the addendum I kept the brief's "(61)" style for regulation numbers, and marked each omitted paragraph [...].
  8. The body's departures go beyond the brief's minimum: reg 56(3A), the reg 3(3) exception, a joint claimant without a notice, a course started after the migration day, and Income Support and income-based JSA migrants before 2026-27. "Overlapping work" adds main at 52b30809f, which I checked with merge-tree, along with the SHAs the body already cites.
  9. The remote branch didn't exist (only a stale local tracking ref at main), so the first push created it; I never force-pushed.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

f06a9c636 adds what the earlier reply listed. It cites the Northern Ireland provisions in the three variables, adds cases 36 to 41 to uc_receiving_education.yaml and a property that checks the protection against the closure dates, and describes where the annual closure proxy departs from reg 60.

The description, invariant 9 and the property's docstrings keep your distinction. Where the model derives the closure, the protection starts no earlier than 2025-26. Where a user or dataset sets claims_uc_at_legacy_closure or legacy_benefits_closed, the model applies it in any year, as test_education_condition_invariants does in 2019, 2021 and 2024. rulespec-uk#514 now holds the Northern Ireland text and five companion cases.

@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 f06a9c6362bc03b4247f275e9062d532296451f5, incrementally from 4d363920686117ae58ce23fa284918964ab86bbc. COMPLETE: no findings or material evidence gaps.

The new NI references, temporal cases and actual Axiom #514 addendum are corroborated. The NI qualifying-claim/migration-day/student provisions and cessation cross-references support the stated examples. I checked £400.14 against NI's 2025 Schedule 13 as made and its 7 April assessment-period commencement, rather than relying on the now-omitted revised schedule.

The expanded description correctly distinguishes default-derived closure protection (model 2025/2026) from explicit historical closure/migration inputs. Notice/deadline, same-course and cessation history remain disclosed annual-model limitations; the new properties test that proxy rather than claim full longitudinal statutory implementation.

Validation: 43 adopted checks passed, including all 41 YAML cases and both new closure tests; the prior 164 passing checks remain applicable because production formula ASTs and prior property functions are unchanged. Corrected ordinary-input GB/NI paired diagnostics also give final UC and household-income changes of £4,801.68; the income comparison allows for the unchanged default TV licence charge. Author mutation/population claims were not reproduced.

The PR remains a draft, and hosted Python checks were pending at capture. This is a completed review comment, not an approval; I will retain the review and check CI/readiness on a later run.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Verdict: REQUEST_CHANGES at f06a9c6362bc03b4247f275e9062d532296451f5

Two P2s, no P1.

  • P2-1: invariant 9 and both new docstrings state the closure property for a wider population than the test draws, and the PR's own case 15 falsifies the wider statement.
  • P2-2: "Overlapping work" leaves out Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845. The composition change the PR thread prescribes for whichever PR lands second would take the reg 60(1) protection from a young closure migrant. I ran it, and the award goes from £4,062.96 to nil, with no test to catch it.

No formula, expected value or count at this head is wrong. Every count I reran matches the body, and both of my mutants are killed. A run of my own in 2021 confirms juaristi22's qualification. I worked cases 36 to 41 again from legislation.gov.uk, and their law and amounts hold. CI is green at the head. The P3s are fixes to citations, timing and wording in the cases, the body and the addendum.

legislation.gov.uk and gov.uk resolved from this session, so I fetched each provision below (data.xml, current and point-in-time) rather than relying on the addendum's quotations.

Prior round, item by item

Item Status Evidence
juaristi22, review 5746962807: the NI transitional provisions for GB regs 28, 48 and 60, with their dates Closed The body's NI table and the addendum give SR 2016/226 regs 29, 49 and 61. I re-fetched each one: 27 September 2017, 24 July 2019, 1 August 2022 and 27 January 2025 all hold. Wording fixes are in P3-11.
juaristi22, review 5746962807: source-backed temporal cases for the qualifying claim, course continuity and cessation Closed Cases 36 to 41 cover a qualifying claim (36), the same course (37), the end of the course (38), a claim after the final deadline (39), a claim before closure (40) and Northern Ireland (41). I worked each again from the law (Checked and sound). Cessation under reg 60(2) stays a documented departure, and addendum companion case 5 covers reg 61(2).
juaristi22, review 5749714412: limit "only from 2025" to closures the model derives Closed, with two precision gaps The body (departures intro, Migration day, Which years), invariant 9 and both docstrings limit the year bound to derived closures. My 2021 run shows an explicit flag reaching 2021 (P3-1). The docstrings' scope leaves out legacy_benefits_closed (P2-1). The legacy_benefits_closed route also needs would_claim_uc_at_legacy_closure (P3-1).
Hub reply 6599628316: NI references in the three variables, five temporal cases, departures including reg 60(2) Closed The variables' diff touches only documentation and references. The push adds six cases and a departures subsection with a reg 60(2) bullet. One reference is missing (P3-8).
Hub reply 6602652027 Partly closed It matches the push. But "sets claims_uc_at_legacy_closure or legacy_benefits_closed, the model applies it in any year" overstates the second route (P3-1). Correct it in the next reply.
Hand-off, #2845 scratch merge: "3 failed, 38 passed ... no new case fails" Confirmed, but incomplete I get the same count at f379377. The prescribed follow-up change breaks an untested result (P2-2).

Findings

P2-1. Invariant 9 and both new docstrings promise eligibility to "anyone else", and case 15 contradicts that

Where:

  • PR body, Invariants, item 9: "The model then finds a full-time degree student among them eligible if and only if the exception holds, and finds anyone else eligible either way."
  • policyengine_uk/tests/test_uc_receiving_education_properties.py:72-73 (module docstring): "and is_uc_eligible holds for a full-time degree student exactly when that exception holds, and for anyone else always."
  • :887-888 (test docstring): "and anyone else eligible either way."

The stated population ("single people aged 25 to 59 with no child, no listed disability benefit, no foster role, no capital and no other benefit report") sets no limit on the course. So it takes in a full-time student on a non-advanced course with maintenance funding. That student receives education under reg 12(2)(b) and, with no exception, isn't eligible. Case 15 is such a person: aged 30, single, POST_SECONDARY, is_in_full_time_education and has_education_maintenance_funding true, no benefit report, in 2026. It asserts is_uc_eligible: false. uv run policyengine-core test policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml -c policyengine_uk gives 41 passed, case 15 among them.

The test never draws such a person. closure_situation (:853) gives everyone current_education: TERTIARY and sets neither has_education_maintenance_funding nor in_HE, so in the test "anyone else" means only people not on a full-time course. The body opens the list with "These hold for every input".

Both docstrings also scope the property to "where the model derives claims_uc_at_legacy_closure itself" (:69, :880). With legacy_benefits_closed set true, the model still derives claims_uc_at_legacy_closure, now from the set value, and excepts a person who reports nothing. My 2021 run, setup B in P3-1, gives uc_is_excepted_from_education_condition=True with no report. So the first invariant fails within that literal scope. The body's invariant 9 already excludes this case ("no user or dataset sets it or legacy_benefits_closed").

Replacements:

  • Body item 9, last sentence: "The model then finds a full-time degree student among them eligible if and only if the exception holds, and finds anyone among them who isn't on a full-time course eligible either way."
  • Module docstring, :69-76, from "report, when the model derives" to the end:
report, when the model derives claims_uc_at_legacy_closure and
legacy_benefits_closed itself: uc_is_excepted_from_education_condition holds
exactly when, that year, they report a legacy benefit whose active parameter
reads false and their family claims at closure; and is_uc_eligible holds for
a full-time degree student exactly when that exception holds, and for anyone
not on a full-time course always. It reads the closure dates from the
parameters. With claims_uc_at_legacy_closure set true, which
test_education_condition_invariants draws in 2019, 2021 and 2024, the model
excepts them in any year.
  • Test docstring, :880-891. It also names the Department for Communities beside DWP, since half of each grid lives in Northern Ireland:
    model derives claims_uc_at_legacy_closure and legacy_benefits_closed
    itself, uc_is_excepted_from_education_condition holds exactly when, in
    that model year, they report a legacy benefit whose active parameter reads
    false and their family claims at closure
    (would_claim_uc_at_legacy_closure). None of them meets a reg. 14(1) limb,
    so the model excepts them only under its reg. 60(1) rule (SR 2016/226 reg.
    61(1) in Northern Ireland), and it takes the closure from the active
    parameters. The model then finds a full-time degree student among them
    eligible for Universal Credit exactly when the exception holds, and anyone
    among them not on a full-time course eligible either way. For a person off
    a course the model keeps the exception, although DWP lifts the condition
    under reg. 60(1), and the Department for Communities under reg. 61(1),
    only while they continue the course (case 38 of
    uc_receiving_education.yaml); this test pins that proxy."""

Editing the test docstring changes the drawn examples. Hypothesis 6.170.11 seeds derandomize from function_digest, and _clean_source strips decorators and comments but keeps the docstring (hypothesis/core.py:718, internal/reflection.py:57-104). The 40 drawn cases already changed at c960b384 and f06a9c63. At this head, seven of them fall in 2025 (P3-9), so the hand-off's "Hypothesis drew no 2025 case" describes an earlier docstring. I applied both replacements in a scratch worktree. uvx ruff@0.17.2 format --check passes (1 file already formatted), and uv run pytest policyengine_uk/tests/test_uc_receiving_education_properties.py -n 4 gives 5 passed in 274.27s.

P2-2. Overlapping work leaves out #2845, and the prescribed composition change would remove the reg 60(1) protection from a young closure migrant

"Overlapping work" never mentions #2845. On a scratch merge with f379377, uc_receiving_education.yaml gives 3 failed, 38 passed, as the hand-off says:

  • case 31: is_qualifying_young_person_for_universal_credit@2026: [0.] differs from [1.];
  • case 33: @2024: [0. 0.] differs from [1. 1.];
  • case 34: @2026: [0.] differs from [1.].

Cases 36 to 41 pass. Comment 6594856385 then prescribes the change for whichever PR lands second: is_qualifying_young_person_for_uc_education returns is_qualifying_young_person_for_universal_credit. On #2845 that variable reads a report only while the model pays the benefit.

I applied that formula change on the merge and ran scripts/composition_check.py. Both people are a single 18-year-old in 2026 on a full-time POST_SECONDARY course in North East England. Each reports £1,000 of Working Tax Credit, so the model derives the closure, and would_claim_uc is false.

Tree W: earns £9,000, social rent £6,000 (the body's "Young people on legacy benefits" row) M: case 34 with Working Tax Credit in place of Housing Benefit, maintenance funding
head f06a9c6 £5,112.96 (is_qualifying_young_person_for_uc_education False; not receiving education) £4,062.96 (receiving education; excepted)
head merged with #2845 £5,112.96 £4,062.96
that merge with the composition change £0 (qualifying young person; receiving education; not excepted) £0 (qualifying young person, so migrated_student is False)

M's nil contradicts this PR's own rule: "One with maintenance funding receives education under reg 12(2)(b), and on a closure claim keeps the award under reg 60(1) (case 34)". It also contradicts the law that rule rests on. Reg 60(1) asks whether, on the migration day, the person failed the condition "because the person is undertaking a full-time course (see regulation 12(2) and 13 ...)". Then it disapplies the condition "while they are continuing to undertake that course". Reg 8(2)(a) ends the Working Tax Credit award "on the day before the first date on which the claimant is entitled to universal credit", which is the migration day itself (reg 49). So reg 28 still applied that day, and the student took reg 12(2)(b). migrated_student (uc_is_excepted_from_education_condition.py:132-136) tests the year's status instead.

Nothing would catch the change. No YAML case has a young claimant whose own closed report derives the closure. Case 34 uses Housing Benefit and sets the flag, and test_education_condition_invariants always sets claims_uc_at_legacy_closure itself. W's row changes too. Nil there is arguably the law once no award exists, as the body's reg 28 bullet concedes, but the body states £5,112.96.

The comment also says the patch "applies at either head". At f06a9c6, git apply --check gives error: patch failed: policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py:19 / patch does not apply, because this push appended the Northern Ireland sentence after "out.". Its YAML hunks apply at offset 10 and its property hunks at offset 12.

"Overlapping work" also cites stale heads. fix-2120-uc-carer-awards (466519e) is now at 260f6a7, and fix-2562-esa-abolition (68ccc08) is now at dc69f3a, as #2813. main has moved to 1d4d020 (#2776). All of them merge cleanly with this head (Merges table).

Replacements in "Overlapping work":

P3-1. The explicit-flag route: legacy_benefits_closed alone needs would_claim_uc_at_legacy_closure, and earlier years need their own law

My 2021 run (scripts/qualification_2021.py) uses one single full-time degree student aged 30 in North East England, with would_claim_uc true:

Setup, 2021 claims_uc_at_legacy_closure excepted is_uc_eligible UC
A: claims_uc_at_legacy_closure set true, no report True True True £4,938.12
B: legacy_benefits_closed set true, would_claim_uc_at_legacy_closure default True True True £4,938.12
C: legacy_benefits_closed set true, would_claim_uc_at_legacy_closure false False False False £0
D: neither set, reports £1,000 of Working Tax Credit False False False £0
E: claims_uc_at_legacy_closure set false, Working Tax Credit £1,000 False False False £0

2019 and 2024 give the same pattern (A and B get £3,813.84 and £4,721.40). In 2025, D also gets £4,801.68, from the derived closure. So the qualification holds for claims_uc_at_legacy_closure. For legacy_benefits_closed it holds only where would_claim_uc_at_legacy_closure holds, and datasets draw that input at DWP's claim rates (would_claim_uc_at_legacy_closure.py documentation). The model never protects a qualifying young person (uc_is_excepted_from_education_condition.py:132-136).

The law before 2025-26 also differs by year:

  • Reg 60 exists only from 24 July 2019. uksi/2014/1230/regulation/60/2019-06-01/data.xml returns 404, and the note reads "Pt. 4 inserted (24.7.2019) by ... (S.I. 2019/1152), regs. 1(2), 3(7) (with reg. 2)".
  • From 27 January 2025 it reaches only "a person who has made a qualifying claim".
  • In Northern Ireland, SR 2016/226 reg 45(7), in its version to 31 July 2022, read: "The Department must not issue a migration notice while the restriction in regulation 2 (managed migration pilot: limit on number of cases migrated) of the Universal Credit (Managed Migration Pilot and Miscellaneous Amendments) Regulations 2019 is in force". That restriction was revoked on 25 July 2022 (S.I. 2022/752 reg 10).

Replacements:

  • Departures intro, second sentence: "Unless a user or dataset sets the flag, the model derives it from legacy_benefits_closed and would_claim_uc_at_legacy_closure, and unless a user or dataset sets legacy_benefits_closed, it takes that closure from the active parameters."
  • Migration day, last sentence: "Where, for a year before 2025-26, a user or dataset sets claims_uc_at_legacy_closure true, as test_education_condition_invariants does in 2019, 2021 and 2024, or sets legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds, the model protects every claimant and partner in the family who doesn't count as a qualifying young person. DWP protected no one under reg 60 before 24 July 2019, when SI 2019/1152 inserted it, then only a notified person until 26 January 2025, and from 27 January 2025 only a person who had made a qualifying claim (reg 48); in Northern Ireland the Department for Communities could issue no migration notice before 25 July 2022 (SR 2016/226 reg 45(7)). So a partner whom DWP never notified gains, and so does everyone the model protects before 24 July 2019, or in Northern Ireland before 25 July 2022."
  • Which years, second paragraph: "A user or dataset can also set claims_uc_at_legacy_closure true for any year, or legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds (its default), and the model then applies the protection in that year: ..."
  • For the next reply to juaristi22, since 6602652027 is already posted: "Where a user or dataset sets claims_uc_at_legacy_closure true, or sets legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds (its default), the model applies it in any year, as test_education_condition_invariants does with claims_uc_at_legacy_closure in 2019, 2021 and 2024."

P3-2. The text in force before 27 January 2025 loses "as a consequence of a claim", and case 40 understates it

GB reg 60(1) at uksi/2014/1230/regulation/60/2024-06-01/data.xml reads: "Where a notified person does not meet the basic condition in section 4(1)(d) of the Act (not receiving education) on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit because the person is undertaking a full-time course ..., that condition is not to apply in relation to the notified person while they are continuing to undertake that course." When a claim ends a tax credit award, reg 8(2)(a) ends it on the migration day (reg 49).

  • Body, Migration day. The words "under which DWP applied reg 60(1) to a notified person on the day the existing awards were to end" drop the causal clause, so the paraphrase reads as covering a notified person whose awards ended at the deadline because they didn't claim (reg 46(1)). Replacement: "The model also never applies the text in force before 27 January 2025, under which DWP lifted the condition for a notified person who was on a full-time course "on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit" (SI 2025/3 reg 6(9))."
  • Body NI table, reg 61 row, and the same cell in the addendum. This also stops a paragraph acting ("turns on"). Replacement: "From 24 July 2019 (SR 2019/152). The Department for Communities has applied paragraph (2) since 1 August 2022 (SR 2022/194 reg 11). Since 27 January 2025 it has applied paragraph (1) to a person who has made a qualifying claim and is on the course on the migration day; before then it applied it to a notified person on the course on the day on which all their existing awards were to terminate as a consequence of a claim for universal credit (SR 2025/3 reg 6(9))."
  • uc_receiving_education.yaml:1129-1133 (case 40). The comment states only the text in force from 27 January 2025. But DWP protected such a notified student for the whole of 2024-25, so the model departs from the law more widely than the comment says. Replacement for the sentence beginning "A family that claimed by the final deadline": "A family that claimed by the final deadline after DWP issued it a migration notice in 2024-25 did make a qualifying claim (reg. 48). Until 26 January 2025 DWP applied reg. 60(1) to a notified person in it who was on the course on the day their existing awards were to terminate as a consequence of the claim, which for a tax credit award ended by the claim is the migration day (regs. 8(2)(a) and 49); from 27 January 2025 it applied reg. 60(1) to a full-time student in it who was on the course on the migration day."

P3-3. Case 36: two citations, and the Working Tax Credit premise

  • :975, "Working Tax Credit, an existing benefit (reg. 44(1))". Reg 2(1) holds the definition ("“existing benefit” means income-based jobseeker's allowance, income-related employment and support allowance, income support, housing benefit and child tax credit and working tax credit under the 2002 Act"); reg 44(1) is the power to issue a notice. Replacement: "DWP issued the claimant a migration notice (reg. 44(1)) while they had an award of Working Tax Credit, an existing benefit (reg. 2(1)), so they became a notified person (reg. 44(6))."
  • :982, "no transitional element (reg. 55)". Reg 52(1) decides whether DWP includes an element at all ("A transitional element is to be included in the calculation of an award if the total amount of any awards of existing benefits ... is greater than ... the indicative UC amount"). Reg 55(1) sets "The initial amount of the transitional element". Replacement: "no transitional element (reg. 52(1))".
  • :967-969, the shared profile "aged 30 and single, with no child, no listed disability benefit and no earnings". Such a claimant couldn't hold Working Tax Credit while they had no earnings. For a single claim, the Working Tax Credit (Entitlement and Maximum Rate) Regulations 2002 reg 4(1), second condition, requires that the person "is aged at least 25 and undertakes not less than 30 hours work per week" (version at 2024-06-01). Case 37's step that "their earned income didn't reach the single administrative threshold in the first assessment period" needs the work to have stopped before the award began. Add: "While they had Working Tax Credit they worked at least 30 hours a week (Working Tax Credit (Entitlement and Maximum Rate) Regulations 2002 reg. 4(1)); they stopped before their Universal Credit award began, so they have no earned income from its first assessment period on."
  • Addendum, shared premise of the companion cases: "the Department may issue a migration notice to a person entitled to an award of tax credit (reg 45(1) and (3B))". Reg 45(3B) only allows a 6 April 2025 deadline day. Replacement: "Before the migration they had an award of Working Tax Credit, an existing benefit (reg 2(1)), and the Department may issue a migration notice to a person entitled to an award of an existing benefit (reg 45(1))."

P3-4. Case 36's migration day falls in 2024-25, not 2025-26

The name "a full-time student whose family claims when its tax credits close in 2025" reads as a claim in 2025-26. In law, a tax credit migrant whose deadline day falls on or before 6 April 2025 has a migration day on or before 5 April 2025:

  • reg 6A(9): "a person may not make, or be treated as making, a claim for a tax credit in respect of the whole, or any part, of the tax year beginning on 6th April 2025";
  • reg 44(3B): for a notice "issued on or after 27th January 2025 and before 6th April 2025, the deadline day may be 6th April 2025";
  • reg 46(3): for a claim after the deadline day but by the final deadline, "the award is to commence on the deadline day".

So 2025-26 is a later year of the award, and DWP protects the student in it under the text in force from 27 January 2025. The result stands.

  • Name: "Case 36, a full-time student whose family claimed when DWP moved it off tax credits keeps the award in 2025."
  • Comment: after "(reg. 60(1))." add "No one could claim a tax credit for 2025-26 (reg. 6A(9)), and an award on a claim made by the final deadline starts no later than the deadline day (reg. 46(3)), so with a deadline day on or before 6 April 2025 (reg. 44(3B)) the migration day fell on or before 5 April 2025; in 2025-26 DWP applies the text of reg. 60(1) in force from 27 January 2025."

P3-5. Reg 56(3)(b) also covers a start-up period

Reg 56(3)(b) counts a claimant as reaching the threshold in any assessment period in which "regulation 62 (minimum income floor) of the Universal Credit Regulations applies to that claimant or would apply but for regulation 62(5) of those Regulations (minimum income floor not to apply in a start-up period)". UC Regs reg 62(1)(a) applies the floor only to a claimant who "is in gainful self-employment".

  • uc_receiving_education.yaml:1014-1017 (case 37). Replacement: "The claimant has no earned income and isn't in gainful self-employment, so the minimum income floor neither applies to them nor would apply but for a start-up period (Universal Credit Regulations 2013 reg. 62(1) and (5)); so even under reg. 56(3)(b) their earned income didn't reach the single administrative threshold in the first assessment period, and DWP can't apply reg. 56(2) (reg. 56(2)(a)(ii))."
  • Body, Reg 60(2), first sub-bullet: replace "counting a claimant on the minimum income floor as reaching it" with "counting a claimant to whom the minimum income floor applies, or would apply but for a start-up period, as reaching it".
  • Addendum companion case 2 has the same gap in NI reg 57(3)(b) ("or would apply but for regulation 63(6)"). Replace "and the Department applies no minimum income floor to them" with "and the minimum income floor in reg 63 of the Universal Credit Regulations (Northern Ireland) 2016 neither applies to them nor would apply but for reg 63(6) (start-up period)".

P3-6. The departures intro scopes the gains and losses too narrowly

The intro reads: "Each gain or loss concerns a full-time student with no reg 14(1) exception whose family has no other claimant who meets the condition". is_uc_eligible needs one claimant who meets the age and education conditions together. In case 25's mixed-age couple, the 70-year-old meets the education condition but not the age condition, so reg 60(1) still decides the award. Case 25 asserts uc_is_excepted_from_education_condition: [true, true], and dropping migrated_student fails it (row 10 of the mutant table). Replacement: "Each gain or loss concerns a full-time student with no reg 14(1) exception whose family has no other claimant who meets both the age and the education conditions, such as the single claimant of cases 36 to 41 or the younger member of case 25's mixed-age couple."

P3-7. The mutant paragraph contradicts its own table

Body, Test plan, the paragraph that opens "We ran each mutant on a copy of the code". It goes on: "first against the 35 YAML cases and then against the property file. The YAML runs and the last mutant's property run used db140e0. The other property runs used 13bbf82". Rows 10 and 11 ran on 41 cases at f06a9c6, as the paragraph's last sentence says. So "each mutant", "the last mutant" and "the other property runs" no longer fit the eleven-row table. The hub's edit kept the author's wording here. Replacement for those three sentences: "We ran each mutant on a copy of the code, first against the YAML cases and then against the property file. For the first nine mutants, the YAML runs, against the 35 cases, and the ninth mutant's property run used db140e0; their other property runs used 13bbf82, and the two with shrinking off used 27e004a." My rerun of the YAML half of rows 10 and 11 reproduces both rows (Checked and sound).

P3-8. is_qualifying_young_person_for_uc_education.py:43-49 doesn't cite the NI regulation its new sentence names

:38-40 name "reg. 6 of the Universal Credit Regulations (Northern Ireland) 2016" in place of UC Regs reg 5. The tuple cites GB reg 5 but not NI reg 6, while the other two variables cite every NI regulation they name. The URL resolves: HTTP 200, "Meaning of “qualifying young person” ... 6 1 A person who has reached the age of 16 but not the age of 20 ...". Replacement for the tuple's last lines:

        "https://www.legislation.gov.uk/nisr/2016/226/regulation/29",
        "https://www.legislation.gov.uk/nisr/2016/216/regulation/6",
    )

P3-9. The grids aren't "every combination", the guard isn't tied to the grid years, and "tests 2020 to 2028" claims more than the draws reach

  • Body, the first bullet under invariant 9's test: "with every combination in North East England and in Northern Ireland". closure_grid (:826-843) crosses region, no report or exactly one, the closure flag and full-time. would_claim_uc is always the opposite of the closure flag, and no person has two reports. The second gap matters: 2025 is the only year with tax credits closed while Income Support and income-based JSA stay open. So a person with Child Tax Credit and Income Support in 2025 is tested only if a drawn case lands there. Replacement: "40 drawn cases plus explicit grids of 40 people for 2024, 2025 and 2026, the years either side of each closure: in North East England and in Northern Ireland, each legacy report alone and none, with would_claim_uc_at_legacy_closure true or false (and would_claim_uc the opposite), on a full-time degree or not;". A grid row with two reports would close the gap.
  • test_closure_grids_straddle_each_closure (:941-945) checks its own literals 2024, 2025 and 2026, not the @example years (:873-875). My mutant A2 moves the Income Support closure to 2027-04-01. It passes all five YAML files and the main property, and only this guard fails (:945). So here the guard alone pins the Income Support and income-based JSA dates. Sharing the years keeps the guard and the grids in step:
ALL_OPEN, TAX_CREDITS_CLOSED, ALL_CLOSED = 2024, 2025, 2026
@example(closure_grid(ALL_OPEN))
@example(closure_grid(TAX_CREDITS_CLOSED))
@example(closure_grid(ALL_CLOSED))
...
    assert active(ALL_OPEN, "tax_credits.active")
    assert not active(TAX_CREDITS_CLOSED, "tax_credits.active")
    for path in ("income_support.active", "JSA.income.active"):
        assert active(TAX_CREDITS_CLOSED, path)
        assert not active(ALL_CLOSED, path)
  • Which years: "test_reg_60_protection_follows_the_closure_dates tests 2020 to 2028". The strategy draws from 2020 to 2028, but the source fixes the 40 derandomised examples. I logged each case's year from closure_situation, a helper, so the test's seed stays the same. uv run pytest …::test_reg_60_protection_follows_the_closure_dates gave 1 passed in 255.66s over 43 calls: 2020 (6), 2021 (7), 2023 (8), 2024 (8 + grid), 2025 (7 + grid), 2026 (3 + grid) and 2027 (1). No case fell in 2022 or 2028, and the P2-1 edit changes the draws again. Replacement: "and test_reg_60_protection_follows_the_closure_dates draws its model year from 2020 to 2028, besides its grids for 2024 to 2026."

P3-10. Two citations in the body that don't fit what they cite

  • The Same course bullet: "keeps the exception whatever the course, or none (case 38)". Case 38 doesn't assert the exception, and its comment says so. The property test pins the proxy. Replacement: "keeps the exception whatever the course, or none, as case 38's comment notes and test_reg_60_protection_follows_the_closure_dates checks".
  • Test plan, first checkbox: "The YAML tests use ... the non-dependant contribution from SI 2026/148 art. 32(3)". grep -n "96.55\|non-dependant\|2026/148" uc_receiving_education.yaml finds nothing. The £96.55 comes from test_student_head_moves_non_dependant_to_joint_tenant. Replacement for the end of the sentence: "and the taper from reg 22(1)(b); test_student_head_moves_non_dependant_to_joint_tenant takes the non-dependant contribution from SI 2026/148 art. 32(3)."

P3-11. The body's Northern Ireland section and the shared table

  • The section's first paragraph, which probably predates this push, says "The Department for Communities made the Universal Credit Regulations (Northern Ireland) 2016". Their preamble reads "The Secretary of State makes the following Regulations ... Those powers are exercisable by the Secretary of State by virtue of Article 4(1)(a) of the Welfare Reform (Northern Ireland) Order 2015" (nisr/2016/216/introduction/made). Replacement: "The Universal Credit Regulations (Northern Ireland) 2016, which the Secretary of State made under Article 4(1)(a) of the 2015 Order, have GB reg 3(2) as their reg 3(1), and GB reg 13 and reg 14(1) under the same numbers."
  • The reg 29 row, in the body and the addendum: "which came into operation when Article 6 of the Welfare Reform (NI) Order 2015 first came into force for any part of NI (their reg 1)". SR 2016/216 reg 1 makes the Regulations "come into operation on the day on which Article 6 of the Welfare Reform (Northern Ireland) Order 2015 (universal credit) comes into force for all purposes in respect of the whole or any part of Northern Ireland". SR 2017/190 art 4(3) brought Article 6 in only "in relation to the case of a claim referred to in paragraph (4)", which legislation.gov.uk records as "for specified purposes". legislation.gov.uk dates SR 2016/216 reg 1, SR 2016/226 reg 1 and reg 29 from 2017-09-27, so the date holds. Replacement for the clause: "which come into operation on the day on which Article 6 of the Welfare Reform (NI) Order 2015 "comes into force for all purposes in respect of the whole or any part of Northern Ireland" (their reg 1); legislation.gov.uk dates both from 27 September 2017, when Article 6 came into force for claims in the first districts (SR 2017/190 art 4(3) to (5) and Schedule 1)."
  • The SR 2025/64 link, in the body's last NI paragraph and the addendum's companion-case intro, now opens a revoked Order: "Order revoked (5.5.2026) by The Social Security Benefits Up-rating Order (Northern Ireland) 2026 (S.R. 2026/59), arts. 1(1)(o), 34", with Schedule 13 shown as dots. Link https://www.legislation.gov.uk/nisr/2025/64/schedule/13/made instead, and say "as made".
  • Addendum, the paragraph after the table: "The Department has applied reg 50 ... since 24 July 2019". Reg 50 has been in operation since then, but reg 45(7) (P3-1) barred any migration notice, and so any qualifying claim, until 25 July 2022. Replacement: "Reg 50, meaning of "migration day", which matches GB reg 49 word for word, has been in operation since 24 July 2019, when Part 4 came in (SR 2019/152), but until 25 July 2022 reg 45(7) barred the Department from issuing a migration notice, so no one in Northern Ireland could make a qualifying claim before then."

P3-12. Addendum: the verbatim blocks

  • SR 2025/3 reg 6(9). The block has four spaces the source doesn't have. The data.xml (SHA-256 97ee2227…, the addendum's own hash) reads "completed)<FootnoteRef …/>—", "…qualifying claim”;", "…migration day”;" and "“person”.". The block has "completed) —", "claim” ;", "day” ;" and "“person” .". Replacement block:
(9) In regulation 61(1) (protection for full-time students until course completed)—
(a) for “notified person”, in the first place it occurs, substitute “person who has made a qualifying claim”;
(b) for “on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit” substitute “on the migration day”;
(c) for “notified person,” in the second place it occurs, substitute “person”.
  • Reg 29. Its only note is commencement information (<Commentary … Type="I">), not an amendment note. Label it "Commencement information (legislation.gov.uk):".
  • Hashes. Every hash reproduced for me, but a hash identifies one rendering of the file. A reviewer fetched reg 47 cached and uncached and got 36c0fcac… and e47f5312…, whose canonical XML is identical. That explains the hand-off's reg 47 mismatch. The intro could say each hash covers the file as served on 3 November 2026.

P3-13. Addendum: companion cases 1 and 5

  • Case 1: "We assume an award whose 12 assessment periods all start on or after 7 April 2025" leaves the end open. SR 2026/54 reg 1(4) gives £424.90 for "assessment periods commencing on or after" 6 April 2026. Replacement: "We assume an award whose 12 assessment periods all start on or after 7 April 2025 and before 6 April 2026, with no transitional element."
  • Case 5 rests "in every later one" on reg 57(1) alone. When the claimant forms the couple, their award ends and the couple are treated as claiming jointly. SR 2016/220 reg 8(8) reads: "A couple who are joint claimants are to be treated as making a claim for universal credit where— (a) one of them ceased to be entitled to an award of universal credit ... on the formation of that couple; and (b) the other member of the couple did not have an award ...". So the later assessment periods belong to a new award, where reg 58(1)(a) bars a transitional element; reg 58(3) treats the new award as a continuation only where reg 58(2) applies. The partner's missing notice matters because it stops the joint claim being a qualifying claim (reg 49), so the claim brings no fresh reg 61(1) protection. The case mentions the notice only beside reg 3(2), which doesn't depend on it. The expected values still follow. Replacement for the sentence that begins "So the Department applies reg 57(4)(b)": "The claimant's award ends when they form the couple, and since the partner has no award the couple are treated as claiming jointly (reg 8(8) of the Universal Credit, Personal Independence Payment, Jobseeker's Allowance and Employment and Support Allowance (Claims and Payments) Regulations (Northern Ireland) 2016). Had the claimant had a transitional element, it would stop from the assessment period in which they became a couple (reg 57(4)(b) and (1)), and no later award could include it, since earned income didn't end the claimant's award (reg 58(1)(a) and (2)(a)); so under reg 61(2) the Department doesn't apply reg 61(1) to the claimant in any of those assessment periods. The joint claim isn't a qualifying claim, since the partner isn't a notified person (regs 45(6) and 49), so it brings no new migration day and no protection under reg 61(1) for either member."
  • Case 5, last sentence. The "only" comes from Article 8(2)(a) of the Order ("Joint claimants are jointly entitled to universal credit if— (a) each of them meets the basic conditions"), which reg 3(1)(b) relaxes. Replacement: "Joint claimants are entitled only if each meets the basic conditions (Article 8(2)(a) of the Order), and reg 3(1)(b) relaxes that for Article 9(1)(d) only "if the other member does meet that condition or is excepted from it"; neither does here, so the couple has no joint entitlement."

P3-14. Writing

  • Body, Reported receipt, last sentence. The student keeps the protection, not the user or dataset. Replacement: "Cases 37 and 38 keep the report in 2026 and 2027 to stand for the award that closed, and where a user or dataset sets claims_uc_at_legacy_closure, the student keeps the protection without a report."
  • uc_receiving_education.yaml:1171-1172 (case 41) attaches "the Northern Ireland amount in SR 2025/64 Schedule 13" to £4,801.68, where Schedule 13 sets the monthly £400.14. Replacement: "With the assumptions of case 36, and the monthly £400.14 that the Department for Communities set in SR 2025/64 Schedule 13, 12 x £400.14 = £4,801.68."

Checked and sound

Setup. I ran everything in a scratch clone detached at f06a9c6, after uv sync --frozen --extra dev, with HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset. The host has 4 CPUs and also ran my other jobs, so my times exceed the body's.

Tests at the head

Run Result Body
uc_receiving_education.yaml 41 passed 41 passed
person_legacy_benefits_closed.yaml 3 passed 3
legacy_closure_ineligible_partner.yaml, legacy_closure_pension_route.yaml, legacy_closure_migrant_nations.yaml 1, 4 and 9 passed 1, 4 and 9
pytest -n 4 on test_uc_receiving_education_properties.py, test_uc_legacy_closure_properties.py and test_legacy_closure_scope.py 30 passed in 408.33 s (test_reg_60_protection_follows_the_closure_dates 270.62 s) 30 passed in 202 s
tests/policy/baseline/gov/dwp/universal_credit/ 57 passed (41 + 3 + 5 + 8 cases) 57
tests/policy/baseline/finance/benefit/family/universal_credit/ 563 passed in 67.96 s 563 in 36 s
test_parameter_descriptions.py; ::test_examples_reach_the_cases 24 passed; 1 passed 24; 1
pytest policyengine_uk/tests/code_health -n 4 3,095 passed 3,095
main at 2da212a with this head's two new test files YAML: 41 failed. Property file: 4 failed, and test_closure_grids_straddle_each_closure passed the same
uvx ruff@0.17.2 format --check .; ruff check .; ruff check --select F811,F821 policyengine_uk; git diff --check 4d3639206..f06a9c636 2177 files already formatted; all checks passed; all checks passed; clean clean
.github/check_fix_pr.py from main (the same file at 0080274 and 1d4d020), run as pr_fix_evidence.yaml runs it: live title and body, empty labels, git diff --name-status --no-renames -z from the merge base 0080274, a base checkout as --repo-root, --head-sha f06a9c6 Fix evidence: PASS ('Apply the Universal Credit education condition') - Fix PR has valid reproducer evidence. Changed executable tests: 2. PASS, 2

My mutants, and the author's last two rows. Each ran in a throwaway worktree, against the five YAML files and the property file with -n 4.

Mutant YAML Property file Verdict
B: claims_uc_at_legacy_closure returns legacy_benefits_closed alone, dropping would_claim_uc_at_legacy_closure Case 39 fails (claims_uc_at_legacy_closure@2025: [1.] differs from 0.0). One legacy_closure_pension_route.yaml case fails ("Protected mixed-age couple declining Universal Credit keeps the same Housing Benefit reclaim route"). The rest pass. 1 failed, 4 passed in 147.60 s: test_reg_60_protection_follows_the_closure_dates at :917, with "Mismatched elements: 8 / 40" on an explicit grid and "1 other explicit example also failed" Killed by the new property
A: tax_credits/active.yaml 2025-04-06: false changed to 2026-04-06: false Cases 36, 39 and 41 fail on legacy_benefits_closed@2025. In person_legacy_benefits_closed.yaml and the three legacy_closure_*.yaml files, 1 of 3, 1 of 1, 4 of 4 and 4 of 9 fail. 1 failed, 4 passed in 266.05 s: only test_closure_grids_straddle_each_closure (:942). The main property passes, because it reads the moved date. Killed by the guard and the cases. The main property follows the parameters by design.
A2 (extra): income_support/active.yaml 2026-04-01 changed to 2027-04-01 All pass (41, 3, 1, 4, 9) 1 failed, 4 passed in 293.87 s: only the guard (:945) Killed by the guard alone (P3-9)
Body row 10: drop migrated_student 7 of 41 fail: 19, 25, 26, 34, 36, 37 and 41. The legacy closure files pass. not rerun Matches the body
Body row 11: legacy_benefits_closed with all three active closed in that variable alone 1 of 41 fails: case 40. The legacy closure files pass. not rerun Matches the body

Invariant 9 and the grids. Apart from P2-1, invariant 9 states what the test asserts. The exception holds exactly when a reported legacy benefit's active reads false and would_claim_uc_at_legacy_closure holds; eligibility then equals ~full_time | expected. For this population the expected array reproduces legacy_award_is_closed: there is no Pension Credit or age route below 60, the claimant's own reports decide, no reg 14(1) limb applies, and no branch depends on region. The grids reach each side of each closure: tax credits are open in 2024 and closed in 2025, and Income Support and JSA are open in 2025 and closed in 2026. That follows from convert_to_fiscal_year_parameters sampling 30 April (utils/parameters.py:171), which the comment at :894-897 describes correctly. Only claims_universal_credit.py reads would_claim_uc, so tying it to the opposite of the closure flag costs invariant 9 nothing.

The law in cases 36 to 41. All from legislation.gov.uk data.xml, with point-in-time texts where dates matter.

  • Text in force. GB reg 60(1) read "notified person … as a consequence of a claim" from 24 July 2019 to 26 January 2025, with para (2) from 25 July 2022. From 27 January 2025 it reads "a person who has made a qualifying claim … on the migration day" (SI 2025/3 regs 1(2) and 6(9)(a) to (c)). NI reg 61(1) made the same change from 27 January 2025 (SR 2025/3 reg 6(9)), with para (2) from 1 August 2022. Cases 36, 39 and 41 (2025-26), 37 (2026-27) and 38 (2027-28) fall wholly under the current texts. Case 40 (2024-25) spans both, and its claimant, who claimed before any notice, is protected under neither.
  • Case 36. A notified person (reg 44(6)) who claims by the final deadline (reg 46(4)) makes a qualifying claim (reg 48), so the migration day falls under reg 49 and reg 60(1) protects them. 12 × £400.14 = £4,801.68, from gov.uk "Benefit and pension rates 2025 to 2026": "Single 25 or over | 393.45 | 400.14". It holds, subject to P3-3 and P3-4.
  • Case 37. Reg 56(2) needs first-period earnings at or above the threshold (reg 56(2)(a)(ii)). Reg 56(3A) needs a reg 60A(1) award, which needs State Pension Credit age. Neither reg 56(4)(b) nor reg 57 arises. 12 × £424.90 = £5,098.80, from gov.uk 2026 to 2027: "Single 25 or over | 400.14 | 424.90". It holds, subject to P3-5.
  • Case 38. "while they are continuing to undertake that course" is verbatim, and off the course reg 12(2) isn't met. The model reads only is_in_full_time_education for the full-time test (uc_receiving_education.py), and migrated_student ignores the course, so leaving the exception unasserted is right. gov.uk /government/publications/benefit-and-pension-rates-2027-to-2028 returns 404, and the newest edition gov.uk lists is 2026 to 2027. So "no rates for 2027 to 2028 by November 2026" holds today.
  • Case 39. Reg 48 counts a claim only "where the claim is made on or before the final deadline". For a tax credit notified person in 2025-26, the flags (would_claim_uc true, would_claim_uc_at_legacy_closure false) can only mean a late claim. The body's The claim bullet discloses the other reading.
  • Case 40. It holds under both texts; P3-2 widens its closing sentences.
  • Case 41. It follows SR 2016/226 regs 45(1), 45(6), 47(4), 49, 50 and 61(1), and Working Tax Credit is an existing benefit under NI reg 2(1). £400.14 comes from SR 2025/64 art 32(2) and Schedule 13 (as made), from the first assessment period on or after 7 April 2025 (art 1(1)(n)). It holds.
  • UC Regs 2013 reg 56 is "Employee involved in trade dispute", and no case relies on it. The cases' "reg. 56" is SI 2014/1230's, whose threshold comes from UC Regs reg 99(6)(a).

The variables. git diff 4d3639206 f06a9c636 -- policyengine_uk/variables touches three files (+23 −6), only in documentation strings and reference tuples. A reviewer's AST comparison with those removed finds the classes identical. Each provision the new text cites says what the text says:

  • NI reg 29 refers to "regulation 6 of the Universal Credit Regulations", and SR 2016/216 reg 6 matches UC Regs reg 5 apart from NI names.
  • NI reg 61 matches GB reg 60, with Article 9(1)(d) and regs 57 and 58 in their places. NI reg 49 matches GB reg 48, with reg 47(4) for reg 46(4).
  • GB reg 48 defines the qualifying claim.
  • NI UC reg 12(1) treats every qualifying young person as receiving education, with no training-scheme exception.
  • No formula has a regional branch or any reg 60(2) or 61(2) logic, so "the model doesn't apply reg. 61(2) either" holds.

The hand-off's line references are right: :36-40, :48, :29-34, :44, :23-25, :52-58 and :65-70.

The addendum.

  • All ten SHA-256 values reproduce.
  • Every block matches its data.xml word for word, curly quotes, "(2) , (3A)" and reg 61(2)'s closing ”. included, except for P3-12. That ”. is in legislation.gov.uk's own text of SR 2022/194 reg 11.
  • Each [...] stands for a real omitted paragraph, and nothing else is left out.
  • The dates hold: 27 September 2017, 24 July 2019 (SR 2019/152 regs 1(2) and 2(8)), 1 August 2022 (SR 2022/194 regs 1(1) and 11) and 27 January 2025 (SR 2025/3 reg 1(2), which defers only regs 6(2)(b) and 6(8)).
  • The cross-references hold, and NI reg 50 matches GB reg 49 word for word.
  • The companion cases' expected values follow:
    • £400.14 (SR 2025/64 Schedule 13, as made) and £424.90 (SR 2026/54 regs 1(4) and 3(3)(a));
    • case 3: a claim after the final deadline isn't a qualifying claim (reg 49), reg 46 lets the Department move the deadline day only "on the Department's own initiative" or on a request made "before the deadline day", and reg 48 was revoked from 1 August 2022;
    • case 4 under both texts;
    • case 5 to £0 (Article 8(2)(a)).

The description.

  • The live title and body are the brief's hub version (invariant 9 reads "if and only if").
  • The hub's three edits change no fact: "exactly when" becomes "if and only if" twice, "and anyone else" becomes "and finds anyone else", and the rewritten opening of the mutant paragraph keeps the author's facts (P3-7 is the author's).
  • Every backticked name exists at the head (a script ran git grep and git ls-tree over each one). The exception is esa_income_abolition_reached, which lives on fix-2562-esa-abolition (dc69f3a), as the body says.
  • In Which years, cases 19, 25, 26, 34 and 35 set claims_uc_at_legacy_closure: true for 2026, and test_education_condition_invariants draws it in YEARS = [2019, 2021, 2024, 2026] (:100, :225).
  • The departures bullets hold against the code and the law, apart from P3-1, P3-2, P3-5 and P3-6.
  • The test-plan counts match my reruns (table above).
  • My run reproduces the body's "Young people on legacy benefits" Working Tax Credit row at this head (£5,112.96; P2-2).

Merges. git merge-tree --write-tree --name-only f06a9c636 X exits 0 with no conflicted file for each of these:

X Tree
main 1d4d020 (moved past 52b3080; #2776 merged) b4efb8250b
52b3080 35f8ffb3f5
#2845 f379377 68b757b79b
#2279 c5b9850 1b2989076d
#2813 / fix-2562-esa-abolition dc69f3a eff04b1893
fix-2120-uc-carer-awards 260f6a7 fff3f53cc7

On the #2845 merge, uc_receiving_education.yaml gives 3 failed and 38 passed: cases 31, 33 and 34 fail on is_qualifying_young_person_for_universal_credit, and cases 36 to 41 pass. That matches the hand-off's account at d5d3207. P2-2 covers the follow-up change.

CI at f06a9c6. Every check has completed. Of 21 check runs, 20 succeeded:

  • Changes, Lint, Documentation build, Release build records the commit, and Install + smoke-import on Python 3.11 to 3.14;
  • Test / YAML (11:37Z), Test / Python 1/7 to 7/7 (the last at 12:38:23Z) and Test / Result (12:38:34Z);
  • Changelog fragments, Fix evidence and Aggregate impact (12:40:14Z).

One run, Fix evidence 41627920552, shows cancelled. Two pull_request_target events arrived at 11:29:25Z, and the workflow's concurrency group (cancel-in-progress for pull_request_target) cancelled the earlier run. Its successor, 41627920585, passed. Nothing failed.

What I couldn't check

  • rulespec-uk#514 lies outside this session's GitHub access, so I checked the brief's copy of the addendum (AXIOM_514_NI.md), not the posted comment.
  • I didn't rerun the body's differential row (two hand-built datasets), or the property halves of the eleven mutant rows. I reran the YAML halves of rows 10 and 11 and my own three mutants.
  • Merges with 2da212a, Value means-tested capital less 10% sale expenses and secured debt #1969 and Add overseas and other real estate to property wealth and capital #2688 weren't in this round's list, so I didn't rerun them.
  • Whether DWP set any tax credit deadline day after 6 April 2025, through reg 44(3) notices issued before 27 January 2025 or reg 45 extensions. P3-4 holds for deadline days on or before 6 April 2025.
  • Whether DWP read the pre-2025 reg 60(1) as covering a notified person who claimed after the deadline day but by the final deadline, whose awards ended under reg 46(1). The explanatory memorandum to SI 2025/3 doesn't say.
  • When the Department for Communities first issued migration notices in practice. I checked only the legal bar in reg 45(7).
  • The legal outcome for the body's W row after Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845 (P2-2). It turns on UC Regs reg 5(5), which no one in this round fetched and quoted.

Generated by Claude Code

@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 the new detailed reply at unchanged head f06a9c6362bc03b4247f275e9062d532296451f5. COMPLETE — no confirmed criticals; two nonblocking findings remain.

  • A1 — Narrow invariant 9 and both closure-test docstrings. The generator fixes the course at TERTIARY; “anyone else” overstates its population. A full-time non-advanced student with maintenance funding is already in case 15 and is correctly ineligible. Say “anyone among them not on a full-time course”, and exclude both explicitly supplied closure inputs when describing the fully derived route.
  • A2 — Complete the source references and precision edits. Add NI UC regulation 6 to the reference tuple that names it. In case 36, regulation 2(1) defines an existing benefit; regulation 44(1) supplies the notice power. Preserve the causal UC-claim condition when describing the pre-27 January 2025 regulation 60 text, and link the 2025 NI rates as made. These edits leave the verified monthly/annual amounts unchanged.

The #2845 concern is a useful integration handoff: the reported lost award needs a future merge plus an additional proposed formula change. It does not establish a wrong award at this head. When integrating that change, preserve migration-day status and cover the young tax-credit migrant; I have not certified the proposed patch or executed that future tree.

Prior passing code/final-UC/household-income evidence was retained after checking the unchanged code, dependency and artifact hashes. I checked the new source claims against the 56 retained official/queue records rather than rerunning unchanged tests. Exact-head CI is green (the cancelled duplicate Fix evidence run has a passing successor). Approval remains withheld while A1/A2 are open; the PR is also still a draft.

@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

…ons after review

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

Copy link
Copy Markdown
Collaborator Author

PR_TITLE.txt

Apply the Universal Credit education condition

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

PR_BODY.md

Fixes #2346

## What and why

Universal Credit has a basic condition that the claimant "is not receiving education" ([Welfare Reform Act 2012 s.4(1)(d)](https://www.legislation.gov.uk/ukpga/2012/5/section/4)). In the UC Regs 2013, the Secretary of State sets out who receives education ([reg 12](https://www.legislation.gov.uk/uksi/2013/376/regulation/12)), when a person undertakes a course ([reg 13](https://www.legislation.gov.uk/uksi/2013/376/regulation/13)) and who doesn't have to meet the condition ([reg 14(1)](https://www.legislation.gov.uk/uksi/2013/376/regulation/14)). `is_uc_eligible` on `main` read no education condition, so the model paid a full-time student who meets no exception. The issue's 19-year-old university student in Plymouth gets £6,348.91 on `main` at 2da212abc, the figure the issue reports.

This PR adds:

- **`is_in_full_time_education`** (Person, year), an input that stays false until a user or dataset sets it: whether the person takes a full-time course of study or training. The model reads the level from `current_education` and `in_HE`, and no formula derives the input from them. #2311 needs the same fact for Carer's Allowance and can read it; this PR doesn't change Carer's Allowance.
- **`has_education_maintenance_funding`** (Person, year), a second input that also stays false until a user or dataset sets it: whether the person has a student loan or grant for their maintenance on the course, the test in reg 12(2)(b). The model doesn't read `student_loans` or `education_grants` for it, since it can't tell from either amount what the support pays for. A learner aged 19 or over on a further education course can get [Learner Support](https://www.gov.uk/learner-support) for travel, accommodation, materials and equipment, a laptop or childcare, and only support for their maintenance meets reg 12(2)(b). In case 30, a student on a full-time course that doesn't lead to advanced education, with a £100 equipment grant, a £3,000 student loan and no maintenance funding, keeps an award of £5,098.80.
- **`is_qualifying_young_person_for_uc_education`**: the qualifying young person status the model reads for the condition. Under [SI 2014/1230 reg 28](https://www.legislation.gov.uk/uksi/2014/1230/regulation/28), a person entitled to an existing benefit doesn't count as a qualifying young person for the UC Regs. The Secretary of State lists the existing benefits in [reg 2(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/2): income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit. The model reads entitlement from the year's reported receipt, the evidence it reads for legacy closure. A claimant or partner on a couple's tax credit award counts as entitled, since the couple claims jointly ([Tax Credits Act 2002 s.3(3)(a)](https://www.legislation.gov.uk/ukpga/2002/21/section/3)). A person outside the definition under reg 28 takes the ordinary route under reg 12(2). This PR leaves `is_qualifying_young_person_for_universal_credit` as it stands: it doesn't apply reg 28, and the model still reads it for the child element, the benefit cap and Council Tax Reduction. #2804 tracks the same rule in `is_qualifying_young_person_for_universal_credit`.
- **`uc_receiving_education`** (reg 12). A person on a full-time course receives education where:
  - they take a course of advanced education under reg 12(2)(a) and (3), which the model reads from tertiary `current_education` or `in_HE`;
  - they have a student loan or grant for their maintenance under reg 12(2)(b), which the model reads from `has_education_maintenance_funding`;
  - or they count as a qualifying young person after reg 28, whom the decision maker treats as receiving education under reg 12(1A).
- **`uc_is_excepted_from_education_condition`** (reg 14(1), and [SI 2014/1230 reg 60](https://www.legislation.gov.uk/uksi/2014/1230/regulation/60)), with one sourced condition for each limb the model can express:
  - (b): entitlement to Attendance Allowance (including Armed Forces Independence Payment, which DWP counts as Attendance Allowance under reg 2), DLA or PIP, with limited capability for work (`uc_limited_capability_for_work`);
  - (c): responsibility for a child or qualifying young person in the benefit unit;
  - (d): a single foster parent, whom the model identifies from `uc_is_responsible_foster_parent_of_child_under_one` or `uc_is_foster_parent_or_new_friend_or_family_carer`;
  - (e)(ii): a member of a couple, both of whom receive education, whose partner fosters a child;
  - (f): the older member of a mixed-age couple;
  - SI 2014/1230 reg 60(1): a member of a family that claims when DWP moves it off its legacy benefits (`claims_uc_at_legacy_closure`), which the model takes as the qualifying claim under [reg 48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48). A person who receives education under reg 12(2) gets the protection; a qualifying young person under reg 12(1A) doesn't, while a person outside the definition under reg 28 takes reg 12(2) and gets it.
  - The model needs no rule of its own for limb (e)(i): it places responsibility for a child on the benefit unit, so both members meet (c).
- **`meets_uc_education_condition`** (s.4(1)(d)): not receiving education, or having an exception.
- **`is_uc_eligible`** needs one claimant (`is_uc_single_or_joint_claimant`) to meet the age condition and the education condition together, on every route.

### The couple rule

Under [reg 3(2)](https://www.legislation.gov.uk/uksi/2013/376/regulation/3), a couple can claim jointly where one member has reached the qualifying age for State Pension Credit "if the other member does meet that condition", or where one member receives education and has no exception "if the other member does meet that condition or is excepted from it". In [ADM E2016](https://assets.publishing.service.gov.uk/media/6a0474b6ee62840dba48a1eb/adme2.pdf), DWP accepts a joint claim where one member meets all the basic conditions. Under reg 14(1)(f), an older student can claim jointly with a younger partner who meets them. The model follows E2016, so one claimant must meet the age and education conditions together. A member of a couple who claims as a single person under reg 3(3) has no joint claimant beside them, so they must meet both conditions themselves.

For a couple aged 70 and 60 with no income in 2026-27, the model gives these awards:

| Who studies full-time | Award | Why |
|---|---:|---|
| Neither | £8,003.64 | The 60-year-old meets every basic condition (reg 3(2)(a)) |
| The 70-year-old | £8,003.64 | The 70-year-old has the reg 14(1)(f) exception, and the 60-year-old meets both conditions (reg 3(2)(a)) |
| The 60-year-old | nil | The 70-year-old meets the education condition and the 60-year-old the age condition, but neither meets both (ADM E2016) |
| Both | nil | The 70-year-old has the reg 14(1)(f) exception but still doesn't meet the age condition, so again neither meets both |

Each positive award comes to 12 × £666.97 = £8,003.64, DWP's joint rate where one or both claimants are 25 or over, from [Benefit and pension rates 2026 to 2027](https://www.gov.uk/government/publications/benefit-and-pension-rates-2026-to-2027/proposed-benefit-and-pension-rates-2026-to-2027). Since 15 May 2019 the couple can't claim Pension Credit instead (SPCA 2002 s.4(1A)).

The model keeps the condition on every route into `is_uc_eligible`:

| Route, 2026-27 unless stated | Neither studies | Older studies | Younger studies | Both study |
|---|---:|---:|---:|---:|
| Ordinary: aged 70 and 60 | £8,003.64 | £8,003.64 | nil | nil |
| Closure claim: aged 70 and 60, and under reg 60(1) neither member has to meet the condition | £8,003.64 | £8,003.64 | £8,003.64 | £8,003.64 |
| Reg 60A: aged 72 and 70 on Working Tax Credit, with a closure claim | £8,003.64 | £8,003.64 | £8,003.64 | £8,003.64 |
| Aged 72 and 70 on Working Tax Credit, no closure claim: the Pension Credit route | nil | nil | nil | nil |
| Pension Credit route: aged 70 and 60 in 2019-20, before 15 May 2019 | nil | nil | nil | nil |
| The same, with a closure claim: 12 × £498.89 | £5,986.68 | £5,986.68 | £5,986.68 | £5,986.68 |

For a couple aged 30 where one member takes a full-time degree and the other doesn't, the pair claim jointly at the couple rate, £8,003.64 a year with no income. Under reg 68, DWP counts a student's loan and some grants as student income, after statutory exclusions and disregards. The model leaves `student_loans` and `education_grants` out of its Universal Credit unearned income list (`gov.dwp.universal_credit.means_test.income_definitions.unearned`), so it counts no student income and the couple gets the full joint amount. If both members study and neither has an exception, the couple gets nothing.

### Young people on legacy benefits

An 18-year-old on a full-time course that doesn't lead to advanced education, who reports Working Tax Credit or Housing Benefit, keeps the award they get on `main`. Under reg 28 they don't count as qualifying young people, and with no maintenance funding they don't receive education under reg 12(2). One with maintenance funding receives education under reg 12(2)(b), and on a closure claim keeps the award under reg 60(1) (cases 34 and 42).

| 18-year-old, social rent £6,000, claims when DWP moves them off legacy benefits, 2026-27 | `main` | This PR | Arithmetic |
|---|---:|---:|---|
| Reports Working Tax Credit of £1,000 and earns £9,000 | £5,112.96 | £5,112.96 | 12 × £338.58 + £6,000 − 55% × £9,000 |
| Reports Housing Benefit of £6,000, with Housing Benefit awards closed | £10,062.96 | £10,062.96 | 12 × £338.58 + £6,000 |

### Limited capability for work

Under reg 14(1)(b), for a claim on or after 15 December 2021, the decision maker must determine limited capability for work (LCW) before the course starts. The model reads `uc_limited_capability_for_work` and holds no other LCW fact. The model can derive the LCW variable from disability status without an assessment, so it can keep students who have never had an LCW determination, as well as students with an assessment after their course began. A full-time student aged 30 on the standard daily living rate of PIP, with `is_disabled_for_benefits` true and no limited capability for work and work-related activity, gets £5,098.80 (12 × £424.90) in the model. The model doesn't infer LCW from an ESA award. A user who wants an ESA assessment to count sets `uc_limited_capability_for_work` or `uc_limited_capability_for_WRA`, and a user or dataset can set `is_disabled_for_benefits`, from which the model derives LCW.

### What the model can't express

The model can't express the parts of regs 12 to 14 and SI 2014/1230 regs 28 and 60 below.

- **Reg 14(1)(a)**: a person under 21, or 21 having reached that age during the course, on a full-time course that doesn't lead to advanced education, without parental support (reg 8(3)). The model has no input for parental support. Where a user flags such a student and they count as a qualifying young person, or have maintenance funding, the model gives nil where DWP must pay the award. Otherwise the model doesn't treat them as receiving education, and they keep the award.
- **Reg 14(1)(b) timing**: for a claim on or after 15 December 2021, the decision maker must determine LCW before the course starts (SI 2021/1224). For a claim from 5 August 2020 to 14 December 2021, SI 2020/827 required the determination by the date of claim, or by the course start where the course starts after the claim. The model reads any LCW, so a student with a late assessment keeps the award, where DWP must pay nil.
- **Reg 14(1)(b) benefits**: child disability payment, adult disability payment, pension age disability payment, Scottish adult disability living allowance, and the constant attendance and war pension payments that DWP counts as attendance allowance under reg 2. The model has no variables for them. A student whose one listed benefit is among these gets nil, where DWP must pay the award.
- **Foster parents (reg 14(1)(d) and (e)(ii))**: if a user sets neither input for a foster parent, the model gives nil where DWP must pay the award. A user can also set the second input for a foster parent between placements, who keeps the award in the model, where DWP must pay nil.
- **Reg 12(1A)**: if a user doesn't set the input for a qualifying young person, the model doesn't treat them as receiving education, and they keep the award. A flagged one in a relevant training scheme (reg 12(1B)) gets nil, where DWP must pay the award. #2279 adds a group to this gap: under reg 5(1)(a), it counts a 16-year-old before the 1 September after their 16th birthday as a qualifying young person whatever their education. With both PRs merged, a 16-year-old carer outside education who claims alone with no income in 2026-27 counts as a qualifying young person, doesn't receive education in the model and gets £6,575.04, where DWP must pay nil under reg 12(1A). The author of whichever PR lands second must apply reg 12(1A) to that route without the full-time flag and add the 16-year-old carer case as a regression test.
- **Reg 12(4)**: the Secretary of State treats a claimant as receiving education when their course conflicts with a work-related requirement the Secretary of State imposes. The model keeps their award.
- **Reg 13(4)**: a person who leaves a course with the establishment's consent because of illness or caring, recovered or stopped caring within the past year, hasn't resumed and can't get a grant or loan doesn't undertake the course in that time. Set the input false only when all of these hold.
- **SI 2014/1230 reg 28**: the model reads the year's reported receipt of an existing benefit in place of entitlement on a given day. The model removes the status for the whole year when a person reports receipt for part of it, or reports a tax credit in 2025-26 or later although no award exists, and keeps the status for a person on a nil-rate award.

#### Where the annual closure proxy departs from reg 60

The model applies the [reg 60(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/60) protection to a claimant or partner who doesn't count as a qualifying young person, in a family that claims at closure (`claims_uc_at_legacy_closure`), in every model year in which that flag holds. Unless a user or dataset sets the flag, the model derives it from `legacy_benefits_closed` and `would_claim_uc_at_legacy_closure`, and unless a user or dataset sets `legacy_benefits_closed`, it takes that closure from the `active` parameters. Against reg 60, and SR 2016/226 reg 61 in Northern Ireland, the model departs in the ways below. Each gain or loss concerns a full-time student with no reg 14(1) exception whose family has no other claimant who meets both the age and the education conditions, such as the single claimant of cases 36 to 41 or the younger member of case 25's mixed-age couple.

- **Migration day.** Under reg 60(1), DWP lifts the condition for a student on the course on the migration day, the day before the Universal Credit award starts ([reg 49](https://www.legislation.gov.uk/uksi/2014/1230/regulation/49)). With the closure taken from the `active` parameters, the model protects a student only from the model year in which their reported benefit closes: 2025-26 for tax credits, 2026-27 for Income Support and income-based JSA. A tax credit family whose Universal Credit award started in 2024-25 has its migration day in that year, where, on the parameters alone, the model protects nobody (case 40). So for 2024-25 the model gives no Universal Credit to a student in that family whom DWP protects under reg 60(1). Likewise, on the parameters alone, the model gives no Universal Credit before 2026-27 to a student whom DWP protects under reg 60(1) after an earlier migration from Income Support or income-based JSA. The model also never applies the text in force before 27 January 2025, under which DWP lifted the condition for a notified person who was on a full-time course "on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit" ([SI 2025/3 reg 6(9)](https://www.legislation.gov.uk/uksi/2025/3/regulation/6/made)). Where, for a year before 2025-26, a user or dataset sets `claims_uc_at_legacy_closure` true, as `test_education_condition_invariants` does in 2019, 2021 and 2024, or sets `legacy_benefits_closed` true for a family whose `would_claim_uc_at_legacy_closure` holds, the model protects every claimant and partner in the family who doesn't count as a qualifying young person. DWP protected no one under reg 60 before 24 July 2019, when SI 2019/1152 inserted it, then only a notified person until 26 January 2025, and from 27 January 2025 only a person who had made a qualifying claim (reg 48); in Northern Ireland the Department for Communities could issue no migration notice before 25 July 2022 (SR 2016/226 reg 45(7)). So a partner whom DWP never notified gains, and so does everyone the model protects before 24 July 2019, or in Northern Ireland before 25 July 2022.
- **The same course.** Under reg 60(1), DWP lifts the condition only "while they are continuing to undertake that course", the course the student undertook on the migration day. The model reads `is_in_full_time_education` year by year, keeps no record of which course a student takes, and keeps the exception whatever the course, or none, as case 38's comment notes and `test_reg_60_protection_follows_the_closure_dates` checks. A student who finishes that course and starts another full-time course keeps the award in the model, where DWP applies the condition again and pays nil. So does a claimant who wasn't on a full-time course on the migration day and starts one later, to whom DWP never applies reg 60(1).
- **Reg 60(2)** (NI reg 61(2)), which the model never applies. Under it, DWP (in Northern Ireland, the Department for Communities) stops the protection in any assessment period in which a transitional element would have stopped under [reg 56](https://www.legislation.gov.uk/uksi/2014/1230/regulation/56) or [reg 57](https://www.legislation.gov.uk/uksi/2014/1230/regulation/57) (NI regs 57 and 58), and under reg 56(1) in every later one:
  - from the assessment period after three in a row of earned income below the administrative earnings threshold, where the first assessment period's earned income reached it, counting a claimant to whom the minimum income floor applies, or would apply but for a start-up period, as reaching it (reg 56(2) and (3));
  - for an award under reg 60A (NI reg 61A), from an assessment period after the first 12 that follows three in a row of earned income below 16 hours a week at the national minimum wage (reg 56(3A));
  - when a single claimant joins a couple, unless they can still claim as a single person under UC Regs reg 3(3) (NI UC Regs reg 3(2)), or when joint claimants separate or join another couple (reg 56(4));
  - once the award ends, unless earnings ended it and a new award starts within three months (reg 57).

  The model keeps the student's exception through each, so the student keeps the award in the model where DWP applies the condition again. Case 37 works through these events for a claimant with no earnings, who stays single and keeps the award.
- **The claim.** The model has no deadline day. It treats a claim at closure (`claims_uc_at_legacy_closure`, which it derives from `legacy_benefits_closed` and `would_claim_uc_at_legacy_closure` unless a user or dataset sets it) as the qualifying claim under [reg 48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48), and any other claim (`would_claim_uc` alone) as a claim outside the migration, as cases 36 and 39 show. A family that claimed of its own accord by the final deadline after its migration notice made a qualifying claim, so where a user or dataset sets `would_claim_uc_at_legacy_closure` false for it, its student loses the award in the model, where DWP pays it. A family that claimed before DWP issued it a migration notice didn't claim as a notified person (reg 44(6)), so its claim doesn't count as a qualifying claim (reg 48). Yet if it still reports the closed benefit and keeps the default `would_claim_uc_at_legacy_closure`, its student gains the award in the model, where DWP pays nil. Nor does the model check that DWP issued a migration notice to both joint claimants (reg 48), so a couple whose second member never got one gains the protection.
- **Reported receipt.** As for reg 28, the model reads the year's report in place of entitlement to an existing benefit on a given day (reg 44(1)), and it keeps reading a legacy benefit report after the award closed. With the flag derived, a student who reports a closed benefit for part of a year counts as migrated for the whole year, and gains the award for the months before their migration day (reg 49). A student whose report stops in a later year, as the report of an award that ended would, loses the protection in the model from that year, where DWP keeps it while they continue the course (reg 60(1)). A student who doesn't report the award DWP moved them off gets no protection in the model. Cases 37 and 38 keep the report in 2026 and 2027 to stand for the award that closed, and where a user or dataset sets `claims_uc_at_legacy_closure`, the student keeps the protection without a report.

### Northern Ireland

Claimants in Northern Ireland meet the same condition under the [Welfare Reform (Northern Ireland) Order 2015 art. 9(1)(d)](https://www.legislation.gov.uk/nisi/2015/2006/article/9). The Universal Credit Regulations (Northern Ireland) 2016, which the Secretary of State made under Article 4(1)(a) of the 2015 Order, have GB reg 3(2) as their [reg 3(1)](https://www.legislation.gov.uk/nisr/2016/216/regulation/3), and GB [reg 13](https://www.legislation.gov.uk/nisr/2016/216/regulation/13) and [reg 14(1)](https://www.legislation.gov.uk/nisr/2016/216/regulation/14) under the same numbers. Under their [reg 12(1)](https://www.legislation.gov.uk/nisr/2016/216/regulation/12), the decision maker treats every qualifying young person as receiving education, with no exception for a training scheme, which the model doesn't apply in Great Britain either. So the model's one rule covers Northern Ireland, and case 21 tests it there.

The Department for Communities applies the transitional provisions this PR follows under the [Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016](https://www.legislation.gov.uk/nisr/2016/226) (SR 2016/226), with these dates from legislation.gov.uk:

| GB, SI 2014/1230 | NI, [SR 2016/226](https://www.legislation.gov.uk/nisr/2016/226) | In operation in NI |
|---|---|---|
| reg 28, meaning of "qualifying young person" | [reg 29](https://www.legislation.gov.uk/nisr/2016/226/regulation/29) | From 27 September 2017. SR 2016/226 came into operation immediately after the NI Universal Credit Regulations (reg 1), which come into operation on the day on which Article 6 of the Welfare Reform (NI) Order 2015 "comes into force for all purposes in respect of the whole or any part of Northern Ireland" (their reg 1); legislation.gov.uk dates both from 27 September 2017, when Article 6 came into force for claims in the first districts ([SR 2017/190](https://www.legislation.gov.uk/nisr/2017/190) art 4(3) to (5) and Schedule 1). |
| reg 48, meaning of "qualifying claim" | [reg 49](https://www.legislation.gov.uk/nisr/2016/226/regulation/49) | From 24 July 2019, when Part 4 came in ([SR 2019/152](https://www.legislation.gov.uk/nisr/2019/152)). |
| reg 60, protection for full-time students | [reg 61](https://www.legislation.gov.uk/nisr/2016/226/regulation/61) | From 24 July 2019 (SR 2019/152). The Department for Communities has applied paragraph (2) since 1 August 2022 ([SR 2022/194](https://www.legislation.gov.uk/nisr/2022/194) reg 11). Since 27 January 2025 it has applied paragraph (1) to a person who has made a qualifying claim and is on the course on the migration day; before then it applied it to a notified person on the course on the day on which all their existing awards were to terminate as a consequence of a claim for universal credit ([SR 2025/3](https://www.legislation.gov.uk/nisr/2025/3) reg 6(9)). |

NI regs 29, 49 and 61 match GB regs 28, 48 and 60, with NI cross-references: Article 9(1)(d) of the 2015 Order for section 4(1)(d) of the 2012 Act, reg 6 of the NI Universal Credit Regulations for reg 5, reg 47(4) for reg 46(4), and regs 57 and 58 for regs 56 and 57. So the model's GB routes carry over to Northern Ireland unchanged, and its departures from reg 60 above apply to reg 61 too. Case 41 tests reg 61(1): the student of case 36, in Northern Ireland, keeps 12 × £400.14 = £4,801.68 in 2025-26, at the monthly standard allowance of £400.14 that the Department for Communities set in the [Social Security Benefits Up-rating Order (Northern Ireland) 2025](https://www.legislation.gov.uk/nisr/2025/64/schedule/13/made) art 32(2) and Schedule 13, as made.

### Which years

The change reaches every year the model calculates Universal Credit eligibility. No parameter or date limits it: regs 12 and 14 have applied since 29 April 2013, reg 28 since 16 June 2014 with no amendment, and the dated changes to reg 14(1)(b) concern its timing and the Scottish benefits, which the model doesn't read. In model year 2014 the model raises `ParameterNotFoundError` for `gov.dwp.pension_credit.mixed_age_couples.saving_latest_birth_date`, on `main` too. Case 22 tests 2016.

Where the model takes the closure from the `active` parameters, it applies the reg 60(1) protection only from 2025-26 for tax credits and from 2026-27 for Income Support and income-based JSA, in Great Britain and Northern Ireland alike. So on the parameters alone, the model protects no one in 2024-25 or earlier, and never reaches a year in which only the text in force before 27 January 2025 applied. A user or dataset can also set `claims_uc_at_legacy_closure` true for any year, or `legacy_benefits_closed` true for a family whose `would_claim_uc_at_legacy_closure` holds (its default), and the model then applies the protection in that year: cases 19, 25, 26, 34 and 35 set the first for 2026, and `test_education_condition_invariants` draws it for 2019, 2021, 2024 and 2026. Cases 36 to 42 test 2024 to 2027 with the closure taken from the parameters, and `test_reg_60_protection_follows_the_closure_dates` draws its model year from 2020 to 2028, besides its grids for 2024 to 2026.

### Data

This repository holds no survey-backed record of full-time study or maintenance funding. `git grep` finds:

- `current_education`, the enrolment stage;
- `in_HE` and `in_FE`;
- `employment_status`, with a `STUDENT` category;
- `student_loans` and `education_grants`, amounts that don't say what they pay for;
- `childcare_grant_full_time_student`, an input no dataset supplies.

Both new inputs stay false on current data until a dataset supplies them, so aggregates don't move. With neither input set, the new variables still evaluate: they give everyone `meets_uc_education_condition` true, so `is_uc_eligible` gives `main`'s result. On two hand-built datasets with neither input, every variable both versions define gives `main`'s values.

### Overlapping work

`git merge-tree` merges this branch cleanly with `main` at 2da212abc, 52b30809f and 1d4d020aa, #1969 (b4e872273), #2279 (c5b9850a3), #2688 (0d760fccd), #2845 (f379377f9), `fix-2120-uc-carer-awards` (260f6a77e) and `fix-2562-esa-abolition` (#2813, dc69f3ada).

- **#2279** reads raw reports of Universal Credit, ESA of either kind and JSA of either kind, and excludes a young person who reports any of them from qualifying young person status. Under UC Regs reg 5(5), DWP excludes a young person on Universal Credit, contributory ESA or contribution-based JSA, and under SI 2014/1230 reg 28 one on income-related ESA or income-based JSA. Merged with this branch, an 18-year-old on full-time A levels who reports Universal Credit or contributory ESA no longer counts as a qualifying young person, and gets £4,062.96 and £1,062.96 respectively. This branch alone gives nil, since it reads reg 5(5) only from `receives_benefits_in_own_right`. #2279's reports of income-related ESA and income-based JSA overlap this branch's reg 28 list, and applying both gives the same result. The reg 5(1)(a) route needs the change in the reg 12(1A) bullet above.
- **#2845** applies SI 2014/1230 reg 28 in `is_qualifying_young_person_for_universal_credit` and reads a report only while the model pays that benefit. With both merged, cases 31, 33 and 34 fail on that variable, and whichever PR lands second makes `is_qualifying_young_person_for_uc_education` return it. On its own, that change stops the reg 60 gate reading a closed report. An 18-year-old on a full-time non-advanced course with maintenance funding, whose closure the model derives from a Working Tax Credit report, would then count as a qualifying young person and lose the reg 60(1) protection, although on the migration day reg 28 still applied and the student received education under reg 12(2)(b). Case 42 tests that migrant, and fails under that change alone. So the author of whichever PR lands second keeps the gate on the migration-day status, which reads the report of the award that the claim ended, and keeps case 42 passing. With that gate, the model still gives £5,112.96 in the 2026-27 Working Tax Credit row of "Young people on legacy benefits", but through the reg 60(1) exception rather than reg 28; whether DWP pays that young person once the tax credit award has ended turns on UC Regs reg 5(5), which this description doesn't settle.
- **`fix-2562-esa-abolition`** widens `claims_uc_at_legacy_closure` to families whose income-related ESA award the abolition reaches (`esa_income_abolition_reached`). This branch then gives reg 60 protection to every claimant and partner in those families who doesn't count as a qualifying young person. DWP gives reg 60(1) protection only to a person "who has made a qualifying claim", which the Secretary of State defines in [reg 48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48) as "a claim for universal credit by a single claimant who is a notified person or by joint claimants, both of whom are notified persons, where the claim is made on or before the final deadline". Abolition alone does not make their claim qualifying: under regs [44(6)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/44) and [48](https://www.legislation.gov.uk/uksi/2014/1230/regulation/48), DWP must have issued a migration notice to each claimant, and they must claim by the final deadline. The #2562 author should distinguish that qualification from the abolition-triggered claim proxy before the model gives reg 60 protection. The author of whichever PR lands second carries that integration fix.

## Invariants

These hold for every input:

1. **Never raises.** Setting `is_in_full_time_education` never raises a benefit unit's Universal Credit. A family in a household of its own gets either its `main` amount or nil.
2. **Unflagged households don't change.** In a household where no one has the input set, every output equals `main`'s.
   - When the household head's family includes a student and can't claim, its non-dependants count in the next claim by a family liable for the rent, whose award then falls by the housing cost contribution. Under Sch 4 para 9(2)(f), DWP doesn't count a person as a non-dependant in a claim if it already counts them in another claim by someone liable for the rent. The model assigns non-dependants to the household head's eligible claim first.
   - In the cross-family test, a joint tenant whose own family has no one flagged loses 12 × £96.55 = £1,158.60 in 2026-27 (Sch 4 para 14(1), as SI 2026/148 art. 32(3) sets it).
3. **Exceptions keep the claimant in.** A claimant who meets a modelled reg 14 exception, or reg 60, meets the condition, and a family whose claimants all meet it gets its `main` Universal Credit.
4. **The couple decides.** The input on a child or qualifying young person in the benefit unit never changes its Universal Credit.
5. **One member meets both.** Every eligible family that makes no closure claim has a claimant who meets the minimum age, hasn't reached the qualifying age for State Pension Credit and meets the education condition.
6. **Maintenance funding comes from the input.** Removing every student loan and education grant changes no one's receiving education and no Universal Credit output.
7. **Reg 28.** No one who reports an existing benefit counts as a qualifying young person for the condition.
8. **Reference agreement.** Receiving education, the exceptions, the reg 28 status and `is_uc_eligible` equal a reference implementation, which reads the inputs and applies regs 3(2), 12 and 14 as DWP reads them in ADM E2016, and SI 2014/1230 regs 28 and 60. Without the education condition, the reference gives `main`'s eligibility.
9. **Closure dates.** For single people aged 25 to 59 with no child, no listed disability benefit, no foster role, no capital and no other benefit report, where the model derives `claims_uc_at_legacy_closure` (no user or dataset sets it or `legacy_benefits_closed`), `uc_is_excepted_from_education_condition` holds if and only if, in that model year, they report a legacy benefit whose `active` parameter reads false and their family claims at closure (`would_claim_uc_at_legacy_closure`). The model then finds a full-time degree student among them eligible if and only if the exception holds, and finds anyone among them who isn't on a full-time course eligible either way.

We check the first eight with Hypothesis in `policyengine_uk/tests/test_uc_receiving_education_properties.py`, for families in households of their own:

- 15 drawn cases plus 4 explicit ones;
- up to four families each, in 2019, 2021, 2024 or 2026, drawing Attendance Allowance, Armed Forces Independence Payment, DLA, PIP, the maintenance input and reported Housing Benefit and Working Tax Credit;
- one simulation that calculates each case five ways: as drawn, with no one flagged, with the non-members' flags reversed, with no loans or grants, and under `main`'s `is_uc_eligible` formula, which the test copies with its comments removed; the formatter re-wrapped two lines.

`test_student_head_moves_non_dependant_to_joint_tenant` pins the cross-family effect.

`test_reg_60_protection_follows_the_closure_dates` checks invariant 9:

- 40 drawn cases plus explicit grids of 48 people for 2024, 2025 and 2026, the years either side of each closure: in North East England and in Northern Ireland, no legacy report, each alone, and Child Tax Credit with Income Support, which the model closes in different years, with `would_claim_uc_at_legacy_closure` true or false (and `would_claim_uc` the opposite), on a full-time degree or not;
- a model year from 2020 to 2028 and up to 24 single people, for each of whom Hypothesis draws the four legacy reports (Child Tax Credit, Working Tax Credit, Income Support and income-based JSA), both claim flags, a region, Northern Ireland included, and a full-time degree;
- one simulation for each case. The test reads the closure dates from the `active` parameters, not from constants, and `test_closure_grids_straddle_each_closure` checks that the grid years, which the test shares with the grids, still fall either side of each closure.

## Test plan

- [x] The YAML tests use legislation and DWP rates for their expected values: the 2026-27 rates from DWP's Benefit and pension rates 2026 to 2027, the 2025-26 rate from DWP's Benefit and pension rates 2025 to 2026 and, for Northern Ireland, the Social Security Benefits Up-rating Order (Northern Ireland) 2025 Schedule 13, and the taper from reg 22(1)(b); `test_student_head_moves_non_dependant_to_joint_tenant` takes the non-dependant contribution from SI 2026/148 art. 32(3). Case 2's housing costs element comes from the model's Universal Credit LHA rate for Plymouth (£400 a month), and its total matches `main`.
- [x] `make format` equivalent on the changed files: `uvx ruff@0.17.2 format --check`, `uvx ruff@0.17.2 check`, `uvx ruff@0.17.2 check --select F811,F821 policyengine_uk` and `git diff --check`, all clean.
- [x] Changelog fragment `changelog.d/2346.fixed.md`.

Results, each run alone without survey data (`HUGGING_FACE_TOKEN` and `POLICYENGINE_UK_DEFAULT_DATASET` unset):

| Run | Branch | `main` (1d4d020aa) |
|---|---|---|
| `tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml` (new) | 42 passed | 42 failed (the variables don't exist) |
| `tests/test_uc_receiving_education_properties.py::test_examples_reach_the_cases` | 1 passed | |
| `tests/test_uc_receiving_education_properties.py` (new, all five tests), with `test_uc_legacy_closure_properties.py` (4) and `test_legacy_closure_scope.py` (21), `-n 4` | 30 passed in 246 s | this file: 4 failed (the input doesn't exist); `test_closure_grids_straddle_each_closure`, which reads only parameters, passed |
| `tests/test_parameter_descriptions.py` | 24 passed | |
| Differential, every variable, two hand-built datasets with neither input (380 people, 240 benefit units of which 125 get Universal Credit, 180 households), at an earlier head; no formula has changed since | 1,387 shared variables × 2 datasets = 2,774 identical, none raising | |
| Existing `tests/policy/baseline/finance/benefit/family/universal_credit/` (directory) | 563 passed in 43 s | |
| `tests/policy/baseline/gov/dwp/universal_credit/` (directory, the 42 new cases and 16 existing ones) | 58 passed | |
| `person_legacy_benefits_closed.yaml` and the three `legacy_closure_*.yaml` files under `tests/policy/baseline/` | 3, 1, 4 and 9 passed | |
| `tests/code_health` (`-n 4`) | 3,095 passed | |

We ran each mutant on a copy of the code, first against the YAML cases and then against the property file. For the first nine mutants, the YAML runs, against the 35 cases, and the ninth mutant's property run used db140e0ca; their other property runs used 13bbf82bf, and the two with shrinking off used 27e004ace. At all three commits every formula matches this head, and the property file's code matches it apart from docstrings, documentation and the closure tests, which these runs predate. At db140e0ca the YAML file differs from this head only in comments and cases 36 to 41. The last two rows ran at f06a9c636, on all 41 cases, `person_legacy_benefits_closed.yaml`, the three `legacy_closure_*.yaml` files and the property files; the legacy closure YAML files pass under both.

| Mutant | YAML cases failing | Property tests |
|---|---|---|
| Keep PIP alone in reg 14(1)(b), dropping Attendance Allowance, AFIP and DLA | 3 of 35 (cases 27, 28 and 29) | no failure before the 1,500 s cap with `-x`; with shrinking off, fails at the reference differential after 1,055 s |
| Drop the maintenance funding limb of reg 12(2)(b) | 3 of 35 (cases 15, 32 and 34) | fails (287 s) |
| Test the age and education conditions on any claimant separately, so no member need meet both | 2 of 35 (cases 7 and 8) | fails on an explicit example (137 s) |
| Drop reg 28, reading `is_qualifying_young_person_for_universal_credit` | 4 of 35 (cases 31, 32, 33 and 34) | fails (131 s) |
| Read `student_loans` and `education_grants` in place of `has_education_maintenance_funding` | 4 of 35 (cases 15, 30, 32 and 34) | fails (322 s) |
| Drop reg 14(1)(f) | 2 of 35 (cases 7 and 23) | fails (573 s) |
| Apply reg 28 to the person's own reports, without the joint tax credit award | 1 of 35 (case 33) | no failure before the 1,500 s cap with `-x`; with shrinking off, passes all 19 examples |
| Drop the education condition from the reg 60A route | 1 of 35 (case 35) | no failure before the 1,500 s cap with `-x` |
| Read `is_qualifying_young_person_for_universal_credit`, before reg 28, for the reg 60 protection | 1 of 35 (case 34) | `test_examples_reach_the_cases` fails (64 s) |
| Drop `migrated_student`, the reg 60(1) limb, from `uc_is_excepted_from_education_condition` | 7 of 41 (cases 19, 25, 26, 34, 36, 37 and 41) | 3 of 5 fail (24 s): `test_reg_60_protection_follows_the_closure_dates` on its 2025 and 2026 grids, `test_education_condition_invariants` on an explicit example, and `test_examples_reach_the_cases` |
| Make `legacy_benefits_closed` close any reported legacy benefit, ignoring the `active` parameters | 1 of 41 (case 40) | 1 of 5 fails (77 s): `test_reg_60_protection_follows_the_closure_dates` on its 2024 and 2025 grids; in the legacy closure property files, 3 of 4 tests in `test_uc_legacy_closure_properties.py` and 1 of 21 in `test_legacy_closure_scope.py` fail (442 s) |

The YAML cases kill all eleven mutants. A property run that reaches its cap without a failure only shows that the cases it reached in time don't kill that mutant; several runs shared the host, so the times also vary with load.

Case 35 pins the reg 60A gate. With a closure claim, every claimant who isn't a qualifying young person meets the condition under reg 60(1), and a claimant or partner on the couple's tax credit award loses that status under reg 28. A claimant responsible for a child or qualifying young person in the benefit unit meets reg 14(1)(c). The gate changes the result only where the claimant is a qualifying young person and an adult in the benefit unit who is neither claimant, partner, child nor qualifying young person reports Working Tax Credit, as in case 35. The model keeps the gate because under reg 60A the Secretary of State waives the age condition and nothing else. The model's reg 60A route reads Working Tax Credit from every member of the benefit unit, where under reg 60A(1) the single claimant, or both joint claimants, must be "entitled to an award of working tax credit"; that predates this PR, and we set it out in #2805.

No existing test's expected value changes.

## Reproducer

Reproducer: `policyengine_uk/tests/test_uc_receiving_education_properties.py::test_examples_reach_the_cases`; `policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml`

axiom: TheAxiomFoundation/rulespec-uk#514 queued

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


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

AXIOM_514_NI.md

## Northern Ireland addendum: SR 2016/226 regs 29, 49, 50 and 61

Above, we asked for SI 2014/1230 regs 28 and 60 and quoted reg 48, and our Northern Ireland section covered SR 2016/216 alone. In Northern Ireland, the Department for Communities applies the same transitional rules under the Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016 (SR 2016/226). Below, we give their text, their dates and companion cases that we work from that text. In policyengine-uk, `is_qualifying_young_person_for_uc_education` and `uc_receiving_education` cite reg 29, and `uc_is_excepted_from_education_condition` cites regs 29, 49 and 61; its formula applies reg 61(1) through `claims_uc_at_legacy_closure`, with no rule of its own for Northern Ireland, and doesn't apply reg 61(2). Case 41 of `policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml` tests reg 61(1).

### Verbatim law

legislation.gov.uk published this text, in force on 3 November 2026, with these amendment notes, in each regulation's data.xml; each SHA-256 identifies the file as legislation.gov.uk served it on 3 November 2026.

#### SR 2016/226 reg 29, meaning of "qualifying young person"

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/29 (data.xml SHA-256 `6f41b7bc49cf735a78f424b02487c5d66fcde4f035deb156922cf6b830b33109`)

```text
PART 2 CHAPTER 2
EFFECT OF TRANSITION TO UNIVERSAL CREDIT
Meaning of “qualifying young person”
 (29) Where a person who would (apart from the provision made by this regulation) be a “qualifying young person” within the meaning of regulation 6 of the Universal Credit Regulations is entitled to an existing benefit—
 (a) that person is not a qualifying young person for the purposes of the Universal Credit Regulations; and
 (b) regulation 6(5) of those Regulations applies as if, after “a person who is receiving” there were inserted “an existing benefit (within the meaning of the Universal Credit (Transitional Provisions) Regulations (Northern Ireland) 2016),”.

Commencement information (legislation.gov.uk):
- Reg. 29 coming into operation in accordance with reg. 1
```

#### SR 2016/226 reg 49, meaning of "qualifying claim"

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/49 (data.xml SHA-256 `d90f94900f1293627e4adc8e917630eaefe67e92254a45309b67ee8040dbb9ef`)

```text
PART 4
MANAGED MIGRATION TO UNIVERSAL CREDIT
Transitional Protection
Meaning of “qualifying claim”
 (49) A “qualifying claim” is a claim for universal credit by a single claimant who is a notified person or by joint claimants, both of whom are notified persons, where the claim is made on or before the final deadline (see regulation 47(4)).

Amendment notes (legislation.gov.uk):
- Pt. 4 added (24.7.2019) by The Universal Credit (Managed Migration and Miscellaneous Amendments) Regulations (Northern Ireland) 2019 (S.R. 2019/152), regs. 1(2), 2(8)
```

#### SR 2016/226 reg 50, meaning of "migration day"

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/50 (data.xml SHA-256 `ee2e793edc167e7d33768b1c6b239f362d0aed913b86acad6289d191b48ba20c`)

```text
PART 4
MANAGED MIGRATION TO UNIVERSAL CREDIT
Transitional Protection
Meaning of “migration day”
 (50) “Migration day”, in relation to a qualifying claim, means the day before the first day on which the claimant is entitled to universal credit in connection with that claim.

Amendment notes (legislation.gov.uk):
- Pt. 4 added (24.7.2019) by The Universal Credit (Managed Migration and Miscellaneous Amendments) Regulations (Northern Ireland) 2019 (S.R. 2019/152), regs. 1(2), 2(8)
```

#### SR 2016/226 reg 61, protection for full-time students

Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/61 (data.xml SHA-256 `bd5fdb8a6243da0ff008c5f3afdb367f7f3d117147771b032132153ffb195c40`)

```text
PART 4
MANAGED MIGRATION TO UNIVERSAL CREDIT
Miscellaneous
Protection for full-time students until course completed
 (61)
 (1) Where a person who has made a qualifying claim does not meet the basic condition in Article 9(1)(d) of the Order (not receiving education) on the migration day because the person is undertaking a full-time course (see regulation 12(2) and 13 of the Universal Credit Regulations), that condition is not to apply in relation to the person while they are continuing to undertake that course.
 (2) Paragraph (1) does not apply to any assessment period in respect of which a transitional element or transitional capital disregard would (if the claimant had been entitled to that element or that disregard) have ceased to apply by virtue of regulation 57 (circumstances in which transitional protection ceases) or regulation 58 (application of transitional protection to a subsequent award).”.

Amendment notes (legislation.gov.uk):
- Pt. 4 added (24.7.2019) by The Universal Credit (Managed Migration and Miscellaneous Amendments) Regulations (Northern Ireland) 2019 (S.R. 2019/152), regs. 1(2), 2(8)
- Reg. 61 renumbered as reg. 61(1) (1.8.2022) by The Universal Credit (Transitional Provisions) (Amendment) Regulations (Northern Ireland) 2022 (S.R. 2022/194), regs. 1(1), 11
- Reg. 61(2) inserted (1.8.2022) by The Universal Credit (Transitional Provisions) (Amendment) Regulations (Northern Ireland) 2022 (S.R. 2022/194), regs. 1(1), 11
- Words in reg. 61(1) substituted (27.1.2025) by The Social Security (Miscellaneous Amendments) Regulations (Northern Ireland) 2025 (S.R. 2025/3), regs. 1(2), 6(9)(a)
- Words in reg. 61(1) substituted (27.1.2025) by The Social Security (Miscellaneous Amendments) Regulations (Northern Ireland) 2025 (S.R. 2025/3), regs. 1(2), 6(9)(b)
- Word in reg. 61(1) substituted (27.1.2025) by The Social Security (Miscellaneous Amendments) Regulations (Northern Ireland) 2025 (S.R. 2025/3), regs. 1(2), 6(9)(c)
```

#### SR 2025/3 reg 6(9), the amendment of reg 61(1) from 27 January 2025

Source: https://www.legislation.gov.uk/nisr/2025/3/regulation/6/made (paragraph (9), as made; data.xml SHA-256 `97ee2227c8400010f52aed80aa068f250398ae1369a33a41875ed4deb2ea3246`)

```text
(9) In regulation 61(1) (protection for full-time students until course completed)—
(a) for “notified person”, in the first place it occurs, substitute “person who has made a qualifying claim”;
(b) for “on the day on which all awards of any existing benefit are to terminate as a consequence of a claim for universal credit” substitute “on the migration day”;
(c) for “notified person,” in the second place it occurs, substitute “person”.
```

### Dates and cross-references

| GB, SI 2014/1230 | NI, [SR 2016/226](https://www.legislation.gov.uk/nisr/2016/226) | In operation in NI |
|---|---|---|
| reg 28, meaning of "qualifying young person" | [reg 29](https://www.legislation.gov.uk/nisr/2016/226/regulation/29) | From 27 September 2017. SR 2016/226 came into operation immediately after the NI Universal Credit Regulations (reg 1), which come into operation on the day on which Article 6 of the Welfare Reform (NI) Order 2015 "comes into force for all purposes in respect of the whole or any part of Northern Ireland" (their reg 1); legislation.gov.uk dates both from 27 September 2017, when Article 6 came into force for claims in the first districts ([SR 2017/190](https://www.legislation.gov.uk/nisr/2017/190) art 4(3) to (5) and Schedule 1). |
| reg 48, meaning of "qualifying claim" | [reg 49](https://www.legislation.gov.uk/nisr/2016/226/regulation/49) | From 24 July 2019, when Part 4 came in ([SR 2019/152](https://www.legislation.gov.uk/nisr/2019/152)). |
| reg 60, protection for full-time students | [reg 61](https://www.legislation.gov.uk/nisr/2016/226/regulation/61) | From 24 July 2019 (SR 2019/152). The Department for Communities has applied paragraph (2) since 1 August 2022 ([SR 2022/194](https://www.legislation.gov.uk/nisr/2022/194) reg 11). Since 27 January 2025 it has applied paragraph (1) to a person who has made a qualifying claim and is on the course on the migration day; before then it applied it to a notified person on the course on the day on which all their existing awards were to terminate as a consequence of a claim for universal credit ([SR 2025/3](https://www.legislation.gov.uk/nisr/2025/3) reg 6(9)). |

The NI text matches the GB text, with NI cross-references: Article 9(1)(d) of the 2015 Order for section 4(1)(d) of the 2012 Act, reg 6 of the NI Universal Credit Regulations for reg 5, reg 47(4) for reg 46(4), and regs 57 and 58 for regs 56 and 57. Reg 50, meaning of "migration day", which matches GB reg 49 word for word, has been in operation since 24 July 2019, when Part 4 came in (SR 2019/152), but until 25 July 2022 reg 45(7) barred the Department from issuing a migration notice, so no one in Northern Ireland could make a qualifying claim before then.

### Other provisions the cases use

We also work the cases below from these paragraphs, as legislation.gov.uk published them in force on 3 November 2026; `[...]` marks each paragraph we leave out.

SR 2016/226 reg 45, migration notice. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/45 (data.xml SHA-256 `db60c3d0b80da8afadead5447ba3a9c60fb6f9ff02feab6b92f208887027c214`)

```text
 (45)
 (1) The Department may, at any time, issue a notice (“a migration notice”) to a person who is entitled to an award of an existing benefit—
 (a) informing the person that all awards of any existing benefits to which they are entitled are to terminate and that they will need to make a claim for universal credit; and
 (b) specifying a day (“the deadline day”) by which a claim for universal credit must be made.
[...]
 (3B) Where a migration notice is—
 (a) issued to a person who is entitled to an award of tax credit; and
 (b) issued on or after 27th January 2025 and before 6th April 2025, the deadline day may be 6th April 2025.
[...]
 (6) A “notified person” is a person to whom a migration notice has been issued.
[...]
```

SR 2016/226 reg 47, termination of existing benefits if no claim before the deadline. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/47 (data.xml SHA-256 `36c0fcacab3b68c23cdee1241fe87cdb20540653abef0db2a089421a8d1a9fb9`)

```text
 (47)
[...]
 (4) The final deadline is the day that would be the last day of the first assessment period in relation to an award commencing on the deadline day.
[...]
```

SR 2016/226 reg 57, circumstances in which transitional protection ceases. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/57 (data.xml SHA-256 `33bcd90d67c66e415bb529a83e7f6342ff669f57a511cf9576951de6d7c3b0f2`)

```text
 (57)
 (1) A transitional capital disregard or a transitional element does not apply in any assessment period to which paragraph (2) , (3A) or (4) applies, or in any subsequent assessment period.
Cessation of employment or sustained drop in earnings
 (2) This paragraph applies to an assessment period other than an assessment period in relation to an award of universal credit mentioned in regulation 61A(1) (waiver of upper age limit for claimants migrated from tax credits) if the following condition is met—
 (a) in the case of a single claimant—
 (i) it is the assessment period after the third consecutive assessment period in which the claimant’s earned income is less than the amount specified in regulation 97(14)(a) of the Universal Credit Regulations (“the single administrative threshold”), and
 (ii) in the first assessment period of the award, the claimant’s earned income was equal to or more than that threshold; or
[...]
 (3) For the purposes of paragraph (2)—
[...]
 (b) a claimant is to be treated as having earned income that is equal to or more than the single administrative threshold and the couple administrative threshold respectively in any assessment period in respect of which regulation 63 (minimum income floor) of the Universal Credit Regulations (Northern Ireland) 2016 applies to that claimant or would apply but for regulation 63(6) of those Regulations (minimum income floor not to apply in a start-up period).
 (3A) This paragraph applies to an assessment period in relation to an award of universal credit mentioned in regulation 61A(1) (waiver of upper age limit for claimants migrated from tax credits) which—
 (a) is not one of the first 12 assessment periods; and
 (b) is the assessment period after the third consecutive assessment period in which the claimant’s earned income, (or, if the claimant is a member of a couple the couple’s combined earned income) is less than the amount that a person would be paid at the hourly rate set out in regulation 4 of the National Minimum Wage Regulations 2015 for 16 hours a week converted to a monthly amount by multiplying by 52 and dividing by 12.
Couple separating or forming
 (4) This paragraph applies to an assessment period in which—
 (a) joint claimants cease to be a couple or become members of a different couple; or
 (b) a single claimant becomes a member of a couple, unless it is a case where the person may, by virtue of regulation 3(2) of the Universal Credit Regulations (claimant with an ineligible partner), claim as a single person.
```

SR 2016/226 reg 58, application of transitional protection to a subsequent award. Source: https://www.legislation.gov.uk/nisr/2016/226/regulation/58 (data.xml SHA-256 `3ecb1bbc3d868acacd325e55358e8d7a39b37312a0af72bdae8c07463a63a5b2`)

```text
 (58)
 (1) Where—
 (a) a transitional capital disregard is applied, or a transitional element is included, in the calculation of an award, and that award terminates; or
 (b) the Department determines (in accordance with regulation 51) that a transitional capital disregard is to apply, or a transitional element is to be included in the calculation of an award, but the decision on the qualifying claim is that there is no entitlement to an award,
no transitional capital disregard is to apply and no transitional element is to be included in the calculation of any subsequent award unless paragraph (2) applies.
 (2) This paragraph applies if—
 (a) the reason for the previous award terminating or, as the case may be, there being no entitlement to an award, was that the claimant (or joint claimants) had earned income on account of which the financial condition in Article 10(1)(b) or (2)(b) of the Order (income is such that the amount payable is at least 1p) was not met; and
 (b) the claimant becomes entitled to an award within the period of three months beginning with—
 (i) where paragraph (1)(a) applies, the last day of the month that would have been the final assessment period of the previous award (had it not terminated), or
 (ii) where paragraph (1)(b) applies, the day that would have been the last day of the first assessment period had there been entitlement to an award.
[...]
```

The Universal Credit Regulations (Northern Ireland) 2016 (SR 2016/216) reg 3, couples. Source: https://www.legislation.gov.uk/nisr/2016/216/regulation/3 (data.xml SHA-256 `195ff2ed6fbdf1aeadf0924581254bfdd84657ad35e5666a429fc4797ca3c9e7`; no amendment notes)

```text
Couples
 (3)
 (1) A couple may be entitled to universal credit as joint claimants where—
 (a) one member does not meet the basic condition in Article 9(1)(b) of the Order (under the qualifying age for state pension credit) if the other member does meet that condition, or
 (b) one member does not meet the basic condition in Article 9(1)(d) of the Order (not receiving education) and is not excepted from that condition if the other member does meet that condition or is excepted from it.
 (2) A person who is a member of a couple may make a claim as a single person if the other member of the couple—
 (a) does not meet the basic condition in Article 9(1)(a) of the Order (at least 18 years old) and is not a person in respect of whom the minimum age specified in regulation 8 applies,
 (b) does not meet the basic condition in Article 9(1)(c) of the Order,
 (c) is a prisoner,
 (d) is a person other than a prisoner in respect of whom entitlement does not arise by virtue of regulation 19 (restrictions on entitlement), or
 (e) is a person to whom section 115 of the Immigration and Asylum Act 1999 (exclusion from benefits) applies,
and regulations 18 (capital), 38 (amounts of elements) and 23 (deduction of income and work allowance) provide for the calculation of the award in such cases.
[...]
```

### Companion cases

We work each case from the text above. The amounts come from the Department for Communities' standard allowance for a single claimant aged 25 or over: £400.14 for each assessment period that starts on or after 7 April 2025 ([SR 2025/64](https://www.legislation.gov.uk/nisr/2025/64/schedule/13/made) art 1(1)(n), art 32(2) and Schedule 13, as made), and £424.90 for each that starts on or after 6 April 2026 ([SR 2026/54](https://www.legislation.gov.uk/nisr/2026/54) regs 1(4) and 3(3)(a)).

Every case has one claimant in Northern Ireland: aged 30, single unless the case says otherwise, with no child, none of the disability benefits that the Department for Communities lists in reg 14(1)(b) of the Universal Credit Regulations (Northern Ireland) 2016, no foster child and no earned income, so the Department applies no reg 14(1) exception to them. The claimant undertakes a full-time degree, so they receive education under reg 12(2)(a) of those Regulations. Before the migration they had an award of Working Tax Credit, an existing benefit (reg 2(1)), and the Department may issue a migration notice to a person entitled to an award of an existing benefit (reg 45(1)).

1. **A qualifying claim in 2025-26** (policyengine-uk case 41). The Department issued the claimant a migration notice while they had the Working Tax Credit award (reg 45(1)), so they became a notified person (reg 45(6)). They claim Universal Credit on or before the final deadline (reg 47(4)), so they make a qualifying claim (reg 49). On the migration day, the day before their entitlement starts (reg 50), they undertake the degree, so the Department doesn't apply the condition in Article 9(1)(d) of the Welfare Reform (Northern Ireland) Order 2015 to them while they continue it (reg 61(1)). We assume an award whose 12 assessment periods all start on or after 7 April 2025 and before 6 April 2026, with no transitional element.
   - Expected: notified person, yes; qualifying claim, yes; reg 61(1) applies, yes; Article 9(1)(d) applies, no; entitled to Universal Credit, yes; standard allowance £400.14 for each assessment period; 12 assessment periods come to 12 × £400.14 = £4,801.68.
2. **The same course in 2026-27** (the NI version of policyengine-uk case 37). The claimant continues the degree. Under reg 61(2), the Department stops the protection only in an assessment period in which a transitional element would have stopped under reg 57 or 58, and none of those events has happened. The claimant has no earned income, and the minimum income floor in reg 63 of the Universal Credit Regulations (Northern Ireland) 2016 neither applies to them nor would apply but for reg 63(6) (start-up period), so even under reg 57(3)(b) their earned income didn't reach the single administrative threshold in the first assessment period, and the Department can't apply reg 57(2) (reg 57(2)(a)(ii)). The Department applies reg 57(3A) only to an award mentioned in reg 61A(1), and the claimant has no such award. They haven't become a member of a couple (reg 57(4)(b)), and their award continues, so the Department doesn't apply reg 58 either. We assume 12 assessment periods that all start on or after 6 April 2026, with no transitional element.
   - Expected: reg 61(2) applies, no; reg 61(1) applies, yes; entitled, yes; standard allowance £424.90 for each assessment period; 12 assessment periods come to 12 × £424.90 = £5,098.80.
3. **A claim after the final deadline in 2025-26** (the NI version of policyengine-uk case 39). The Department issued the claimant a migration notice, but they claim Universal Credit after the final deadline (reg 47(4)), so their claim doesn't count as a qualifying claim (reg 49), and the Department doesn't apply reg 61(1). They receive education with no reg 14(1) exception, so they don't meet Article 9(1)(d).
   - Expected: qualifying claim, no; reg 61(1) applies, no; Article 9(1)(d) met, no; entitled, no; award £0.
4. **A claim before any migration notice in 2024-25** (the NI version of policyengine-uk case 40). The claimant claims Universal Credit before the Department issues them a migration notice, so they don't claim as a notified person (reg 45(6)) and have no deadline day or final deadline (regs 45(1)(b) and 47(4)). Their claim doesn't count as a qualifying claim (reg 49), and the Department doesn't apply reg 61(1). Since 27 January 2025 the Department has applied reg 61(1) to a person who has made a qualifying claim (SR 2025/3 reg 6(9)(a) and (c)); before then it applied reg 61(1) only to a notified person, so the claimant gets no protection under either text. They receive education with no reg 14(1) exception.
   - Expected: notified person when they claim, no; qualifying claim, no; reg 61(1) applies, no; entitled, no; award £0.
5. **The Department stops the protection under reg 61(2) when the claimant joins a couple.** The claimant of case 1, protected under reg 61(1) and continuing the degree in 2026-27, becomes a member of a couple in an assessment period. Their partner, aged 30, also undertakes a full-time degree, has no reg 14(1) exception and never received a migration notice, and falls under none of sub-paragraphs (a) to (e) of reg 3(2) of the Universal Credit Regulations (Northern Ireland) 2016, so the claimant can't claim as a single person. The claimant's award ends when they form the couple, and since the partner has no award the Department treats the couple as claiming jointly (reg 8(8) of the Universal Credit, Personal Independence Payment, Jobseeker's Allowance and Employment and Support Allowance (Claims and Payments) Regulations (Northern Ireland) 2016). Had the claimant had a transitional element, it would stop from the assessment period in which they became a couple (reg 57(4)(b) and (1)), and no later award could include it, since earned income didn't end the claimant's award (reg 58(1)(a) and (2)(a)); so under reg 61(2) the Department doesn't apply reg 61(1) to the claimant in any of those assessment periods. The joint claim isn't a qualifying claim, since the partner isn't a notified person (regs 45(6) and 49), so it brings no new migration day and no protection under reg 61(1) for either member. Both members then receive education with no exception, so neither meets Article 9(1)(d). Joint claimants have a joint entitlement only if each meets the basic conditions (Article 8(2)(a) of the Order), and reg 3(1)(b) relaxes that for Article 9(1)(d) only "if the other member does meet that condition or is excepted from it"; neither does here, so the couple has no joint entitlement.
   - Expected, from the assessment period in which they become a couple: reg 57(4)(b) applies, yes; reg 61(2) applies, yes; reg 61(1) applies, no; Article 9(1)(d) met, no for each member; entitled as joint claimants, no; award £0.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

COMPOSITION.md

## The change whichever of #2845 and #2806 lands second needs, with the reg 60(1) gate on the migration day

This replaces the change in comment 6594856385. #2845 (branch `fix-2804-reg-28-qyp`, head `f379377f91b39251ace94b1a541555fe64065473`) and #2806 at the head of `claude/pr-2806-review-changes-hgvazm` (`5961424461ff5ca4b7f87582d37d55314c5411b6`) merge cleanly. The diff below applies to #2806's head and to the tree that merges both heads, and touches only #2806's files, none of which #2845 changes.

#2845 applies SI 2014/1230 reg. 28 in `is_qualifying_young_person_for_universal_credit`, and reads an existing benefit report only in a year the model pays that benefit. `is_qualifying_young_person_for_uc_education` applies the exclusion again from the raw reports in every year, so after a benefit closes the two disagree, and cases 31, 33 and 34 fail on the merged tree. The earlier change made `is_qualifying_young_person_for_uc_education` return #2845's variable. That change alone also moved the reg. 60(1) gate (`migrated_student` in `uc_is_excepted_from_education_condition`) onto the year's status. A young claimant whose closure the model derives from a tax credit report then counts as a qualifying young person in the year after the closure and loses the protection. Under reg. 60(1), though, DWP asks whether the person failed the condition on the migration day "because the person is undertaking a full-time course (see regulation 12(2) and 13 ...)". Under reg. 8(2)(a) the claim ends a tax credit award "on the day before the first date on which the claimant is entitled to universal credit", which is the migration day (reg. 49), so reg. 28 still applied that day.

This diff keeps the earlier change for receiving education and moves the gate onto the migration day:
- `is_qualifying_young_person_for_uc_education` returns `is_qualifying_young_person_for_universal_credit`, as before. Its documentation keeps the Northern Ireland sentence and adds NI UC Regs reg. 6 to the references, which the earlier diff's hunk at line 19 couldn't reach.
- A new variable, `is_qualifying_young_person_on_uc_migration_day`, holds #2806's current formula: `is_qualifying_young_person_for_universal_credit` and no reported existing benefit, the person's own or, for a claimant or partner, the couple's tax credits, read in every year as the award the claim ended.
- `uc_is_excepted_from_education_condition` reads the new variable for `migrated_student`, and cites reg. 49.
- `uc_receiving_education.yaml`: cases 31, 33 and 34 as in the earlier change; case 42 now expects `is_qualifying_young_person_for_uc_education: [true]` and `is_qualifying_young_person_on_uc_migration_day: [false]`, and keeps its exception, eligibility and £4,062.96.
- `test_uc_receiving_education_properties.py`: the earlier oracle change (`paying_reports`, property 8 and `test_examples_reach_the_cases`), and in `reference` a second status, `qyp_on_migration_day`, from every reported existing benefit, which the reg. 60 limb reads in place of `qyp`.

## Test runs

`uv sync --frozen --extra dev`. "Merged tree" means #2806's head `596142446` merged with #2845's `f379377f9` (merge commit `2475a5a55, local only`, no conflict).

| Run | Result |
|---|---|
| `git apply --check` at #2806's head `596142446` | applies |
| `git apply --check` at the merged tree | applies |
| `git apply --check` at #2845's head alone | doesn't apply: the three #2806 files don't exist there |
| The earlier diff (comment 6594856385), `git apply --check` at `596142446`, at `f06a9c636` and at the merged tree | `error: patch failed: .../is_qualifying_young_person_for_uc_education.py:19`, `patch does not apply` at each |
| `uc_receiving_education.yaml` on the merged tree, no change | 3 failed, 39 passed: cases 31, 33 and 34, each on `is_qualifying_young_person_for_universal_credit` |
| the same, with the earlier change (its YAML and property hunks, and its formula by hand) | 1 failed, 41 passed: case 42, `is_qualifying_young_person_for_uc_education@2026: [1.] differs from [0.]` |
| the same, with this diff | 42 passed |
| `test_uc_receiving_education_properties.py` on the merged tree with this diff, `-n 4` | 5 passed in 180 s |

Single 18-year-olds in North East England in 2026 on a full-time `POST_SECONDARY` course, each reporting £1,000 of Working Tax Credit, with `would_claim_uc` false and `would_claim_uc_at_legacy_closure` true, so the model derives the closure:

| Tree | W: earns £9,000, council rent £6,000, no maintenance funding | M: case 42, maintenance funding |
|---|---:|---:|
| #2806 head `596142446` | £5,112.96 | £4,062.96 |
| merged tree | £5,112.96 | £4,062.96 |
| merged tree with the earlier change | £0 | £0 |
| merged tree with this diff | £5,112.96 | £4,062.96 |

On the merged tree with this diff:
- **M** keeps 12 × £338.58 = £4,062.96, the 2026 to 2027 standard allowance for a single claimant under 25. `is_qualifying_young_person_for_universal_credit` and `is_qualifying_young_person_for_uc_education` read true, since the model no longer pays Working Tax Credit in 2026 (#2845's reg. 28), so `uc_receiving_education` reads true; `is_qualifying_young_person_on_uc_migration_day` reads false (reg. 28 on the migration day, with reg. 8(2)(a) and reg. 49), so `migrated_student` gives the exception (reg. 60(1)): on the migration day M received education under reg. 12(2)(b) and failed the condition because of the course.
- **W** gets 12 × £338.58 + £6,000 − 55% × £9,000 = £5,112.96 (housing costs element at the rent, taper under UC Regs reg. 22(1)(b)). The model reaches it through the exception: `uc_receiving_education` reads true under reg. 12(1A), and `migrated_student` excepts W because `is_qualifying_young_person_on_uc_migration_day` reads false. In law, W didn't receive education on the migration day (reg. 28 applied, and with no maintenance funding on a non-advanced course reg. 12(2) didn't), so W met the condition that day and reg. 60(1) doesn't reach W. Whether W receives education once the tax credit award has ended turns on whether W counts as a qualifying young person then, which turns on UC Regs reg. 5(5) for a person receiving Universal Credit. No one has quoted reg. 5(5) in this work, so we stop on W's legal outcome: the amount the model gives doesn't change, and whether the body's W row matches what DWP pays stays pending.

## Diff against #2806's head (`596142446`)

```diff
diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml
index bc1ed54e..dda84a45 100644
--- a/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml
+++ b/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_receiving_education.yaml
@@ -831,7 +831,7 @@
         members: [person1]
         region: NORTH_EAST
   output:
-    is_qualifying_young_person_for_universal_credit: [true]
+    is_qualifying_young_person_for_universal_credit: [false]
     is_qualifying_young_person_for_uc_education: [false]
     uc_receiving_education: [false]
     is_uc_eligible: true
@@ -886,7 +886,7 @@
         members: [person1, person2]
         region: NORTH_EAST
   output:
-    is_qualifying_young_person_for_universal_credit: [true, true]
+    is_qualifying_young_person_for_universal_credit: [false, false]
     is_qualifying_young_person_for_uc_education: [false, false]
     uc_receiving_education: [false, false]
 
@@ -915,7 +915,7 @@
         members: [person1]
         region: NORTH_EAST
   output:
-    is_qualifying_young_person_for_universal_credit: [true]
+    is_qualifying_young_person_for_universal_credit: [false]
     is_qualifying_young_person_for_uc_education: [false]
     uc_receiving_education: [true]
     uc_is_excepted_from_education_condition: [true]
@@ -1256,7 +1256,8 @@
   output:
     legacy_benefits_closed: true
     claims_uc_at_legacy_closure: true
-    is_qualifying_young_person_for_uc_education: [false]
+    is_qualifying_young_person_for_uc_education: [true]
+    is_qualifying_young_person_on_uc_migration_day: [false]
     uc_receiving_education: [true]
     uc_is_excepted_from_education_condition: [true]
     meets_uc_education_condition: [true]
diff --git a/policyengine_uk/tests/test_uc_receiving_education_properties.py b/policyengine_uk/tests/test_uc_receiving_education_properties.py
index 44c304d9..1d010d4d 100644
--- a/policyengine_uk/tests/test_uc_receiving_education_properties.py
+++ b/policyengine_uk/tests/test_uc_receiving_education_properties.py
@@ -101,16 +101,34 @@ PROPERTY_SETTINGS = settings(
 YEARS = [2019, 2021, 2024, 2026]
 REGIONS = ["NORTH_EAST", "LONDON", "SCOTLAND", "NORTHERN_IRELAND"]
 EDUCATION = ["TERTIARY", "POST_SECONDARY", "NOT_IN_EDUCATION"]
-# SI 2014/1230 reg. 2(1), as reported receipt.
+TAX_CREDITS = ["child_tax_credit_reported", "working_tax_credit_reported"]
+# SI 2014/1230 reg. 2(1), as reported receipt, for the migration day: the
+# report stands for the award the closure claim ended.
 EXISTING_BENEFITS = [
     "jsa_income_reported",
     "esa_income_reported",
     "income_support_reported",
     "housing_benefit_reported",
-    "child_tax_credit_reported",
-    "working_tax_credit_reported",
+    *TAX_CREDITS,
 ]
-TAX_CREDITS = ["child_tax_credit_reported", "working_tax_credit_reported"]
+# Reg. 28 turns on entitlement,
+# so a report counts only in a year the model pays that benefit (the
+# parameter, or None where the model has no closure date) and only if no
+# reform neutralises one of its variables.
+REPORT_BENEFITS = {
+    "jsa_income_reported": ("JSA.income.active", ["jsa_income"]),
+    "esa_income_reported": (None, ["esa_income"]),
+    "income_support_reported": ("income_support.active", ["income_support"]),
+    "housing_benefit_reported": (None, ["housing_benefit"]),
+    "child_tax_credit_reported": (
+        "tax_credits.active",
+        ["child_tax_credit", "tax_credits"],
+    ),
+    "working_tax_credit_reported": (
+        "tax_credits.active",
+        ["working_tax_credit", "tax_credits"],
+    ),
+}
 # Compare eligibility, maximum entitlement, income reduction and Universal
 # Credit awards.
 OUTPUTS = [
@@ -302,6 +320,23 @@ DRAWN = (None, False)
 BEFORE = (None, True)
 
 
+def paying_reports(simulation, year):
+    """The existing benefit reports reg. 28 reads in the year: those of
+    benefits the model pays then and no reform neutralises."""
+    system = simulation.tax_benefit_system
+    dwp = system.parameters(f"{year}-01-01").gov.dwp
+    paying = []
+    for report, (active, variables) in REPORT_BENEFITS.items():
+        node = dwp
+        for key in active.split(".") if active else []:
+            node = getattr(node, key)
+        if (active is None or bool(node)) and not any(
+            system.get_variable(v).is_neutralized for v in variables
+        ):
+            paying.append(report)
+    return paying
+
+
 def run(case, variants=(DRAWN,)):
     """One simulation of every variant; returns one result per variant."""
     simulation = Simulation(situation=situation(case, variants))
@@ -314,6 +349,7 @@ def run(case, variants=(DRAWN,)):
         np.array([uses_main for _, uses_main in variants for _ in case["families"]]),
     )
     values = {name: simulation.calculate(name, year) for name in VARIABLES}
+    paying = paying_reports(simulation, year)
     limit = simulation.tax_benefit_system.parameters(
         f"{year}-01-01"
     ).gov.dwp.universal_credit.means_test.capital.limit
@@ -324,7 +360,7 @@ def run(case, variants=(DRAWN,)):
     }
     results = []
     for v in range(len(variants)):
-        result = {"capital_limit": limit}
+        result = {"capital_limit": limit, "paying": paying}
         for name, array in values.items():
             entity = simulation.tax_benefit_system.variables[name].entity.key
             size = sizes[entity]
@@ -353,18 +389,31 @@ def reference(case, result):
         members = range(start, start + len(fam["people"]))
         person_of = dict(zip(members, fam["people"]))
         couple = [i for i in members if result["is_uc_assessed_claimant"][i]]
+        tax_credits = [name for name in TAX_CREDITS if name in result["paying"]]
         couple_tax_credits = any(
+            person_of[j].get(name, 0) > 0 for j in couple for name in tax_credits
+        )
+        couple_tax_credits_on_migration_day = any(
             person_of[j].get(name, 0) > 0 for j in couple for name in TAX_CREDITS
         )
-        qyp, recv = {}, {}
+        qyp, qyp_on_migration_day, recv = {}, {}, {}
         for i, person in person_of.items():
             existing_benefit = any(
-                person.get(name, 0) > 0 for name in EXISTING_BENEFITS
+                person.get(name, 0) > 0 for name in result["paying"]
             ) or (i in couple and couple_tax_credits)
             qyp[i] = (
                 bool(result["is_qualifying_young_person_for_universal_credit"][i])
                 and not existing_benefit
             )
+            # Reg. 28 on the migration day, when the award the closure claim
+            # ended still existed.
+            existing_benefit_on_migration_day = any(
+                person.get(name, 0) > 0 for name in EXISTING_BENEFITS
+            ) or (i in couple and couple_tax_credits_on_migration_day)
+            qyp_on_migration_day[i] = (
+                bool(result["is_qualifying_young_person_for_universal_credit"][i])
+                and not existing_benefit_on_migration_day
+            )
             advanced = person["current_education"] == "TERTIARY" or person.get(
                 "in_HE", False
             )
@@ -410,7 +459,7 @@ def reference(case, result):
             reg_60 = (
                 in_couple_role
                 and fam["benunit"]["claims_uc_at_legacy_closure"]
-                and not qyp[i]
+                and not qyp_on_migration_day[i]
             )
             exc[i] = limb_b or limb_c or limb_d or limb_e or limb_f or reg_60
         claimants = [i for i in members if result["is_uc_single_or_joint_claimant"][i]]
@@ -663,11 +712,12 @@ def check(case):
             atol=0.005,
             err_msg=f"{name}: {context}",
         )
-    # 8. Reg. 28: no one who reports an existing benefit counts as a
-    # qualifying young person for the condition.
+    # 8. Reg. 28: no one who reports an existing benefit the model pays that
+    # year, and no reform neutralises, counts as a qualifying young person
+    # for the condition.
     reports = np.array(
         [
-            any(p.get(name, 0) > 0 for name in EXISTING_BENEFITS)
+            any(p.get(name, 0) > 0 for name in drawn["paying"])
             for fam in case["families"]
             for p in fam["people"]
         ]
@@ -711,7 +761,7 @@ def test_examples_reach_the_cases():
     # People 9 and 10: the young Housing Benefit claimants.
     young = slice(9, 11)
     np.testing.assert_array_equal(
-        drawn["is_qualifying_young_person_for_universal_credit"][young], [True, True]
+        drawn["is_qualifying_young_person_for_universal_credit"][young], [False, False]
     )
     np.testing.assert_array_equal(
         drawn["is_qualifying_young_person_for_uc_education"][young], [False, False]
diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py b/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py
index 39307c22..88d8dc99 100644
--- a/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py
+++ b/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py
@@ -1,16 +1,5 @@
 from policyengine_uk.model_api import *
 
-# Universal Credit (Transitional Provisions) Regulations 2014 reg. 2(1).
-EXISTING_BENEFITS = [
-    "jsa_income_reported",
-    "esa_income_reported",
-    "income_support_reported",
-    "housing_benefit_reported",
-    "child_tax_credit_reported",
-    "working_tax_credit_reported",
-]
-TAX_CREDITS = ["child_tax_credit_reported", "working_tax_credit_reported"]
-
 
 class is_qualifying_young_person_for_uc_education(Variable):
     value_type = bool
@@ -19,25 +8,23 @@ class is_qualifying_young_person_for_uc_education(Variable):
     documentation = (
         "Whether this person counts as a qualifying young person for the "
         "Universal Credit condition that a claimant is not receiving "
-        "education: they meet is_qualifying_young_person_for_universal_credit "
-        "and have no entitlement to an existing benefit. Under the Universal "
-        "Credit (Transitional Provisions) Regulations 2014 reg. 28, a person "
-        "entitled to an existing benefit doesn't count as a qualifying young "
-        "person, and the Secretary of State lists the existing benefits in "
-        "reg. 2(1): income-based Jobseeker's Allowance, income-related "
-        "Employment and Support Allowance, Income Support, Housing Benefit, "
-        "Child Tax Credit and Working Tax Credit. The model reads entitlement "
-        "from the year's reported receipt: the person's own report of any of "
-        "them, or, for a claimant or partner, either member's report of tax "
-        "credits, which a couple claims jointly. A person outside the "
-        "definition under reg. 28 takes the ordinary route under the Universal "
-        "Credit Regulations 2013 reg. 12(2). The model's other rules read "
-        "is_qualifying_young_person_for_universal_credit, which leaves reg. 28 "
-        "out. In Northern Ireland the decision maker applies the same rule "
-        "under reg. 29 of the Universal Credit (Transitional Provisions) "
-        "Regulations (Northern Ireland) 2016, with reg. 6 of the Universal "
-        "Credit Regulations (Northern Ireland) 2016 in place of the Universal "
-        "Credit Regulations 2013 reg. 5."
+        "education. Under the Universal Credit (Transitional Provisions) "
+        "Regulations 2014 reg. 28, a person entitled to an existing benefit "
+        "doesn't count as a qualifying young person, and the Secretary of "
+        "State lists the existing benefits in reg. 2(1): income-based "
+        "Jobseeker's Allowance, income-related Employment and Support "
+        "Allowance, Income Support, Housing Benefit, Child Tax Credit and "
+        "Working Tax Credit. It equals "
+        "is_qualifying_young_person_for_universal_credit, which applies reg. "
+        "28. A person outside the definition under reg. 28 takes the ordinary "
+        "route under the Universal Credit Regulations 2013 reg. 12(2). For "
+        "the reg. 60(1) protection the model reads the status on the "
+        "migration day instead (is_qualifying_young_person_on_uc_migration_day). "
+        "In Northern Ireland the decision maker applies the same rule under "
+        "reg. 29 of the Universal Credit (Transitional Provisions) Regulations "
+        "(Northern Ireland) 2016, with reg. 6 of the Universal Credit "
+        "Regulations (Northern Ireland) 2016 in place of the Universal Credit "
+        "Regulations 2013 reg. 5."
     )
     definition_period = YEAR
     reference = (
@@ -50,19 +37,4 @@ class is_qualifying_young_person_for_uc_education(Variable):
     )
 
     def formula(person, period, parameters):
-        qualifying_young_person = person(
-            "is_qualifying_young_person_for_universal_credit", period
-        )
-        # SI 2014/1230 reg. 28: where a person who would be a qualifying
-        # young person "is entitled to an existing benefit", "that person is
-        # not a qualifying young person for the purposes of the Universal
-        # Credit Regulations".
-        own_award = add(person, period, EXISTING_BENEFITS) > 0
-        # Tax Credits Act 2002 s. 3(3)(a): the members of a couple claim a
-        # tax credit jointly, so the award belongs to both.
-        claimant_or_partner = person("is_claimant_or_partner", period)
-        joint_tax_credits = claimant_or_partner & (
-            person.benunit.sum(claimant_or_partner * add(person, period, TAX_CREDITS))
-            > 0
-        )
-        return qualifying_young_person & ~(own_award | joint_tax_credits)
+        return person("is_qualifying_young_person_for_universal_credit", period)
diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_on_uc_migration_day.py b/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_on_uc_migration_day.py
new file mode 100644
index 00000000..2ef8cb2e
--- /dev/null
+++ b/policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_on_uc_migration_day.py
@@ -0,0 +1,68 @@
+from policyengine_uk.model_api import *
+
+# Universal Credit (Transitional Provisions) Regulations 2014 reg. 2(1).
+EXISTING_BENEFITS = [
+    "jsa_income_reported",
+    "esa_income_reported",
+    "income_support_reported",
+    "housing_benefit_reported",
+    "child_tax_credit_reported",
+    "working_tax_credit_reported",
+]
+TAX_CREDITS = ["child_tax_credit_reported", "working_tax_credit_reported"]
+
+
+class is_qualifying_young_person_on_uc_migration_day(Variable):
+    value_type = bool
+    entity = Person
+    label = "Qualifying young person on the Universal Credit migration day"
+    documentation = (
+        "Whether this person counted as a qualifying young person for the "
+        "Universal Credit condition that a claimant is not receiving "
+        "education on the migration day of a closure claim, the day before "
+        "the Universal Credit award starts (Universal Credit (Transitional "
+        "Provisions) Regulations 2014 reg. 49). Under reg. 28, a person "
+        "entitled to an existing benefit doesn't count as a qualifying young "
+        "person, and the Secretary of State lists the existing benefits in "
+        "reg. 2(1). Under reg. 8(2)(a), a claim for Universal Credit ends a "
+        'tax credit award "on the day before the first date on which the '
+        'claimant is entitled to universal credit", so on the migration day '
+        "reg. 28 still applied to that award. The model reads the year's "
+        "reported receipt of an existing benefit as that award, whether or "
+        "not it still pays the benefit that year: the person's own report, "
+        "or, for a claimant or partner, either member's report of tax "
+        "credits, which a couple claims jointly. "
+        "uc_is_excepted_from_education_condition reads this status for the "
+        "reg. 60(1) protection. In Northern Ireland the decision maker applies "
+        "the same rules under regs. 29 and 50 of the Universal Credit "
+        "(Transitional Provisions) Regulations (Northern Ireland) 2016."
+    )
+    definition_period = YEAR
+    reference = (
+        "https://www.legislation.gov.uk/uksi/2014/1230/regulation/28",
+        "https://www.legislation.gov.uk/uksi/2014/1230/regulation/2",
+        "https://www.legislation.gov.uk/uksi/2014/1230/regulation/8",
+        "https://www.legislation.gov.uk/uksi/2014/1230/regulation/49",
+        "https://www.legislation.gov.uk/ukpga/2002/21/section/3",
+        "https://www.legislation.gov.uk/nisr/2016/226/regulation/29",
+        "https://www.legislation.gov.uk/nisr/2016/226/regulation/50",
+    )
+
+    def formula(person, period, parameters):
+        qualifying_young_person = person(
+            "is_qualifying_young_person_for_universal_credit", period
+        )
+        # SI 2014/1230 reg. 28: where a person who would be a qualifying
+        # young person "is entitled to an existing benefit", "that person is
+        # not a qualifying young person for the purposes of the Universal
+        # Credit Regulations". The report stands for the award the closure
+        # claim ended, so the formula reads it in every year.
+        own_award = add(person, period, EXISTING_BENEFITS) > 0
+        # Tax Credits Act 2002 s. 3(3)(a): the members of a couple claim a
+        # tax credit jointly, so the award belongs to both.
+        claimant_or_partner = person("is_claimant_or_partner", period)
+        joint_tax_credits = claimant_or_partner & (
+            person.benunit.sum(claimant_or_partner * add(person, period, TAX_CREDITS))
+            > 0
+        )
+        return qualifying_young_person & ~(own_award | joint_tax_credits)
diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_is_excepted_from_education_condition.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_is_excepted_from_education_condition.py
index 209eddad..5b42c465 100644
--- a/policyengine_uk/variables/gov/dwp/universal_credit/uc_is_excepted_from_education_condition.py
+++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_is_excepted_from_education_condition.py
@@ -22,10 +22,10 @@ class uc_is_excepted_from_education_condition(Variable):
         "a family that claims Universal Credit when DWP moves it off its "
         "legacy benefits (claims_uc_at_legacy_closure), which the model takes "
         "as the qualifying claim under reg. 48 of the 2014 Regulations, and who "
-        "doesn't count "
-        "as a qualifying young person for the condition "
-        "(is_qualifying_young_person_for_uc_education, which applies reg. 28 "
-        "of the 2014 Regulations). The model needs no rule of its own for limb "
+        "didn't count as a qualifying young person for the condition on the "
+        "migration day (is_qualifying_young_person_on_uc_migration_day, which "
+        "applies reg. 28 of the 2014 Regulations to the award the claim "
+        "ended). The model needs no rule of its own for limb "
         "(e)(i): it places responsibility for a child on the benefit unit, so "
         "both members of the couple meet (c). Under (b), the decision maker "
         "must determine limited capability for work before the course starts. "
@@ -63,6 +63,7 @@ class uc_is_excepted_from_education_condition(Variable):
         "https://www.legislation.gov.uk/uksi/2013/376/regulation/2",
         "https://www.legislation.gov.uk/uksi/2014/1230/regulation/28",
         "https://www.legislation.gov.uk/uksi/2014/1230/regulation/48",
+        "https://www.legislation.gov.uk/uksi/2014/1230/regulation/49",
         "https://www.legislation.gov.uk/uksi/2014/1230/regulation/60",
         "https://www.legislation.gov.uk/nisr/2016/216/regulation/14",
         "https://www.legislation.gov.uk/nisr/2016/226/regulation/29",
@@ -127,12 +128,13 @@ class uc_is_excepted_from_education_condition(Variable):
         # that condition is not to apply ... while they are continuing to
         # undertake that course". A qualifying young person receives
         # education under reg. 12(1A), not reg. 12(2); reg. 28 applies
-        # first, so a person entitled to an existing benefit takes reg.
-        # 12(2) and keeps the protection.
+        # first, so a person entitled to an existing benefit on the
+        # migration day took reg. 12(2) and keeps the protection while they
+        # continue the course, after that award has ended.
         migrated_student = (
             claimant_or_partner
             & benunit("claims_uc_at_legacy_closure", period)
-            & ~person("is_qualifying_young_person_for_uc_education", period)
+            & ~person("is_qualifying_young_person_on_uc_migration_day", period)
         )
         return (
             disabled_with_limited_capability_for_work
```

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Ready for hub review

Head: 5961424461ff5ca4b7f87582d37d55314c5411b6 on claude/pr-2806-review-changes-hgvazm, two commits on f06a9c6362bc03b4247f275e9062d532296451f5, so moving fix-2346-uc-education to it fast-forwards. The branch has no PR, so no CI has run on it. The hub documents sit above: PR_TITLE.txt (unchanged), PR_BODY.md, AXIOM_514_NI.md and COMPOSITION.md.

Before starting. fix-2346-uc-education sat at f06a9c636, the brief's base; the hub's own move to it (CI from 11:29Z) falls inside the six hours, but nothing reached the branch after it, so I treated that as the base the brief names rather than a competing push. juaristi22 posted two reviews at that head (11:52Z: COMPLETE, no findings; 13:21Z: two nonblocking findings, A1 = P2-1 and A2 = P3-8, P3-3 and P3-11's "as made" link, both covered below). No comment said anyone was mid-change, so I posted the pick-up notice at 13:33Z. Apart from that notice and the hub documents, I posted nothing, and nothing on rulespec-uk#514.

Findings

  • P2-1. Applied the review's three replacements: body invariant 9; module docstring policyengine_uk/tests/test_uc_receiving_education_properties.py:66-77; test docstring :887-900. Examples changed (below).
  • P2-2. Body "Overlapping work": the new first sentence and a Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845 bullet. I adapted the review's bullet: case 42 now exists here, so the bullet says case 42 fails under the earlier change alone and that the second author keeps it passing; for the W row it says the model still gives £5,112.96 with the migration-day gate, but through reg 60(1), and that DWP's outcome turns on UC Regs reg 5(5), which the body doesn't settle. "Young people on legacy benefits" now cites cases 34 and 42.
    • Case 42, uc_receiving_education.yaml:1215: person M, 12 x £338.58 = £4,062.96 (the header's 2026-27 under-25 rate), citing regs 8(2)(a), 28, 12(2)(b), 48, 49, 52(1) and 60(1). Passes at the head (42 passed).
    • Guard. On a scratch merge of the head with Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845 f379377f9, with composition.md's change (its YAML and property hunks; its variable hunk fails at :19, so I applied its formula by hand): case 42 fails, is_qualifying_young_person_for_uc_education@2026: [1.] differs from [0.], and M's award goes to £0 (W's too).
    • Patch (COMPOSITION.md, not committed): is_qualifying_young_person_for_uc_education returns Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845's variable; a new is_qualifying_young_person_on_uc_migration_day keeps this PR's raw-report formula; migrated_student reads it; case 42 expects [true] for the first and [false] for the second; the property oracle gains qyp_on_migration_day for the reg 60 limb. On the merge: cases 31, 33, 34 and 42 pass (42 passed), the property file gives 5 passed in 180 s, M gets £4,062.96 and W £5,112.96. git apply --check: applies at 596142446 and at the merge; doesn't apply at Apply SI 2014/1230 reg 28 to the UC qualifying young person test #2845 alone (the files don't exist there).
    • W. I stop on W's legal outcome. Under the patch the model excepts W under reg 60(1), but W met the condition on the migration day (reg 28 applied, no maintenance funding, non-advanced course), so reg 60(1) doesn't reach W; whether W receives education after the award ends turns on UC Regs reg 5(5), which nobody has quoted. The body's W row keeps £5,112.96, now marked as pending in law.
  • P3-1. Body: departures intro, Migration day last sentence, Which years second paragraph, all as the review gives them. The reply text for juaristi22, for the hub to post: "Where a user or dataset sets claims_uc_at_legacy_closure true, or sets legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds (its default), the model applies it in any year, as test_education_condition_invariants does with claims_uc_at_legacy_closure in 2019, 2021 and 2024."
  • P3-2. Body Migration day sentence; reg 61 row in the body and the addendum; case 40 comment (:1129).
  • P3-3. Case 36 (:966): reg 2(1) for the definition, reg 52(1) for the transitional element, and the Working Tax Credit 30-hours sentence. Addendum shared premise.
  • P3-4. Case 36 name and the migration-day sentence.
  • P3-5. Case 37 (:1018), the body's reg 60(2) sub-bullet, addendum companion case 2.
  • P3-6. Departures intro.
  • P3-7. Mutant paragraph, the three sentences.
  • P3-8. policyengine_uk/variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py:49.
  • P3-9. ALL_OPEN, TAX_CREDITS_CLOSED, ALL_CLOSED (:878) shared by the @example lines and the guard (:939); closure_grid (:827) gains a Child Tax Credit + Income Support row (TWO_REPORTS, :824), so each grid holds 48 people. Body: the grid bullet (48 people, the new row) and Which years.
  • P3-10. Same course bullet; Test plan first checkbox.
  • P3-11. Body NI first paragraph; reg 29 row (body and addendum); SR 2025/64 links now open schedule/13/made and say "as made" (body and addendum); the addendum's reg 50 paragraph.
  • P3-12. Reg 6(9) block from the data.xml spacing; reg 29 note labelled "Commencement information"; the intro says each hash identifies the file as served on 3 November 2026.
  • P3-13. Companion case 1's end date; case 5's reg 8(8)/reg 58 sentence and the Article 8(2)(a) sentence, rewritten with the Department as actor ("the Department treats the couple as claiming jointly"; "Joint claimants have a joint entitlement only if").
  • P3-14. Reported receipt; case 41 (:1177).

All fourteen P3s applied; I skipped none.

Examples

Hypothesis's seed for test_reg_60_protection_follows_the_closure_dates changed with its docstring. I logged each call from closure_situation (a helper, so the seed stays put) at both heads, 43 calls each:

  • f06a9c636: 2020 (6), 2021 (7), 2023 (8), 2024 (8), 2025 (7), 2026 (3), 2027 (1), plus three grids: the review's counts exactly.
  • 596142446: 2020 (4), 2021 (5), 2022 (3), 2023 (9), 2024 (7), 2027 (9), 2028 (3), plus three grids of 48. No drawn case now falls in 2025 or 2026; the grids cover both. Two of the 40 drawn cases coincide.

test_closure_grids_straddle_each_closure has no Hypothesis draw. No other test's source changed.

Tests at 596142446

Each from a clean run, without survey data (uv sync --frozen --extra dev):

Run Result
uc_receiving_education.yaml 42 passed
person_legacy_benefits_closed.yaml 3 passed
legacy_closure_ineligible_partner.yaml, legacy_closure_pension_route.yaml, legacy_closure_migrant_nations.yaml 1, 4 and 9 passed
pytest -n 4 on the three property files 30 passed in 245.71 s; none reached the 40-minute cap
tests/policy/baseline/gov/dwp/universal_credit/ 58 passed
tests/policy/baseline/finance/benefit/family/universal_credit/ 563 passed in 42.67 s
test_parameter_descriptions.py 24 passed
main 1d4d020 with this head's YAML and property file 42 failed; 4 failed, 1 passed
pytest policyengine_uk/tests/code_health -n 4 3,095 passed
uvx ruff@0.17.2 format --check .; ruff check .; --select F811,F821 policyengine_uk; git diff --check f06a9c636..HEAD 2177 files already formatted; all passed; all passed; clean
.github/check_fix_pr.py from main 1d4d020, as pr_fix_evidence.yaml runs it (merge base 0080274, empty labels) Fix evidence: PASS, changed executable tests: 2
Scratch merge with #2845 f379377f9, uc_receiving_education.yaml: no change / composition.md's change / my patch 3 failed, 39 passed (31, 33, 34) / 1 failed, 41 passed (42) / 42 passed
the same merge, property file with my patch 5 passed in 179.62 s

git merge-tree --write-tree --name-only HEAD X, no conflict for any: main 1d4d020 (tree 9dc14e992c), 2da212a, 52b3080, #1969 b4e8722, #2279 c5b9850, #2688 0d760fc, #2845 f379377 (c32c24b16e), fix-2120-uc-carer-awards 260f6a7, #2813 dc69f3a. #1969 and #2688 have moved since the body's SHAs; at the old #2688 798491c the merge still conflicts in uprating_indices.yaml, but at 0d760fc it doesn't, so the body now lists both new heads as clean.

Mutant A2

income_support/active.yaml 2026-04-01 changed to 2027-04-01, at this head: all five YAML files pass (42, 1, 4, 3, 9). Property file: 1 failed, 4 passed in 175 s, only test_closure_grids_straddle_each_closure at :957 (assert not active(ALL_CLOSED, path)). The main property and the new two-report row follow the moved parameter by design, so the guard alone still kills A2; it now asserts on the shared constants the grids use.

Skipped

  • Survey-data tests, the full suite (CI), the body's differential row, and the property halves of the old mutant rows.
  • Citing reg 8 for Income Support, income-based JSA or Housing Benefit: the patch's migration-day variable reads those reports as Apply the Universal Credit education condition #2806 already does, but no one has quoted when DWP ends those awards on a migration.
  • rulespec-uk#514: outside this session's access.

Judgment calls

  1. The body keeps W's £5,112.96 and says DWP's outcome depends on reg 5(5) rather than changing the row.
  2. The patch gates on the migration-day status only, as the brief asks. It doesn't also require that the person received education under reg 12(2) on the migration day, which would take W's exception away but would still leave W's final award to reg 5(5).
  3. Case 42 keeps the Working Tax Credit report in 2026 to stand for the closed award, as cases 37 and 38 do. It doesn't say how an 18-year-old held Working Tax Credit (WTC Regs reg 4(1)); the review's M has the same premise.
  4. Case 42 asserts is_qualifying_young_person_for_uc_education: [false] at this head, so the guard fails first on that variable; M's award under the earlier change also drops to £0 (checked with a script on the merge).
  5. The grid row has two reports; the body now says 48 people.
  6. The body's test-plan rows now give this head's counts and main at 1d4d020.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

We pushed 596142446 for both findings in your latest review.

  • A1. Invariant 9 and both closure-test docstrings now limit "either way" to people not on a full-time course. They also cover only families where the model derives both claims_uc_at_legacy_closure and legacy_benefits_closed itself.
  • A2. is_qualifying_young_person_for_uc_education now cites NI UC reg 6, and case 36 cites reg 2(1) for the existing benefit and reg 44(1) for the notice. For the text in force before 27 January 2025, the description quotes the whole condition: the day on which all existing awards "are to terminate as a consequence of a claim for universal credit". The 2025 NI rates now link Schedule 13 as made.

Case 42 covers the young tax-credit migrant. An 18-year-old on a full-time non-advanced course with maintenance funding, whose closure the model derives from a Working Tax Credit report, keeps 12 × £338.58 = £4,062.96 in 2026-27 under reg 60(1). On a merge of both heads, the change in comment 6594856385 fails that case. The new composition note gives whichever of #2845 and #2806 lands second a patch that keeps the reg 60(1) gate on migration-day status. With that patch, all 42 cases and the property file pass on the merge.

My earlier reply overstated the legacy_benefits_closed route. Where a user or dataset sets claims_uc_at_legacy_closure true, or sets legacy_benefits_closed true for a family whose would_claim_uc_at_legacy_closure holds (its default), the model applies the protection in any year. test_education_condition_invariants checks the first route in 2019, 2021 and 2024.

@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 5961424461ff5ca4b7f87582d37d55314c5411b6 and the description update caught by the publication guard. COMPLETE — A1 and A2 are resolved; no findings or material source gaps remain.

The committed test/docstring/reference fixes and case 42 passed 45 focused checks through final UC and household net income. The actual PR description now fixes the course-scope statement, historical causal wording and as-made NI rate link. I also checked the filed #514 addendum edit, rather than treating the proposed text as adopted prematurely.

No unchanged broad suite was rerun. The future #2845 composition patch remains outside this head. Approval is deferred because the PR is still a draft. The completed review can be retained when readiness and applicable required checks at the exact head are checked later.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Universal Credit is paid to full-time students (WRA 2012 s. 4(1)(d); UC Regs 2013 regs. 12-14)

3 participants