Skip to content

Apply SI 2014/1230 reg 28 in is_qualifying_young_person_for_universal_credit #2804

Open

Description

@MaxGhenis

What the model leaves out

is_qualifying_young_person_for_universal_credit (policyengine_uk/variables/gov/dwp/universal_credit/child_element/is_qualifying_young_person_for_universal_credit.py, main 2da212a) applies the age, education or training, entry and terminal date conditions of UC Regs 2013 reg 5, and reg 5(5) through the input receives_benefits_in_own_right (Universal Credit, ESA or JSA in the person's own right, false unless a user sets it).

Reg 5(5) reads: "A person who is receiving universal credit, an employment and support allowance or a jobseeker's allowance is not a qualifying young person." In reg 2 the Secretary of State defines "employment and support allowance" and "jobseeker's allowance" without their income-related and income-based forms, so under reg 5(5) DWP doesn't count a young person receiving Universal Credit, contributory ESA or contribution-based JSA as a qualifying young person. #2279 (open, c5b9850a3) excludes a young person who reports Universal Credit, or ESA or JSA of either kind, reading the person's raw reports (universal_credit_reported, esa_income_reported, esa_contrib_reported, jsa_income_reported and jsa_contrib_reported). Those reports identify the person's own awards without calculating family benefits that depend on qualifying young person status and could recurse.

The predicate doesn't apply the Universal Credit (Transitional Provisions) Regulations 2014 reg 28:

Where a person who would (apart from the provision made by this regulation) be a “qualifying young person” within the meaning of regulation 5 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 5(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 2014),”.

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. Under reg 28, DWP also excludes people entitled to any of them: income-based JSA and income-related ESA, which #2279 reads, and Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit. We fetched reg 28 from legislation.gov.uk at seven dates from 16 June 2014 to 6 April 2026 and on 1 November 2026, and its text reads the same in every copy, so it applies in every model year from 2014-15. Sources: https://www.legislation.gov.uk/uksi/2014/1230/regulation/28/2026-11-01 (CLML SHA-256 8b95cf93d9ab150a82ff1d6fa8108991292d59b79d32dcc454f6ab83dc38af92) and https://www.legislation.gov.uk/uksi/2014/1230/regulation/2/2026-11-01 (32580f8665764351c4d681faaa523170a2248cb9ac6a4a9e38b4458878b38a9a).

So a young person who reports Income Support or Housing Benefit in their own right, or who holds a tax credit award, still counts as a qualifying young person in the model, unless a user sets receives_benefits_in_own_right. A parent then gets the child element for them, where DWP pays none under reg 28.

Reproduction

Run the script below with policyengine-uk, using one simulation per case: python qyp_routes.py

"""Reg 28 and the Universal Credit qualifying young person predicate.

One simulation per case, 2026, North East, owned outright, no income. Every
person has is_claimant_or_partner set.
"""
from policyengine_uk import CountryTaxBenefitSystem, Simulation

SYSTEM = CountryTaxBenefitSystem()
Y = 2026
PARENT = dict(age=45, is_parent=True, is_claimant_or_partner=True)
YOUNG = dict(age=17, current_education="POST_SECONDARY", is_claimant_or_partner=False)
CASES = {
    "parent 45 + 17 reporting IS 3,000": [PARENT, dict(YOUNG, income_support_reported=3_000)],
    "parent 45 + 17 reporting HB 3,000": [PARENT, dict(YOUNG, housing_benefit_reported=3_000)],
    "parent 45 + 17 reporting ESA(IR) 3,000": [PARENT, dict(YOUNG, esa_income_reported=3_000)],
    "parent 45 + 17 reporting JSA(IB) 3,000": [PARENT, dict(YOUNG, jsa_income_reported=3_000)],
    "parent 45 + 17 reporting nothing": [PARENT, dict(YOUNG)],
    "parent 45 + 17 with receives_benefits_in_own_right": [
        PARENT,
        dict(YOUNG, receives_benefits_in_own_right=True),
    ],
    "single 18 on a non-advanced course (no full-time flag) reporting HB 6,000": [
        dict(age=18, current_education="POST_SECONDARY", is_claimant_or_partner=True, housing_benefit_reported=6_000)
    ],
}
VARS = [
    "is_qualifying_young_person_for_universal_credit",
    "is_qualifying_young_person_for_uc_education",
    "uc_receiving_education",
]
for name, members in CASES.items():
    ids = [f"p{j}" for j in range(len(members))]
    people = {
        pid: {k: {Y: v} for k, v in m.items() if k in SYSTEM.variables}
        for pid, m in zip(ids, members)
    }
    sim = Simulation(
        situation=dict(
            people=people,
            benunits={"b": {"members": ids}},
            households={
                "h": {
                    "members": ids,
                    "region": {Y: "NORTH_EAST"},
                    "tenure_type": {Y: "OWNED_OUTRIGHT"},
                }
            },
        )
    )
    flags = " ".join(
        f"{v}={[bool(x) for x in sim.calculate(v, Y)]}" for v in VARS if v in SYSTEM.variables
    )
    print(
        f"{name}: eligible={bool(sim.calculate('is_uc_eligible', Y)[0])} "
        f"UC={sim.calculate('universal_credit', Y)[0]:.2f} "
        f"child_element={sim.calculate('uc_child_element', Y)[0]:.2f} {flags}",
        flush=True,
    )

We ran it on #2346's branch, which leaves this predicate as it stands on main, and, for the columns with #2279, on that branch merged with #2279 (c5b9850a3).

Benefit unit Young person's own report Predicate Parent's Universal Credit Predicate with #2279 Parent's Universal Credit with #2279
Parent 45, dependant 17 on a non-advanced course Income Support £3,000 true £9,321.36 true £9,321.36
The same Housing Benefit £3,000 true £9,321.36 true £9,321.36
The same income-related ESA £3,000 true £9,321.36 false (reg 28) £5,098.80
The same income-based JSA £3,000 true £9,321.36 false (reg 28) £5,098.80
The same none true £9,321.36 true £9,321.36
The same, with receives_benefits_in_own_right set none false £5,098.80 false £5,098.80
An 18-year-old heading their own benefit unit on a non-advanced course Housing Benefit £6,000 true £4,062.96, no child element true £4,062.96, no child element

Under reg 28 the young people on Income Support and Housing Benefit don't count as qualifying young people, so the parent's award should match the row with receives_benefits_in_own_right, 12 × £424.90 = £5,098.80. The model pays 12 × (£424.90 + £351.88) = £9,321.36, £4,222.56 more, with or without #2279. In the last row the predicate, and is_child_or_qualifying_young_person_for_universal_credit, are true for the claimant themselves; the child element stays nil, and we haven't traced what the other readers do with that. For a couple on a tax credit award, #2346's case 33 in uc_receiving_education.yaml asserts the predicate true for both 18-year-olds on the couple's Working Tax Credit award.

Every reader

Readers among the variables (grep -rlE at main 2da212a for the predicate and its child-or-young-person wrapper):

  • variables/gov/dwp/universal_credit/child_element/is_child_or_qualifying_young_person_for_universal_credit.py, the wrapper;
  • variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_specified_benefit.py (line 111, the young person limb of reg 83(1)), which reads both;
  • variables/gov/dwp/universal_credit/is_qualifying_young_person_for_uc_education.py (added by Universal Credit is paid to full-time students (WRA 2012 s. 4(1)(d); UC Regs 2013 regs. 12-14) #2346; applies reg 28 itself for regs 12(1A) and 60).

Readers of the wrapper, which inherit the gap:

  • variables/gov/dwp/lha_renter_has_non_dependant.py
  • variables/gov/dwp/universal_credit/child_element/disability/severe_disability/uc_individual_severely_disabled_child_element.py
  • variables/gov/dwp/universal_credit/child_element/disability/uc_individual_disabled_child_element.py
  • variables/gov/dwp/universal_credit/child_element/uc_child_index.py
  • variables/gov/dwp/universal_credit/child_element/uc_child_limit_disregarded.py
  • variables/gov/dwp/universal_credit/child_element/uc_child_limit_position.py
  • variables/gov/dwp/universal_credit/child_element/uc_is_child_born_before_child_limit.py
  • variables/gov/dwp/universal_credit/child_element/uc_is_child_limit_affected.py
  • variables/gov/dwp/universal_credit/is_responsible_for_child_or_qualifying_young_person_for_universal_credit.py
  • variables/gov/local_authorities/council_tax_reduction/is_child_or_young_person_for_council_tax_reduction.py

variables/household/demographic/is_child.py names the wrapper only in its documentation.

Tests that read the predicate or the wrapper:

  • tests/policy/baseline/finance/benefit/child_qualifying_young_person.yaml
  • tests/policy/baseline/finance/benefit/family/universal_credit/child_element/disability/uc_individual_disabled_child_element.yaml
  • tests/policy/baseline/finance/benefit/family/universal_credit/claimant_child_element.yaml
  • tests/policy/baseline/finance/benefit/family/universal_credit/is_uc_assessed_claimant.yaml
  • tests/policy/baseline/finance/benefit/family/universal_credit/is_uc_claimant.yaml
  • tests/policy/baseline/finance/benefit/family/universal_credit/work_allowance/is_uc_work_allowance_eligible.yaml
  • tests/policy/baseline/gov/dwp/benefit_cap.yaml
  • tests/policy/baseline/gov/dwp/means_test_income_members.yaml
  • tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_young_person.yaml
  • tests/test_benefit_cap_specified_benefits_properties.py
  • tests/test_council_tax_reduction_severe_disability_family.py
  • tests/test_council_tax_reduction_young_person_properties.py
  • tests/test_means_test_income_properties.py
  • tests/test_uc_claimant_elements_properties.py
  • tests/test_uc_claimant_income_properties.py
  • tests/test_uc_receiving_education_properties.py (Universal Credit is paid to full-time students (WRA 2012 s. 4(1)(d); UC Regs 2013 regs. 12-14) #2346)

Proposed fix

Related

#2292 proposes excluding a young person who receives Income Support in their own right from the qualifying young person tests for Pension Credit, Child Benefit and Child Tax Credit, and leaving Universal Credit's test alone, since UC Regs reg 5(5) doesn't list Income Support. For Universal Credit, DWP excludes a person entitled to Income Support, Housing Benefit or either tax credit under SI 2014/1230 reg 28.

🤖 Generated with Claude Code

Activity

  1. MaxGhenis commented on Nov 1, 2026

    @MaxGhenis
    CollaboratorAuthor

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


    Generated by Claude Code

  2. MaxGhenis commented on Nov 1, 2026

    @MaxGhenis
    CollaboratorAuthor

    Ready for hub review

    Head: dbaf71872870f26988c802cab622919c450d95f1 on branch claude/reg-28-qualifying-young-person-xorqye. The harness pins pushes to this name. The branch starts at #2279's head c5b9850a3592769ba35f4d5b5c72c7ba8d2e6f22 and adds two commits. I opened no PR, so no CI has run.

    Items

    1. Reg 28 in the predicate. policyengine_uk/variables/gov/dwp/universal_credit/child_element/is_qualifying_young_person_for_universal_credit.py:49-78. I folded both lists into Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's single receives_own_benefits expression. It now also reads income_support_reported and housing_benefit_reported, plus receives_tax_credits, which is true for a person's own Child Tax Credit or Working Tax Credit report, and for a claimant or partner when either of the couple reports one (lines 56-67). All four reports are Person variables (policyengine_uk/variables/gov/dwp/income_support_reported.py, housing_benefit/housing_benefit_reported.py, child_tax_credit_reported.py, working_tax_credit_reported.py). The couple sum masks with is_claimant_or_partner before benunit.sum, the same way legacy_benefits_closed does. Documentation and references now cite TP reg 28, reg 2 and TCA 2002 s.3 (lines 8-25). Reg 5(5) already excludes income-related ESA and income-based JSA through 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, so I added nothing for them.
    2. A dependant doesn't pick up a parent's report. A dependant reads only their own reports. The couple branch is gated on claimant_or_partner (line 66). This is tested by the YAML case "A parent's own reports of every existing benefit leave their young person a qualifying young person." and by a property.
    3. No cycle. For each of the issue's 7 households, under both OWNED_OUTRIGHT and RENT_PRIVATELY (rent £6,000), I calculated each reader first in a fresh simulation and then universal_credit. The readers were the predicate, its wrapper, is_uc_benefit_cap_exempt_specified_benefit, lha_renter_has_non_dependant, both disabled child elements, uc_child_index, uc_child_limit_disregarded, uc_child_limit_position, uc_is_child_born_before_child_limit, uc_is_child_limit_affected, is_responsible_for_child_or_qualifying_young_person_for_universal_credit, is_child_or_young_person_for_council_tax_reduction, housing_benefit, council_tax_reduction and household_net_income. That gave 238 runs with 0 errors. is_qualifying_young_person_for_uc_education (Apply the Universal Credit education condition #2806) isn't in this base.

    Issue script output

    Before (at c5b9850a3):

    parent 45 + 17 reporting IS 3,000: eligible=True UC=9321.36 child_element=4222.56 is_qualifying_young_person_for_universal_credit=[False, True]
    parent 45 + 17 reporting HB 3,000: eligible=True UC=9321.36 child_element=4222.56 is_qualifying_young_person_for_universal_credit=[False, True]
    parent 45 + 17 reporting ESA(IR) 3,000: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    parent 45 + 17 reporting JSA(IB) 3,000: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    parent 45 + 17 reporting nothing: eligible=True UC=9321.36 child_element=4222.56 is_qualifying_young_person_for_universal_credit=[False, True]
    parent 45 + 17 with receives_benefits_in_own_right: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    single 18 on a non-advanced course (no full-time flag) reporting HB 6,000: eligible=True UC=4062.96 child_element=0.00 is_qualifying_young_person_for_universal_credit=[True]
    

    After (this branch):

    parent 45 + 17 reporting IS 3,000: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    parent 45 + 17 reporting HB 3,000: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    parent 45 + 17 reporting ESA(IR) 3,000: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    parent 45 + 17 reporting JSA(IB) 3,000: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    parent 45 + 17 reporting nothing: eligible=True UC=9321.36 child_element=4222.56 is_qualifying_young_person_for_universal_credit=[False, True]
    parent 45 + 17 with receives_benefits_in_own_right: eligible=True UC=5098.80 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False, False]
    single 18 on a non-advanced course (no full-time flag) reporting HB 6,000: eligible=True UC=4062.96 child_element=0.00 is_qualifying_young_person_for_universal_credit=[False]
    

    The script prints only the predicate column because is_qualifying_young_person_for_uc_education and uc_receiving_education don't exist at this base.

    Tests

    • New file policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_qualifying_young_person_existing_benefits.yaml has 11 cases, all passing. Cases 1-7 are the issue's table rows in 2026 (North East, owned outright), asserting the predicate, the wrapper, uc_child_element and universal_credit: £5,098.80 for the Income Support, Housing Benefit, ESA, JSA and own-right rows, £9,321.36 with no report, and £4,062.96 for the 18-year-old claimant. The other 4 cases cover own CTC/WTC reports (2024), a parent's reports, a couple's Working Tax Credit award excluding both 18-year-olds, and a couple's Child Tax Credit award leaving a dependant in place.
    • New file policyengine_uk/tests/test_uc_qualifying_young_person_existing_benefits_properties.py has 4 Hypothesis properties (25 derandomised examples each, about 30 s for the file with -n 4):
      • any positive own report of an existing benefit, or a couple tax credit report for a claimant or partner, makes the predicate false;
      • with all four reports at zero, the predicate equals reg 5 with Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's reg 5(5) exclusions, re-derived from its component variables; with reports present, the predicate never exceeds that test;
      • zeroing a claimant's or partner's reports never changes a dependant's predicate;
      • a parent's universal_credit never rises when their young person's report of any existing benefit turns positive (2024 and 2026).
        Each example sets inputs on one cached simulation per shape and year, because building a simulation took about 3.7 s here. A check over 6 examples found that cached and fresh simulations give the same predicate and universal_credit.
    • Updated Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's tests: 6 cases in qualifying_young_person_reported_own_awards.yaml asserted that a young person reporting Income Support stays a UC qualifying young person. They now assert false under reg 28, and I renamed 4 of them (YAML case names aren't in shard_durations.toml). test_pension_credit_young_person_income_support.py::test_own_income_support_ends_the_young_person_until_a_reform_removes_it now expects [False, False] for the UC predicate, with and without the reform. Its name is unchanged.
    • Reruns:

    Revert and mutation checks

    Skipped

    • Enhanced FRS question. I have no survey data, so whether the dataset puts a 16 to 19-year-old with their own Income Support or Housing Benefit report in a parent's benefit unit is left to the hub. No dataset test was needed or run.
    • Apply the Universal Credit education condition #2806 files (uc_receiving_education.yaml, test_uc_receiving_education_properties.py) aren't in this base, so I didn't run them. After this change, Apply the Universal Credit education condition #2806's case 33 ("a couple of 18-year-olds on a joint Working Tax Credit award…", 2024) needs is_qualifying_young_person_for_universal_credit: [false, false] in place of [true, true]. I ran the same household here without is_in_full_time_education, which isn't in this base: is_claimant_or_partner comes out [True, True] and the predicate [False, False]. I couldn't run its other two assertions, which read Apply the Universal Credit education condition #2806's variables, so they're pending. Once both land, is_qualifying_young_person_for_uc_education could read the predicate directly, as the issue suggests.

    Judgment calls

    1. Raw reports are read even after a programme closes. Following the brief and Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's UC precedent, which reads jsa_income_reported in 2026, the predicate excludes on any positive report, whatever the year. In 2026 the model treats Income Support as no longer paid (gov.dwp.income_support.active), and tax credits from 2025-26 (gov.dwp.tax_credits.active). Even so, a 2026 Income Support report or a 2025+ tax credit report still triggers reg 28 here. Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's Pension Credit test, by contrast, reads is_on_income_support, which gates on active and on neutralisation. So the same 2026 young person can be a PC young person but not a UC one (Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279 case "…leaves the Pension Credit route available once Income Support stops paying."). And a reform that neutralises Income Support doesn't restore UC status. The alternative is to gate the Income Support and tax credit reports on their active parameters. That would turn the issue's 2026 Income Support row back into £9,321.36, against the brief's expected £5,098.80, so I didn't do it. Whether this matters depends on whether the dataset carries legacy reports after closure, which I can't check.
    2. Couple award for claimant and partner only. A non-claimant's own CTC/WTC report counts for that person alone. Under s.3(3) they claim "as a single person", which the issue reads as an award of their own.
    3. Folded the lists. I extended Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's expression rather than adding a second exclusion. Income-related ESA and income-based JSA stay covered by 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.
    4. Old style awards (TP reg 2(3)). The model doesn't separate old style from new style ESA/JSA. That makes no difference here, because reg 5(5) excludes every ESA and JSA award.

    PR text for the hub

    Title: Apply reg 28 to the UC qualifying young person test
    
    Fixes #2804
    Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_qualifying_young_person_existing_benefits.yaml::A parent's young person who reports their own Income Support attracts no child element.
    axiom: hub fills this line
    
    ## What the code does now
    
    `is_qualifying_young_person_for_universal_credit` excludes a person who reports their own Income Support, Housing Benefit, Child Tax Credit or Working Tax Credit. It also excludes a claimant or partner when either member of the couple reports a tax credit award. These exclusions sit beside the reg. 5(5) exclusions (Universal Credit, and ESA or JSA of either kind) in one list. The formula reads raw person-level reports, so it calculates no family benefit that depends on qualifying young person status. A dependant's predicate depends only on their own reports.
    
    In the issue's household (2026, North East, owned outright, no income), a parent whose 17-year-old on a non-advanced course reports their own Income Support or Housing Benefit now gets £5,098.80 of Universal Credit a year, with no child element, in place of £9,321.36.
    
    ## Law
    
    - [Universal Credit (Transitional Provisions) Regulations 2014 reg. 28](https://www.legislation.gov.uk/uksi/2014/1230/regulation/28): a person entitled to an existing benefit is not a qualifying young person for the purposes of the Universal Credit Regulations, and reg. 5(5) of those Regulations applies as if it listed an existing benefit.
    - [Reg. 2(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/2): the Secretary of State lists income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit as existing benefits. Reg. 25(2) adds incapacity benefit and severe disablement allowance for regs. 22 to 25 only, so it doesn't reach reg. 28.
    - [Tax Credits Act 2002 s.3(3)](https://www.legislation.gov.uk/ukpga/2002/21/section/3): the members of a couple claim a tax credit jointly, and anyone else claims as a single person.
    - [Universal Credit Regulations 2013 reg. 5(5)](https://www.legislation.gov.uk/uksi/2013/376/regulation/5) already excludes income-related ESA and income-based JSA through the ESA and JSA reports.
    
    ## Invariants
    
    - Any positive own report of an existing benefit, or a couple tax credit report for a claimant or partner, makes the predicate false.
    - With every existing benefit report at zero, the predicate equals the reg. 5 test with the reg. 5(5) exclusions; with reports present, it never exceeds that test.
    - A claimant's or partner's reports never change a dependant's predicate.
    - A parent's Universal Credit never rises when their young person's own existing benefit report turns positive.
    - The predicate is deterministic and reads no family benefit, so no reader of it recurses: each of its readers calculates first, then `universal_credit`, for every household in the issue's table.
    
    ## Tests
    
    - `policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_qualifying_young_person_existing_benefits.yaml`: one case per row of the issue's table, plus own tax credit reports, a parent's reports, and a couple's tax credit award.
    - `policyengine_uk/tests/test_uc_qualifying_young_person_existing_benefits_properties.py`: Hypothesis properties for the four invariants above.
    - `qualifying_young_person_reported_own_awards.yaml` and `test_pension_credit_young_person_income_support.py` now assert that a young person reporting their own Income Support is not a UC qualifying young person.
    - Without the formula change, 6 of the 11 new YAML cases, the first property and the updated cases fail.
    
    ## Limitation
    
    The formula reads a report as entitlement in any year, including years after the model stops paying Income Support (from 2026) or tax credits (from 2025-26).
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    https://claude.ai/code/session_01LwQq6HNNv4UarCy16Lmxzo
    

    Generated by Claude Code

  3. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

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


    Generated by Claude Code

  4. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    PR_BODY.md

    Title: Apply SI 2014/1230 reg 28 to the UC qualifying young person test
    
    Fixes #2804
    Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_qualifying_young_person_existing_benefits.yaml::A parent's young person who reports their own Income Support attracts no child element.
    axiom: hub fills this line
    
    ## What the code does now
    
    `is_qualifying_young_person_under_universal_credit_regulation_5` applies UC Regs 2013 reg. 5: the age condition, either route in reg. 5(1), and reg. 5(5)'s exclusions for Universal Credit, contributory ESA and contribution-based JSA. `is_qualifying_young_person_for_universal_credit` is that test less SI 2014/1230 reg. 28. It excludes a person who reports their own income-related ESA, income-based JSA, Income Support, Housing Benefit, Child Tax Credit or Working Tax Credit, and a claimant or partner when either member of the couple reports a tax credit award. So the UC test keeps reg. 5(5)'s exclusions for Universal Credit, contributory ESA and contribution-based JSA, and adds reg. 28's for income-related ESA and income-based JSA.
    
    Reg. 28 turns on entitlement, so the predicate reads a report of Income Support, income-based JSA or a tax credit only in a year the model pays that benefit (`gov.dwp.income_support.active`, `gov.dwp.JSA.income.active`, `gov.dwp.tax_credits.active`) and no reform neutralises it. It reads Housing Benefit and income-related ESA reports in every year. Both variables read raw person-level reports, so they calculate no family benefit that depends on qualifying young person status. A dependant's predicate depends only on their own reports.
    
    In a household of a parent aged 45 and a 17-year-old on a non-advanced course (North East, owned outright, no income), a parent whose young person reports their own Housing Benefit in 2026, or Income Support in 2025, now gets no child element: £5,098.80 in place of £9,321.36 in 2026, and £4,801.68 in place of £8,869.68 in 2025.
    
    | Young person's own report | Before (#2279) | Now |
    |---|---|---|
    | Income Support £3,000, 2025 (paying) | true / £8,869.68 | false / £4,801.68 |
    | Income Support £3,000, 2026 (closed) | true / £9,321.36 | true / £9,321.36 |
    | Income Support £3,000, 2025, reform neutralises `income_support` | true / £8,869.68 | true / £8,869.68 |
    | Child Tax Credit £1,000, 2024 (paying) | true / £8,721.36 | false / £4,721.40 |
    | Child Tax Credit £1,000, 2025 (closed) | true / £8,869.68 | true / £8,869.68 |
    | income-based JSA £3,000, 2026 (closed) | false / £5,098.80 | true / £9,321.36 |
    | Housing Benefit £3,000, 2026 | true / £9,321.36 | false / £5,098.80 |
    
    Each cell gives the young person's predicate and the parent's annual Universal Credit.
    
    ### Scotland's working-age Council Tax Reduction
    
    `is_child_or_young_person_for_council_tax_reduction` now reads the reg. 5 variable for Scotland's working-age scheme, not the UC predicate. SSI 2021/249 reg. 6(1) defines a young person by reference to reg. 5, and the Secretary of State excludes a person entitled to an existing benefit only for the purposes of the UC Regulations and WRA 2012 Part 1 (reg. 28(a) and (b)). So a young person's own report of an existing benefit no longer takes them out of the family for Scottish CTR. That includes income-related ESA and income-based JSA, which #2279 excluded there through the same reader.
    
    What moves, in a Scottish household of a parent aged 45 and a 17-year-old on a non-advanced course, claiming CTR, council tax £1,500:
    
    | Young person's own report | Flag before (#2279) | Flag at the earlier head | Flag now | CTR before / earlier head / now |
    |---|---|---|---|---|
    | Income Support £3,000, 2025 | true | false | true | £412.46 / £1,226.06 / £1,226.06 |
    | Housing Benefit £3,000, 2024 | true | false | true | £430.69 / £1,230.68 / £1,230.68 |
    | income-related ESA £3,000, 2025 | false | false | true | £1,226.06 / £1,226.06 / £1,226.06 |
    | income-based JSA £3,000, 2025 | false | false | true | £1,226.06 / £1,226.06 / £1,226.06 |
    | Child Tax Credit £1,000, 2024 | true | false | true | £430.69 / £1,230.68 / £1,230.68 |
    | contribution-based JSA £3,000, 2025 | false | false | false | £1,226.06 / £1,226.06 / £1,226.06 |
    | none, 2025 | true | true | true | £412.46 / £412.46 / £412.46 |
    
    The young person flag moves back. The CTR award in these households doesn't move with it: the parent's Universal Credit, which reg. 28 does reduce, sets it. With `would_claim_uc` false, the award is £1,500 in every row (£1,374.96 for the Child Tax Credit row) under all three trees. Where the flag does change the award, the new Case 11 shows it: a 22-year-old with a 19-year-old who reports their own Housing Benefit stays a lone parent, with a £4,968.60 applicable amount.
    
    ### Northern Ireland
    
    The model applies the same predicate in Northern Ireland. Northern Ireland's twin of reg. 28 is SR 2016/226 reg. 29, with the existing-benefit list in reg. 2(1); both turn on entitlement, and the variable cites them.
    
    ## Law
    
    - [SI 2014/1230 reg. 28](https://www.legislation.gov.uk/uksi/2014/1230/regulation/28): where a person who would otherwise be a qualifying young person under UC Regs reg. 5 "is entitled to an existing benefit", that person isn't a qualifying young person for the purposes of the Universal Credit Regulations, and reg. 5(5) applies as if it listed an existing benefit. [SR 2016/226 reg. 29](https://www.legislation.gov.uk/nisr/2016/226/regulation/29) makes the same provision for Northern Ireland.
    - [SI 2014/1230 reg. 2(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/2): the Secretary of State lists income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit as existing benefits ([SR 2016/226 reg. 2(1)](https://www.legislation.gov.uk/nisr/2016/226/regulation/2) for Northern Ireland). In reg. 25(2) the Secretary of State prescribes incapacity benefit and severe disablement allowance for regs. 22 to 25 only, so reg. 28 doesn't reach them.
    - [UC Regs 2013 reg. 2](https://www.legislation.gov.uk/uksi/2013/376/regulation/2) and [reg. 5(5)](https://www.legislation.gov.uk/uksi/2013/376/regulation/5): UC Regs reg. 2 defines ESA and JSA without their income-related and income-based forms, so income-related ESA and income-based JSA leave the test through reg. 28.
    - [Tax Credits Act 2002 s.3(3)](https://www.legislation.gov.uk/ukpga/2002/21/section/3): the members of a couple claim a tax credit jointly, and anyone else claims as a single person.
    - Closure: the model stops paying Income Support and income-based JSA on 1 April 2026 and tax credits on 6 April 2025 (`gov.dwp.income_support.active`, `gov.dwp.JSA.income.active`, `gov.dwp.tax_credits.active`). Housing Benefit has no closure parameter. SI 2025/1148 art 3A and SR 2025/176 art 3A abolish remaining old-style income-related ESA awards from 1 July 2026 (GB) and 1 October 2026 (NI), subject to savings; the model has no closure parameter for income-related ESA yet, so the predicate reads its report in every year.
    - [SSI 2021/249 reg. 6(1)](https://www.legislation.gov.uk/ssi/2021/249/regulation/6): Scotland's working-age CTR defines a young person by reference to "the definition of 'qualifying young person' in regulation 5 of the 2013 Regulations".
    
    ## Invariants
    
    - Any positive own report of an existing benefit the model pays that year, or a paying couple tax credit report for a claimant or partner, makes the predicate false.
    - With every existing benefit report at zero, the predicate equals the reg. 5 test less the person's reported UC, ESA and JSA awards, and equals the reg. 5 variable; with reports present, it never exceeds that test.
    - The predicate equals the reg. 5 test less reg. 28's exclusions, exactly: no report of a closed benefit, no partner's Income Support or Housing Benefit and no dependant's tax credit excludes anyone else.
    - A claimant's or partner's reports never change a dependant's predicate.
    - A parent's Universal Credit never rises when their young person's own existing benefit report turns positive.
    - The predicate reads no family benefit. For each household in the issue's table, in 2025 and 2026, owned outright and privately rented, we calculated each of 14 readers first, then `universal_credit`. None raised a cycle, and Universal Credit matched a fresh calculation in every order.
    
    ## Tests
    
    - `uc_qualifying_young_person_existing_benefits.yaml` (17 cases): the issue's household, with the Income Support and income-based JSA rows in 2025 while they pay; the same reports in 2026, and a Child Tax Credit report in 2025, after the benefit closes; own tax credit reports; a parent's reports; a couple's tax credit award; a partner's own Housing Benefit, which leaves an 18-year-old claimant in the test; and a dependant's own Child Tax Credit, which does the same.
    - `test_uc_qualifying_young_person_existing_benefits_properties.py`: Hypothesis properties for the first five invariants over 2024, 2025 and 2026. The exact-equality property draws families where the claimant, the partner or dependant and both dependants would meet reg. 5 two times in three.
    - `council_tax_reduction_young_person.yaml` Cases 11 and 12: Scotland's working-age scheme keeps a young person who reports their own Housing Benefit, Income Support, income-based JSA or income-related ESA, and drops one who reports contribution-based JSA. `test_council_tax_reduction_young_person_properties.py` reads the reg. 5 variable for the Scottish branch, and `test_council_tax_reduction_severe_disability_family.py` supplies the reg. 5 status as its input.
    - `qualifying_young_person_reported_own_awards.yaml`: the three income-based JSA cases move to 2025, while it pays; a member's 2026 Income Support report leaves the UC route available.
    - `test_pension_credit_young_person_income_support.py`: the reform that removes Income Support restores the UC status as well as the Pension Credit one.
    - Against #2279's head (`c5b9850a3`), 9 of the 17 cases in the new YAML file, 5 cases in the reported-awards file, both new CTR cases, the Pension Credit reform test and three of the five properties fail. Against the previous head of this branch (`dbaf71872`), the three closed-benefit cases, the 2026 Income Support case in the reported-awards file, both new CTR cases, the Pension Credit reform test and the exact-equality property fail. The zero-report property also fails there, because it reads the reg. 5 variable this change adds.
    - Mutants that each drop one part of the gate (the Income Support, income-based JSA or tax credit year gate, or the neutralisation check) each fail at least one test; the neutralisation mutant fails only the Pension Credit reform test.
    - Two mutants of the couple rule (the couple sum reading a dependant's tax credit; the couple branch reading Income Support and Housing Benefit) each fail one new YAML case and the exact-equality property.
    
    ## Merge notes
    
    - **#2806.** If this lands after #2806, it changes four of #2806's assertions: `uc_receiving_education.yaml` cases 31 and 34 (`is_qualifying_young_person_for_universal_credit: [false]`) and 33 (`[false, false]`), and `test_examples_reach_the_cases` (`[False, False]` for people 9 and 10). #2806's variable docstring then needs its last sentence replaced (see the hand-off). The landing PR carries these.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    

    Generated by Claude Code

  5. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    Ready for hub review

    Head: 4b4d30df264e3951def2e597aed0aeca15f943ae on branch claude/reg-28-qualifying-young-person-5v7i6p (the harness pins pushes to this name). It starts at dbaf71872870f26988c802cab622919c450d95f1 and adds two commits. I opened no PR and pushed to no other branch, so no CI has run.

    Findings

    P1, closed benefits (ruling 1). is_qualifying_young_person_for_universal_credit.py:34-77 reads a report of a tax credit only while gov.dwp.tax_credits.active holds (:50-53), income-based JSA only while gov.dwp.JSA.income.active holds (:66), and Income Support only while gov.dwp.income_support.active holds (:67). Each is also skipped when a reform neutralises the benefit: income_support, jsa_income and tax_credits. Housing Benefit and IR-ESA stay ungated (:68, :65). The gate follows the reviewer's gated-reading.diff. Households A to G give the reviewer's gated column exactly: A true / £9,321.36, B false / £4,801.68, C true / £8,869.68, D false / £4,721.40, E true / £8,869.68, F true / £9,321.36, G false / £5,098.80. Against #2279's head they give the base column, and against dbaf71872 the head column. Knock-on edits:

    • New YAML: the IS and IB-JSA rows move to 2025 (£4,801.68). New cases cover IS 2026, IB-JSA 2026 and CTC 2025, each closed and asserting the child element, plus CTC 2024 while paying.
    • Reproducer: the IS case, now in 2025. It fails at c5b9850a3 and passes now.
    • Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's YAML: the three IB-JSA cases move to 2025 (qualifying_young_person_reported_own_awards.yaml:48, :224, :365), and the 2026 IS case asserts UC [false, true] with a matching name (:498).
    • PC test: the removed branch asserts UC [False, True] (test_pension_credit_young_person_income_support.py:195-198).
    • Property 1 counts only paying reports (PAYING, reg_28_exclusions).
    • The body's Limitation paragraph is gone.

    P2-1, the reg 5(5) mislabel. Fixed in the variable docstrings, the code comments, the property file (REG_5_5_REPORTS now holds UC, contributory ESA and contribution-based JSA; EXISTING_BENEFITS adds IB-JSA and IR-ESA), #2279's YAML header and the body. git grep finds no remaining place that puts IR-ESA or IB-JSA under reg 5(5).

    P2-2, Scottish CTR (ruling 2). New is_qualifying_young_person_under_universal_credit_regulation_5.py applies reg 5 with its own 5(5) exclusions. The UC predicate is that variable less reg 28. is_child_or_young_person_for_council_tax_reduction.py:62-72 reads the reg 5 variable for Scotland's working-age branch, with is_child_for_universal_credit and the looked-after and claimant-or-partner masks. That also takes #2279's IR-ESA and IB-JSA exclusions out of Scottish CTR.

    probe_ctr.py (Scotland, parent 45 and a 17-year-old, CTR claimed, council tax £1,500), flag / CTR at base c5b9850a3 → dbaf71872 → now:

    • IS 2025: true / £412.46 → false / £1,226.06 → true / £1,226.06
    • HB 2024: true / £430.69 → false / £1,230.68 → true / £1,230.68
    • IR-ESA 2025 and IB-JSA 2025: false / £1,226.06 → false → true / £1,226.06
    • CTC 2024: true / £430.69 → false / £1,230.68 → true / £1,230.68
    • contribution-based JSA 2025: false / £1,226.06 in all three
    • no report: true / £412.46 in all three

    The flag moves back, but the CTR award in these households doesn't. The award follows the parent's UC, which reg 28 does reduce: £8,869.68 at base and £4,801.68 at dbaf71872 and now, for the IS row. With would_claim_uc false, CTR is £1,500 (£1,374.96 for the CTC row) under all three trees. The new council_tax_reduction_young_person.yaml Case 11 shows where the flag moves money: a 19-year-old with their own HB keeps the 22-year-old applicant a lone parent, with a £4,968.60 applicable amount. Case 12 covers IS, IB-JSA, IR-ESA and contribution-based JSA. Both fail at c5b9850a3 and at dbaf71872. A RULINGS.md entry is the hub's to log.

    P2-3, #2806. I didn't edit #2806's files. I merged this head with a5d166266 in a scratch tree. With the four replacements below, uc_receiving_education.yaml passes 35/35 and test_examples_reach_the_cases passes. This branch's three YAML files pass 52/52 on that tree. Ruling 1 doesn't move any of the four, because cases 31 and 34 and the examples rest on Housing Benefit in 2026, and case 33 on WTC in 2024.

    • uc_receiving_education.yaml case 31 (line 824): is_qualifying_young_person_for_universal_credit: [false]
    • case 33 (line 879): [false, false]
    • case 34 (line 908): [false]
    • test_uc_receiving_education_properties.py line 701: [False, False]

    Docstring: replace #2806's last sentence ("The model's other rules read is_qualifying_young_person_for_universal_credit, which leaves reg. 28 out.") with: "is_qualifying_young_person_for_universal_credit applies reg. 28 too, but reads a report of Income Support, income-based JSA or a tax credit only in a year the model pays that benefit; this variable reads those reports in every year." If #2806 instead reads the predicate directly, as the reviewer recommends, the sentence becomes "It equals is_qualifying_young_person_for_universal_credit, which applies reg. 28." The "reads entitlement from the year's reported receipt" sentence then goes too. I haven't run that variant. Its property 8 counts raw reports in 2026 among its YEARS, so it would need the same paying-year filter.

    P2-4, mutants M1 and M2. I added two YAML cases: "A partner's own Housing Benefit report leaves an 18-year-old claimant a qualifying young person." and "A dependant's own Child Tax Credit award leaves an 18-year-old claimant a qualifying young person." I also added test_the_predicate_is_reg_5_less_reg_28 with qualifying_families, which draws claimants, partners or dependants, and dependants who meet reg 5 two times in three. I put it in the existing property file rather than a new test_reg28_exact_property.py, so it shares the cache and fixture without importing one test module from another.

    P3-1. _CACHE holds the cloned system and the simulations. A module-scoped autouse fixture clears it after yield.

    P3-2. The UC predicate cites SR 2016/226 regs 2 and 29, and its docstring names them. The reg 5 variable cites UC Regs (NI) 2016 reg 6 (nisr/2016/216/regulation/6), which I took from the review's note that reg 29 modifies that regulation's para (5).

    P3-3. PC test docstring and assertions now match its name. #2279's YAML: "…excludes them from the Pension Credit and UC statuses." (:526); "…leaves the Pension Credit and UC routes available once Income Support stops paying." (:498); "…counts as neither a Pension Credit nor a UC young person." (:439).

    P3-4 and P3-5. I applied every voice row: title, body ¶1 and ¶2, the Law, Invariants, Limitation and Changelog rows, docstring :13-20, comment :49-55, new YAML :1-8, #2279 YAML :6-8, :433 and :434, PC test :6-9, and property file :12, :65-69 and :166. Some rows needed adjusting:

    • The comment and docstring rows now mention the paying-year gate.
    • The changelog says "while the model pays it" only of the gated benefits, since HB and IR-ESA are read in every year.
    • The invariant on cycles states what I ran: 14 readers × 7 households × 2 years × 2 tenures (392 orders, cycle2.py), 0 errors, UC matching a fresh calculation in each.
    • The body names Scottish CTR, Northern Ireland and Apply the Universal Credit education condition #2806, and keeps axiom: hub fills this line.

    Axiom companion cases for uk/regulations/uksi/2014/1230/28.yaml

    Companion cases for uk/regulations/uksi/2014/1230/28.yaml (#2346's packet). Each case gives the inputs, the outcome the law gives (worked by hand), and the year. Money is the annual Universal Credit award: 12 times the monthly amounts the model holds for 2024-25 to 2026-27 (single claimant aged 25 or over £393.45, £400.14 and £424.90 a month; the child element for this young person £339.00 in 2025-26 and £351.88 in 2026-27). The model computed the same figures.

    1. Entitled to Income Support while it pays (2025-26). A parent aged 45 claims Universal Credit with a 17-year-old on a non-advanced course in the household. The 17-year-old is entitled to Income Support in their own right. They meet UC Regs reg. 5(1)(b), but reg. 28(a) applies, so they aren't a qualifying young person. No child element: 12 × £400.14 = £4,801.68. Without the Income Support entitlement: 12 × (£400.14 + £339.00) = £8,869.68.
    2. Report of a closed benefit (2026-27, closed-benefit case). The same household, and the 17-year-old reports Income Support received, but Income Support stopped paying on 31 March 2026, so nobody is entitled to it in 2026-27. Reg. 28 doesn't apply and the 17-year-old is a qualifying young person: 12 × (£424.90 + £351.88) = £9,321.36. The same holds for a report of income-based JSA in 2026-27, and for a report of Child Tax Credit in 2025-26 (tax credits ended on 5 April 2025): 12 × (£400.14 + £339.00) = £8,869.68.
    3. Entitled to Housing Benefit (2026-27). The same household, and the 17-year-old is entitled to Housing Benefit in their own right (Housing Benefit stays open). Reg. 28 excludes them: 12 × £424.90 = £5,098.80.
    4. Joint tax credit award (2024-25). A couple, both aged 18 and both on non-advanced courses, hold a joint Working Tax Credit award. Under TCA 2002 s.3(3)(a) both members are entitled to it, so reg. 28 excludes both from the qualifying young person test, whichever partner reports it.
    5. A dependant's own tax credit award (2024-25). A claimant aged 18 on a non-advanced course lives with a dependant aged 17 on a non-advanced course who holds their own Child Tax Credit award, claimed as a single person under TCA 2002 s.3(3)(b). Reg. 28 excludes the dependant, not the claimant: the claimant meets reg. 5 and isn't entitled to an existing benefit, so the claimant remains a qualifying young person.
    6. A partner's own Housing Benefit (2024-25). A claimant aged 18 on a non-advanced course has a partner aged 30 who is entitled to Housing Benefit. Only the person who claims Housing Benefit is entitled to it, so the claimant isn't and remains a qualifying young person. Reg. 28 doesn't apply to the partner either, because the partner fails reg. 5's age condition.

    Tests run (this head)

    • uc_qualifying_young_person_existing_benefits.yaml: 17/17.
    • qualifying_young_person_reported_own_awards.yaml: 23/23.
    • council_tax_reduction_young_person.yaml: 12/12.
    • The readers' YAML (child_qualifying_young_person.yaml, family/universal_credit/, qualifying_young_person_no_education_route.yaml, benefit_cap.yaml, benefit_cap/, means_test_income_members.yaml, council_tax_reduction/): 1,093 passed.
    • Full YAML policy suite: 5,293 passed, 0 failed, run in two parts. The first process, over all of tests/policy, was killed (out of memory) after baseline/ (489 files) and integration/ (7 files) had passed, 5,179 cases. I then ran the 15 reforms/ files one by one: 114 passed.
    • Python readers and this branch's files (benefit cap specified benefits, CTR severe disability family, CTR young person properties, means-test income, UC claimant elements, UC claimant income, child and adult definitions, legacy award readers, this property file, PC test): 133 passed and 1 failed on the first commit.
      • The failure was test_own_family_status_contract_by_applicant_scheme[SCOTLAND-2026-45-True], which fed is_qualifying_young_person_for_universal_credit as an input to Scottish CTR. The second commit supplies the reg 5 variable; the file now passes 8/8.
      • The rest of that run: property file plus PC test 15/15, and CTR young person properties passing.
    • code_health: 3,008 passed.
    • ruff 0.17.2 format --check, check, check --select F811,F821, and git diff --check: clean.
    • test_uc_receiving_education_properties.py is Apply the Universal Credit education condition #2806's and isn't in this base.

    Revert checks. I ran this branch's tests against the formula at each base.

    Skipped

    • Survey-data tests: no dataset access.
    • The Enhanced FRS question is still open.
    • The full Python suite is left to CI.
    • I didn't fetch or quote any law beyond what the brief and the review give.

    Judgment calls

    • The gate also checks neutralisation of jsa_income and tax_credits, as the reviewer's diff does, not only income_support.
    • The variable is named is_qualifying_young_person_under_universal_credit_regulation_5.
    • The reg 28 exclusions stay inline in the UC predicate rather than in a third variable.
    • Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's IB-JSA cases move to 2025 rather than asserting the 2026 child element, so their names stay true. The new file holds the 2026 closed cases.
    • The exact property lives in the existing property file.
    • The E household (reform neutralising IS) is covered by the PC test's reform branch, with a parent aged 70, and by the probe. It has no YAML case because the YAML runner needs a reform module.
    • test_council_tax_reduction_young_person_properties.py and test_council_tax_reduction_severe_disability_family.py now read or supply the reg 5 variable for Scotland, since that's the status SSI 2021/249 reg 6 imports.

    Generated by Claude Code

  6. MaxGhenis commented on Nov 2, 2026

    @MaxGhenis
    CollaboratorAuthor

    Ready for hub review

    Verdict: REQUEST_CHANGES at 4b4d30df264e3951def2e597aed0aeca15f943ae

    This is the delta review of dbaf71872870f26988c802cab622919c450d95f1..4b4d30df264e3951def2e597aed0aeca15f943ae on claude/reg-28-qualifying-young-person-5v7i6p.

    The code follows both rulings, and every number I could rerun matches the hand-off. Ruling 1 holds: households A to G give the gated column exactly. Ruling 2 holds: the Scottish probe rows match the hand-off's figures on all three trees. M1, M2 and the four gate mutants die. Merges with main, #2279 and #2806 are clean, and #2806's tests pass with the four replacements.

    I'm requesting changes for three P2 items, all text or test comments; the formula needs no change:

    There are no P1s.

    Scripts named below (scripts/*.py) ran in this review's cloud scratch space against detached worktrees, and they aren't in the repository. Each table gives the command's output as run.

    Findings

    P2-a – P2-1 is only partly closed: two #2279 test files still put IR-ESA and IB-JSA under reg. 5(5)

    The hand-off says "git grep finds no remaining place that puts IR-ESA or IB-JSA under reg 5(5)". Running git grep -n -i "5(5)" -- policyengine_uk at the head finds two such places. Both came in with #2279 (d6bcbf89 and 4b09a360, ancestors of c5b9850a3, not of main), so this branch carries them under the brief.

    1. policyengine_uk/tests/test_legacy_award_readers_properties.py:687-690. The test is parametrised over ["esa_income_reported", "jsa_income_reported"], and its comment reads "UC reg 5(5) and SPC reg 4A(5) still exclude the member's own award." Replace the comment with:

      # SI 2014/1230 reg. 28 excludes the member's own income-related ESA or income-based JSA (paying in 2025) from the UC test, and SPC reg. 4A(5) from the Pension Credit test.

    2. policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py.

      • Lines 127-133: OWN_RIGHT_REPORTS holds esa_income_reported and jsa_income_reported.
      • Lines 280-282: own_benefits labels the whole tuple "UC reg 5(5): a personal report identifies an award in the person's own right."
      • Line 34 of the docstring says "does not receive benefits in their own right (reg 5(5))".

      The oracle still agrees with the model only because YEAR = 2025 (line 91), when income-based JSA pays. Move the year to 2026 and the oracle goes wrong, because the predicate then reads no income-based JSA report.

      Replace line 281 with:

      # UC reg. 5(5) (UC, contributory ESA, contribution-based JSA) and SI 2014/1230 reg. 28 (income-related ESA, and income-based JSA while it pays, as in 2025): a personal report identifies an award in the person's own right.

      Replace "(reg 5(5))" at line 34 with "(reg 5(5); SI 2014/1230 reg. 28)".

    The other 5(5) hits are sound:

    • uc_unearned_income.yaml:344, qualifying_young_person_no_education_route.yaml:152, qualifying_young_person_reported_own_awards.yaml:1-11 and :428, and council_tax_reduction_young_person.yaml:405 concern contributory awards or the own-right flag.
    • benefit_cap.yaml:158, benefit_cap_specified_benefits.yaml:1235 and is_uc_benefit_cap_exempt_specified_benefit.py:113 read the receives_benefits_in_own_right flag.

    P2-b – The body's Case 11 sentence is false: Case 11 shows the applicable amount, not the award

    The PR body (Scotland section, last paragraph) says: "Where the flag does change the award, the new Case 11 shows it". The hand-off says the same: "Case 11 shows where the flag moves money".

    Case 11 (council_tax_reduction_young_person.yaml:355-389) asserts the flags, is_council_tax_reduction_lone_parent and council_tax_reduction_applicable_amount: 4_968.60. It asserts no award. Its household's award is £0 at both dbaf71872 and the head, because the household does not take up CTR. I ran the case's inputs through a Simulation (scripts/case11.py):

    Tree Flag Applicable amount would_claim_council_tax_reduction council_tax_reduction
    dbaf71872 false £3,933.80 false £0.00
    head true £4,968.60 false £0.00
    head, no HB report true £4,968.60 true £1,800.00

    The flag moves the award only once applicable income exceeds the applicable amount. I took the same household with would_claim_council_tax_reduction: true, council tax £1,800 and the applicant's employment income set (scripts/case11c.py). The young person reports either income-related ESA or Housing Benefit, £3,000, in 2026:

    Young person's report, applicant's earnings #2279 c5b9850a3 dbaf71872 head
    IR-ESA, £0 £1,800.00 £1,800.00 £1,800.00
    IR-ESA, £5,000 £1,305.44 £1,305.44 £1,512.40
    IR-ESA, £8,000 £705.44 £705.44 £912.40
    HB, £5,000 £1,512.40 £1,305.44 £1,512.40

    Against #2279, the base this lands on, Scottish CTR moves only for income-related ESA and income-based JSA reports. The IS, HB and tax credit rows return to #2279's values.

    Replace the body's last two sentences of that paragraph ("Where the flag does change the award … applicable amount.") with:

    The flag sets the applicable amount, which moves the award once applicable income exceeds it. In the new Case 11, a 22-year-old with a 19-year-old who reports their own Housing Benefit stays a lone parent, with a £4,968.60 applicable amount in place of £3,933.80. Take the same household with an income-related ESA report instead, CTR claimed and £5,000 of the applicant's earnings in 2026: the award rises from £1,305.44 under #2279 to £1,512.40.

    Make the same correction in the hand-off. If the hub wants the award pinned, add would_claim_council_tax_reduction: true, employment_income: 5_000 and a council_tax_reduction assertion as a separate case.

    P2-c – #2806 merge note: the proposed docstring sentence is accurate, but it describes a merged tree that contradicts ruling 1

    On the tree merged with #2806's head a5d166266 (merge-tree 29c87347), is_qualifying_young_person_for_uc_education returns is_qualifying_young_person_for_universal_credit & ~(own_award | joint_tax_credits). It reads the raw EXISTING_BENEFITS reports in every year. So in a closed year it treats a report as entitlement under reg. 28, which ruling 1 rejects.

    I took a single claimant aged 18 on an upper-secondary course, North East, owned outright, reporting £3,000 (scripts/probe2806.py, merged tree). The columns are the UC predicate, #2806's variable, uc_receiving_education and the UC award:

    Report Predicate #2806 variable Receiving education UC
    IS, 2026 true false false £4,062.96
    IB-JSA, 2026 true false false £4,062.96
    CTC, 2025 true false false £3,803.76
    IS, 2025 false false false £3,803.76

    The award doesn't move in this household, but the two reg. 28 readings disagree in every closed year. The hand-off's first proposed sentence matches the code, but it writes that disagreement into a docstring. It also omits the neutralisation check.

    Recommendation for the landing PR: take the hand-off's alternative and make #2806's variable return is_qualifying_young_person_for_universal_credit. With that change on the merged tree:

    • the 2026 IS and IB-JSA rows and the 2025 CTC row give true for Apply the Universal Credit education condition #2806's variable;
    • uc_receiving_education.yaml passes 35/35 with the four replacements;
    • test_uc_receiving_education_properties.py fails 1 of 3 (test_education_condition_invariants, 381 s). Hypothesis falsifies it in 2026 with a member reporting working_tax_credit_reported: 1000: the variable gives True and the oracle False, because the oracle still counts a closed-year tax credit report. The hand-off predicted this. The oracle needs the same paying-year filter as this branch's PAYING; carry that edit with the variant.

    Then use the hand-off's second sentence ("It equals is_qualifying_young_person_for_universal_credit, which applies reg. 28."). Drop "The model reads entitlement from the year's reported receipt…". If the hub keeps #2806's own reading instead, the sentence needs the neutralisation clause:

    is_qualifying_young_person_for_universal_credit applies reg. 28 too, but reads a report of Income Support, income-based JSA or a tax credit only in a year the model pays that benefit and no reform neutralises it; this variable reads those reports in every year.

    P3-a – Neutralisation covers three aggregate variables only, and only Income Support's is tested

    is_qualifying_young_person_for_universal_credit.py:40-50 checks is_neutralized on income_support, jsa_income and tax_credits. Ruling 1 names only income_support, so this follows the ruling, but two gaps remain (scripts/cycle.py, head):

    • A reform that neutralises child_tax_credit alone leaves household D (2024, own CTC £1,000) excluded: [False, False].
    • A reform that neutralises housing_benefit leaves household G (2026, own HB) excluded: [False, False]. Ruling 1 reads HB "in every year", so this follows the ruling. The hub may want to say whether it also meant "under every reform", since it is household E's problem for HB.
    • jsa_income neutralisation works: a 2025 IB-JSA report under that reform gives [False, True].
    • No test asserts the predicate under a reform that neutralises jsa_income or tax_credits. The only test that does is the PC test, and it neutralises income_support. That is why M8 fails only the PC test.

    There is no text change for the hub's ruling here, only a note. If the gate stays, consider one YAML-free Python case that neutralises tax_credits in 2024.

    P3-b – The exact property's generator meets reg. 5 less often than its docstring says

    test_uc_qualifying_young_person_existing_benefits_properties.py:142-146 (the qualifying_families docstring) says each slot "would meet reg. 5 two times in three". The PR body's Tests bullet says the same. But qualifying is built from member(), which draws a reg. 5(5) report three times in seven (sampled_from((None,) * 4 + REG_5_5_REPORTS), line 85). So a qualifying draw meets reg. 5 at most four times in seven, and a slot at most about 8 in 21.

    The property still kills M1, M2, M7, M9 and M10, so this is wording. Two fixes:

    • Give member() an argument that turns off the reg. 5(5) report draw, and pass it for qualifying.
    • Or change the docstring and body to: "The claimant, the partner or dependant, and both dependants are drawn two times in three from people aged 16 to 19 on a non-advanced course, who meet reg. 5 unless a report excludes them."

    P3-c – The PR body describes how the work was produced

    P3-d – The Northern Ireland UC Regs citation has no quoted source

    The reg. 5 variable cites https://www.legislation.gov.uk/nisr/2016/216/regulation/6, and its docstring says "Northern Ireland's twin is UC Regs (NI) 2016 reg. 6". No input quotes the SR number 2016/216 or the text of that regulation. The previous review said only that SR 2016/226 reg 29 "modifies UC Regs (NI) 2016 reg 6(5)". legislation.gov.uk doesn't resolve from here, so the hub should confirm the number before landing.

    P3-e – The Axiom companion cases: case 6 rests on a provision nobody quoted

    Cases 1 to 5 rest on law quoted in the brief, issue or body:

    • reg. 28 and reg. 2(1);
    • UC Regs reg. 5(1)(b);
    • TCA 2002 s.3(3)(a) and (b);
    • the closure dates the model's parameters hold.

    Case 6 says "Only the person who claims Housing Benefit is entitled to it". No input quotes a Housing Benefit provision for that; the previous review stated it without a citation too. The outcome doesn't depend on it, because the 30-year-old partner fails reg. 5's age condition. Either cite the HB entitlement provision once the hub has it, or replace the sentence with:

    The partner fails reg. 5's age condition, so reg. 28 has no one to exclude; the claimant reports no existing benefit and no tax credit is reported, so the claimant remains a qualifying young person.

    P3-f – Writing rules in the delta's text

    A regulation, scheme or document acts in several new lines, and one comment is passive.

    Where Text Replacement
    is_qualifying_young_person_under_universal_credit_regulation_5.py:14-17 Universal Credit Regulations 2013 reg. 2 defines employment and support allowance and jobseeker's allowance without their income-related and income-based forms, so this test leaves those to SI 2014/1230 reg. 28, which … applies. The Secretary of State defines employment and support allowance and jobseeker's allowance without their income-related and income-based forms (UC Regs 2013 reg. 2), so this test leaves those to SI 2014/1230 reg. 28, which is_qualifying_young_person_for_universal_credit applies.
    same file :19-21 Scotland's working-age Council Tax Reduction scheme defines its young person by this regulation (SSI 2021/249 reg. 6(1)). The Scottish Ministers define the young person for Scotland's working-age Council Tax Reduction by this regulation (SSI 2021/249 reg. 6(1)).
    same file :50 # Reg. 5(5) excludes a person receiving … # The Secretary of State excludes a person receiving Universal Credit, contributory ESA or contribution-based JSA (reg. 5(5)), on both routes.
    is_qualifying_young_person_for_universal_credit.py:34 # TP reg. 28 excludes a person entitled to an existing benefit (TP reg. 2(1)). # The Secretary of State excludes a person entitled to an existing benefit (TP regs. 2(1) and 28).
    property file :46-47 # UC Regs reg. 2 defines ESA and JSA without … so reg. 5(5) reaches only these. # The Secretary of State defines ESA and JSA without their income-related and income-based forms (UC Regs reg. 2), so reg. 5(5) reaches only these.
    council_tax_reduction_young_person.yaml Case 11 comment SSI 2021/249 reg. 6(1) reads UC Regs reg. 5, so the 19-year-old stays in the family. The Scottish Ministers define the young person by UC Regs reg. 5 (SSI 2021/249 reg. 6(1)), so the 19-year-old stays in the family.
    same file, Case 12 comment (:405) Contribution-based JSA is a reg. 5(5) benefit, so it alone excludes. The Secretary of State excludes a person receiving contribution-based JSA (reg. 5(5)), so only that report removes the young person.
    uc_qualifying_young_person_existing_benefits.yaml, partner HB case comment Housing Benefit is paid to the claimant of that benefit, so the partner's report is their own and the couple branch reads only tax credits. The partner reports their own Housing Benefit, and the formula reads only tax credits across the couple.
    #2279 YAML:1-3 (qualifying_young_person_reported_own_awards.yaml) UC reg. 5(5) excludes a person receiving …; UC Regs reg. 2 defines ESA and JSA … SPC reg. 4A(5) excludes own awards … The Secretary of State excludes a person receiving Universal Credit, contributory ESA or contribution-based JSA (UC reg. 5(5)), and defines ESA and JSA without their income-related and income-based forms (UC Regs reg. 2). … The Secretary of State excludes own awards from the Pension Credit routes, including income support (SPC reg. 4A(5)): …
    PR body, Law bullet 3 UC Regs reg. 2 defines ESA and JSA without their income-related and income-based forms, so … The Secretary of State defines ESA and JSA without their income-related and income-based forms (UC Regs reg. 2), so income-related ESA and income-based JSA leave the test through reg. 28.
    Changelog 2804.fixed.md, sentence 2 Scotland's working-age Council Tax Reduction now reads the Universal Credit Regulations reg. 5 test without reg. 28. The formula for Scotland's working-age Council Tax Reduction reads the Universal Credit Regulations 2013 reg. 5 test without reg. 28.

    The body Law bullet and the property-file comment come from the previous round's own voice rows. I'm correcting them here, not faulting the author.


    Previous-round findings and rulings

    Item Status Evidence
    P1 / ruling 1 (paying-year gate) Closed See the code and the A–G table below.
    P2-1 (reg. 5(5) mislabel) Partly closed The variables, comments, property file, #2279's YAML header and body are fixed. Two #2279 test files remain (P2-a).
    P2-2 / ruling 2 (Scottish CTR) Closed in code; body sentence wrong (P2-b) See below.
    P2-3 (#2806) Closed for the four expectations; docstring note needs work (P2-c) See below.
    P2-4 (M1, M2) Closed Both killed by a named YAML case and the exact property.
    P3-1 (cache release) Closed _CACHE and a module-scoped autouse fixture clear it after yield.
    P3-2 (NI references) Closed for SR 2016/226 regs. 2 and 29; the UC Regs (NI) number is unquoted (P3-d)
    P3-3 (test names) Closed The PC test asserts removed UC [False, True] and its docstring matches. #2279's YAML names now read "…Pension Credit and UC statuses" (:526), "…Pension Credit and UC routes…" (:498) and "counts as neither…" (:439).
    P3-4 (body lines) Closed Scotland, NI and #2806 are named, and axiom: hub fills this line is kept. The cycle invariant now states what was run.
    P3-5 (voice rows) Closed, with the extra rows in P3-f

    Ruling 1 code. I read is_qualifying_young_person_for_universal_credit.py:34-77.

    • paying(active, name) returns bool(active) and not system.get_variable(name).is_neutralized.
    • Tax credit reports are multiplied by tax_credits_paying before the couple sum, so the couple branch is gated too.
    • jsa_income_paying and income_support_paying gate lines 66-67.
    • esa_income_reported (:65) and housing_benefit_reported (:68) are ungated.
    • The formula reads no calculated benefit.
    • system.parameters(y) gives tax_credits.active True/False/False and income_support.active True/True/False for 2024/2025/2026, so the gate reads 2025 as tax credits closed and IS paying.

    Households A to G (scripts/probe.py; parent 45 and 17-year-old on POST_SECONDARY, North East, owned outright, no income). Each cell gives the young person's predicate and the parent's annual UC.

    Household #2279 c5b9850a3 dbaf71872 head
    A: 2026, own IS £3,000 true / £9,321.36 false / £5,098.80 true / £9,321.36
    B: 2025, own IS £3,000 true / £8,869.68 false / £4,801.68 false / £4,801.68
    C: 2025, own CTC £1,000 true / £8,869.68 false / £4,801.68 true / £8,869.68
    D: 2024, own CTC £1,000 true / £8,721.36 false / £4,721.40 false / £4,721.40
    E: 2025, own IS, reform neutralises income_support true / £8,869.68 false / £4,801.68 true / £8,869.68
    F: 2026, own IB-JSA £3,000 false / £5,098.80 false / £5,098.80 true / £9,321.36
    G: 2026, own HB £3,000 true / £9,321.36 false / £5,098.80 false / £5,098.80

    The head matches the previous review's gated column and the hand-off in all seven, and the PR body's first table in all seven rows.

    Ruling 2 code.

    • is_qualifying_young_person_under_universal_credit_regulation_5.py applies the age condition, reg. 5(1)(a) or (b), and a reg. 5(5) exclusion of receives_benefits_in_own_right, universal_credit_reported, esa_contrib_reported and jsa_contrib_reported. It has no reg. 28 term.
    • The UC predicate is that variable & ~entitled_to_existing_benefit.
    • The Scottish working-age branch of is_child_or_young_person_for_council_tax_reduction.py:60-72 reads (is_child_for_universal_credit | reg-5 variable) & ~looked_after & ~claimant_or_partner. That mirrors is_child_or_qualifying_young_person_for_universal_credit exactly, with reg. 5 in place of the UC predicate.

    Scottish probe (scripts/probe_ctr.py; Scotland, parent 45 and 17-year-old, CTR claimed, council tax £1,500). Each cell gives the flag / CTR; the last column gives CTR with would_claim_uc false.

    Report #2279 dbaf71872 head CTR, no UC claim (all three)
    IS 2025 true / £412.46 false / £1,226.06 true / £1,226.06 £1,500
    HB 2024 true / £430.69 false / £1,230.68 true / £1,230.68 £1,500
    IR-ESA 2025 false / £1,226.06 false / £1,226.06 true / £1,226.06 £1,500
    IB-JSA 2025 false / £1,226.06 false / £1,226.06 true / £1,226.06 £1,500
    CTC 2024 true / £430.69 false / £1,230.68 true / £1,230.68 £1,374.96
    contribution-based JSA 2025 false / £1,226.06 false / £1,226.06 false / £1,226.06 £1,500
    none 2025 true / £412.46 true / £412.46 true / £412.46 £1,500

    Every figure matches the hand-off and the body. The body's statement that the award doesn't move in these households holds: the parent's UC is the same at dbaf71872 and the head (£4,801.68 for IS 2025, £4,721.40 for HB and CTC 2024), and so is CTR. Cases 11 and 12 pass at the head and fail at c5b9850a3 and at dbaf71872 (2 failed each). Mutant M11, which points the Scottish branch back at the UC predicate, fails exactly those two cases.

    Ruling 2 against the quoted law. The brief quotes SSI 2021/249 reg. 6(1) as defining a young person "by reference to the definition of 'qualifying young person' in regulation 5 of the 2013 Regulations". It quotes reg. 28 as applying "for the purposes of the Universal Credit Regulations" and WRA 2012 Part 1. Nothing quoted contradicts the ruling.


    Mutants (scratch copy, restored after each; scripts/mutate.py, scripts/runmut.sh)

    Each mutant ran against:

    • the three YAML files;
    • the property file;
    • the PC test;
    • test_council_tax_reduction_severe_disability_family.py;
    • for M14, also council_tax_reduction/ and test_council_tax_reduction_young_person_properties.py.
    Mutant Result Failing tests
    M1: couple sum unmasked killed YAML "A dependant's own Child Tax Credit award leaves an 18-year-old claimant a qualifying young person."; test_the_predicate_is_reg_5_less_reg_28
    M2: couple branch adds IS and HB killed YAML "A partner's own Housing Benefit report leaves an 18-year-old claimant a qualifying young person."; exact property
    M7: IS year gate removed, neutralisation kept killed "A young person's own Income Support report after Income Support stops paying leaves the child element."; #2279 YAML "A member's Income Support report leaves the Pension Credit and UC routes available once Income Support stops paying."; exact property. The PC test passes, because I kept the neutralisation check. The hand-off's M7, which fails the PC test too, must have dropped both.
    M8: no neutralisation check killed only test_own_income_support_ends_the_young_person_until_a_reform_removes_it (see P3-a)
    M9: tax credit year gate removed killed "A young person's own Child Tax Credit report after tax credits stop paying leaves the child element."; exact property
    M10: IB-JSA year gate removed killed "A young person's own income-based JSA report after income-based JSA stops paying leaves the child element."; exact property
    M11 (mine): Scottish CTR reads the UC predicate again killed CTR Cases 11 and 12
    M12 (mine): reg. 5 variable drops jsa_contrib_reported killed five #2279 contribution-based JSA cases; CTR Case 12; exact property; zero-report property
    M13 (mine): IR-ESA gated on JSA.income.active killed "…reports their own income-related ESA attracts no child element."; three #2279 IR-ESA cases
    M14 (mine): Scottish branch drops the looked-after mask killed, though it survives the three YAML files and the 333-case council_tax_reduction/ YAML directory test_council_tax_reduction_young_person_properties.py: test_scheme_selection_over_every_combination, test_each_scheme_reads_its_own_young_person, test_schemes_differ_only_where_the_two_definitions_differ
    M15 (mine): HB gated on income_support.active killed the 2026 parent-HB and 18-year-old-HB cases; CTR Case 11; property 1; exact property

    Axiom companion cases (head, Simulation; scripts/axiom.py, scripts/probe.py)

    Case Law's outcome (hand-off) Model at head
    1. own IS, 2025 excluded; £4,801.68; £8,869.68 without IS B: false / £4,801.68. No report: £8,869.68
    2. closed reports: IS 2026, IB-JSA 2026, CTC 2025 QYP; £9,321.36, £9,321.36, £8,869.68 A, F, C: true / £9,321.36, £9,321.36, £8,869.68
    3. own HB, 2026 excluded; £5,098.80 G: false / £5,098.80
    4. joint WTC, two 18-year-olds, 2024 both excluded whichever partner reports claimant reports [false, false]; partner reports [false, false]; no report [true, true]
    5. dependant's own CTC, 2024 claimant remains a QYP [true, false]
    6. partner's own HB, 2024 claimant remains a QYP [true, false] (but see P3-e)

    I checked the arithmetic by hand:

    • 12 × £400.14 = £4,801.68
    • 12 × (£400.14 + £339.00) = £8,869.68
    • 12 × (£424.90 + £351.88) = £9,321.36
    • 12 × £424.90 = £5,098.80
    • £4,721.40 / 12 = £393.45

    Case 6 cites a provision nobody quoted (P3-e).

    Checked and sound

    • Tests at head (uv sync --frozen --extra dev, HUGGING_FACE_TOKEN and POLICYENGINE_UK_DEFAULT_DATASET unset):
      • uc_qualifying_young_person_existing_benefits.yaml 17/17;
      • qualifying_young_person_reported_own_awards.yaml 23/23;
      • council_tax_reduction_young_person.yaml 12/12.
    • Readers' YAML: 1,070 passed, 0 failed. That is child_qualifying_young_person.yaml 8, family/universal_credit/ 574, qualifying_young_person_no_education_route.yaml 14, benefit_cap.yaml 18, benefit_cap/ 116, means_test_income_members.yaml 7 and council_tax_reduction/ 333. The hand-off's 1,093 equals this plus the 23-case reported-awards file.
    • Python: 95 passed, 0 failed, in 924 s. The run covered the property file, the PC test, test_council_tax_reduction_young_person_properties.py, test_council_tax_reduction_severe_disability_family.py, test_legacy_award_readers_properties.py and test_benefit_cap_specified_benefits_properties.py.
    • Revert counts: I ran this head's YAML against the code of c5b9850a3 and of dbaf71872.
      • At c5b9850a3: 9/17, 5/23 and 2/12 fail. The 9 include the Reproducer "A parent's young person who reports their own Income Support attracts no child element."
      • At dbaf71872: 3/17, 1/23 and 2/12 fail.
      • Both match the body.
    • code_health: 3,008 passed in 242 s, 0 failed.
    • Lint: ruff 0.17.2 format --check (2,098 files already formatted) and ruff check (all checks passed) are clean, and so is git diff --check dbaf71872 4b4d30df.
    • Merges (git merge-tree --write-tree, clean, no conflicts):
    • Apply the Universal Credit education condition #2806 on the merged tree: uc_receiving_education.yaml fails 3/35 before the replacements. With the four replacements (lines 824, 879, 908 and property line 701), it passes 35/35, and test_uc_receiving_education_properties.py passes 3/3 in 139 s.
    • Cycles: I ran 210 calculation orders: households A–G × North East and Scotland × 15 readers, each calculated before universal_credit. That gave 0 errors, and UC matched a fresh calculation in every order.
      • The readers were the UC child/QYP flag, uc_child_index, the four child-limit variables, both disabled-child elements, the responsibility flag, lha_renter_has_non_dependant, the LHA shared-rate renter, the UC cap exemption, the CTR young person flag, the reg. 5 variable and council_tax_reduction.
      • Fourteen further orders errored only because I misnamed benefit_cap, which doesn't exist.
    • The predicate reads no family benefit: I read both formulas; they read only person-level reports, is_claimant_or_partner, the reg. 5 inputs and parameters.

    What I couldn't check

    • Anything resting on law beyond the brief, issue and body. legislation.gov.uk doesn't resolve from here. This includes the SR number of UC Regs (NI) 2016 (P3-d) and the HB entitlement point in Axiom case 6 (P3-e).
    • The hand-off's 392-order cycle run (cycle2.py) and its privately rented tenure. I ran my own 210 orders, owned outright only.
    • The full YAML suite (5,293), which the brief told me not to rerun.
    • The Enhanced FRS question: there is no dataset access here.
    • CI: no PR exists, so no CI has run.

    Generated by Claude Code

  7. MaxGhenis commented on Nov 3, 2026

    @MaxGhenis
    CollaboratorAuthor

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


    Generated by Claude Code

  8. MaxGhenis commented on Nov 3, 2026

    @MaxGhenis
    CollaboratorAuthor

    PR_BODY.md

    Title: Apply SI 2014/1230 reg 28 to the UC qualifying young person test
    
    Fixes #2804
    Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_qualifying_young_person_existing_benefits.yaml::A parent's young person who reports their own Income Support attracts no child element.
    axiom: hub fills this line
    
    ## What the code does now
    
    `is_qualifying_young_person_under_universal_credit_regulation_5` applies UC Regs 2013 reg. 5: the age condition, either route in reg. 5(1), and reg. 5(5)'s exclusions for Universal Credit, contributory ESA and contribution-based JSA. `is_qualifying_young_person_for_universal_credit` is that test less SI 2014/1230 reg. 28. It excludes a person who reports their own income-related ESA, income-based JSA, Income Support, Housing Benefit, Child Tax Credit or Working Tax Credit, and a claimant or partner when either member of the couple reports a tax credit award. So the UC test keeps reg. 5(5)'s exclusions for Universal Credit, contributory ESA and contribution-based JSA, and adds reg. 28's for income-related ESA and income-based JSA.
    
    Reg. 28 turns on entitlement, so the predicate reads a report only in a year the model pays that benefit, and only if no reform neutralises it. The model pays Income Support, income-based JSA and tax credits only while `gov.dwp.income_support.active`, `gov.dwp.JSA.income.active` and `gov.dwp.tax_credits.active` hold, and has no closure date for Housing Benefit or income-related ESA. The formula checks each report against its own variable: `income_support`, `jsa_income`, `esa_income`, `housing_benefit`, `child_tax_credit` or `tax_credits` for Child Tax Credit, and `working_tax_credit` or `tax_credits` for Working Tax Credit. It checks each credit the same way in the couple's joint award. Both variables read raw person-level reports, so they calculate no family benefit that depends on qualifying young person status. A dependant's predicate depends only on their own reports.
    
    In a household of a parent aged 45 and a 17-year-old on a non-advanced course (North East, owned outright, no income), a parent whose young person reports their own Housing Benefit in 2026, or Income Support in 2025, now gets no child element: £5,098.80 in place of £9,321.36 in 2026, and £4,801.68 in place of £8,869.68 in 2025.
    
    | Young person's own report | Before (#2279) | Now |
    |---|---|---|
    | Income Support £3,000, 2025 (paying) | true / £8,869.68 | false / £4,801.68 |
    | Income Support £3,000, 2026 (closed) | true / £9,321.36 | true / £9,321.36 |
    | Income Support £3,000, 2025, reform neutralises `income_support` | true / £8,869.68 | true / £8,869.68 |
    | Child Tax Credit £1,000, 2024 (paying) | true / £8,721.36 | false / £4,721.40 |
    | Child Tax Credit £1,000, 2024, reform neutralises `child_tax_credit` | true / £8,721.36 | true / £8,721.36 |
    | Child Tax Credit £1,000, 2025 (closed) | true / £8,869.68 | true / £8,869.68 |
    | income-based JSA £3,000, 2026 (closed) | false / £5,098.80 | true / £9,321.36 |
    | Housing Benefit £3,000, 2026 | true / £9,321.36 | false / £5,098.80 |
    | Housing Benefit £3,000, 2026, reform neutralises `housing_benefit` | true / £9,321.36 | true / £9,321.36 |
    
    Each cell gives the young person's predicate and the parent's annual Universal Credit.
    
    ### Scotland's working-age Council Tax Reduction
    
    `is_child_or_young_person_for_council_tax_reduction` now reads the reg. 5 variable for Scotland's working-age scheme, not the UC predicate. SSI 2021/249 reg. 6(1) defines a young person by reference to reg. 5, and the Secretary of State excludes a person entitled to an existing benefit only for the purposes of the UC Regulations and WRA 2012 Part 1 (reg. 28(a) and (b)). So a young person's own report of an existing benefit no longer takes them out of the family for Scottish CTR. That includes income-related ESA and income-based JSA, which #2279 excluded there through the same reader.
    
    What moves, in a Scottish household of a parent aged 45 and a 17-year-old on a non-advanced course, claiming CTR, council tax £1,500:
    
    | Young person's own report | Flag before (#2279) | Flag now | CTR before (#2279) / now |
    |---|---|---|---|
    | Income Support £3,000, 2025 | true | true | £412.46 / £1,226.06 |
    | Housing Benefit £3,000, 2024 | true | true | £430.69 / £1,230.68 |
    | income-related ESA £3,000, 2025 | false | true | £1,226.06 / £1,226.06 |
    | income-based JSA £3,000, 2025 | false | true | £1,226.06 / £1,226.06 |
    | Child Tax Credit £1,000, 2024 | true | true | £430.69 / £1,230.68 |
    | contribution-based JSA £3,000, 2025 | false | false | £1,226.06 / £1,226.06 |
    | none, 2025 | true | true | £412.46 / £412.46 |
    
    The CTR award in these households follows the parent's Universal Credit, which reg. 28 does reduce. With `would_claim_uc` false, the award is £1,500 in every row (£1,374.96 for the Child Tax Credit row). The flag sets the applicable amount, which moves the award once applicable income exceeds it. In Case 11, a 22-year-old with a 19-year-old who reports their own Housing Benefit stays a lone parent, with a £4,968.60 applicable amount in place of £3,933.80. Case 13 takes the same household with an income-related ESA report instead, CTR claimed and £5,000 of the applicant's earnings in 2026: the award rises from £1,305.44 under #2279 to £1,512.40.
    
    ### Northern Ireland
    
    The model applies the same predicate in Northern Ireland. Northern Ireland's twin of reg. 28 is SR 2016/226 reg. 29, with the existing-benefit list in reg. 2(1); both turn on entitlement, and the variable cites them. The reg. 5 variable cites reg. 6 of the Universal Credit Regulations (Northern Ireland) 2016 (SR 2016/216), whose reg. 6(5) matches GB reg. 5(5): "A person who is receiving universal credit, an employment and support allowance or a jobseeker's allowance is not a qualifying young person."
    
    ## Law
    
    - [SI 2014/1230 reg. 28](https://www.legislation.gov.uk/uksi/2014/1230/regulation/28): where a person who would otherwise be a qualifying young person under UC Regs reg. 5 "is entitled to an existing benefit", that person isn't a qualifying young person for the purposes of the Universal Credit Regulations, and reg. 5(5) applies as if it listed an existing benefit. [SR 2016/226 reg. 29](https://www.legislation.gov.uk/nisr/2016/226/regulation/29) makes the same provision for Northern Ireland.
    - [SI 2014/1230 reg. 2(1)](https://www.legislation.gov.uk/uksi/2014/1230/regulation/2): the Secretary of State lists income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit as existing benefits ([SR 2016/226 reg. 2(1)](https://www.legislation.gov.uk/nisr/2016/226/regulation/2) for Northern Ireland). In reg. 25(2) the Secretary of State prescribes incapacity benefit and severe disablement allowance for regs. 22 to 25 only, so reg. 28 doesn't reach them.
    - [UC Regs 2013 reg. 2](https://www.legislation.gov.uk/uksi/2013/376/regulation/2) and [reg. 5(5)](https://www.legislation.gov.uk/uksi/2013/376/regulation/5): the Secretary of State defines ESA and JSA without their income-related and income-based forms (UC Regs reg. 2), so income-related ESA and income-based JSA leave the test through reg. 28. [SR 2016/216 reg. 6](https://www.legislation.gov.uk/nisr/2016/216/regulation/6) is Northern Ireland's reg. 5.
    - [Tax Credits Act 2002 s.3(3)](https://www.legislation.gov.uk/ukpga/2002/21/section/3): the members of a couple claim a tax credit jointly, and anyone else claims as a single person.
    - Closure: the model stops paying Income Support and income-based JSA on 1 April 2026 and tax credits on 6 April 2025 (`gov.dwp.income_support.active`, `gov.dwp.JSA.income.active`, `gov.dwp.tax_credits.active`). Housing Benefit has no closure parameter. SI 2025/1148 art 3A and SR 2025/176 art 3A abolish remaining old-style income-related ESA awards from 1 July 2026 (GB) and 1 October 2026 (NI), subject to savings; the model has no closure parameter for income-related ESA yet, so the predicate reads its report in every year unless a reform neutralises `esa_income`.
    - [SSI 2021/249 reg. 6(1)](https://www.legislation.gov.uk/ssi/2021/249/regulation/6): Scotland's working-age CTR defines a young person by reference to "the definition of 'qualifying young person' in regulation 5 of the 2013 Regulations".
    
    ## Invariants
    
    - Any positive own report of an existing benefit the model pays that year and no reform neutralises, or such a couple tax credit report for a claimant or partner, makes the predicate false.
    - With every existing benefit report at zero, the predicate equals the reg. 5 test less the person's reported UC, ESA and JSA awards, and equals the reg. 5 variable; with reports present, it never exceeds that test.
    - The predicate equals the reg. 5 test less reg. 28's exclusions, exactly: no report of a closed benefit, no partner's Income Support or Housing Benefit and no dependant's tax credit excludes anyone else.
    - A reform that neutralises a benefit's variable lifts the exclusion on that benefit's reports alone: `child_tax_credit` lifts Child Tax Credit reports, `working_tax_credit` lifts Working Tax Credit reports, `tax_credits` lifts both, and every other report still excludes.
    - A claimant's or partner's reports never change a dependant's predicate.
    - A parent's Universal Credit never rises when their young person's own existing benefit report turns positive.
    - The predicate reads no family benefit. For households A to G (the issue's table, including the `income_support` reform), in the North East and in Scotland, we calculated each of 15 readers first, then `universal_credit` (210 orders). None raised an error, and Universal Credit matched a fresh calculation in every order.
    
    ## Tests
    
    - `uc_qualifying_young_person_existing_benefits.yaml` (17 cases): the issue's household, with the Income Support and income-based JSA rows in 2025 while they pay; the same reports in 2026, and a Child Tax Credit report in 2025, after the benefit closes; own tax credit reports; a parent's reports; a couple's tax credit award; a partner's own Housing Benefit, which leaves an 18-year-old claimant in the test; and a dependant's own Child Tax Credit, which does the same.
    - `test_uc_qualifying_young_person_neutralised_benefits.py` (19 tests, Python): in 2024, when the model pays all six benefits, one 17-year-old per benefit reports it, and a reform neutralises each of `income_support`, `jsa_income`, `esa_income`, `housing_benefit`, `child_tax_credit`, `working_tax_credit` and `tax_credits` in turn. Each reform lifts exactly the exclusions on its own benefit's reports, for a young person's own report and for a couple's joint tax credit award. Household G (own Housing Benefit, 2026) and household D (own Child Tax Credit, 2024) become qualifying young people under a reform that neutralises their benefit, with the parent's Universal Credit at £9,321.36 and £8,721.36.
    - `test_uc_qualifying_young_person_existing_benefits_properties.py`: Hypothesis properties for the invariants over 2024, 2025 and 2026, without a reform. The exact-equality property draws each slot from people aged 16 to 18 on a non-advanced course who report no reg. 5(5) award and at most one existing benefit, or from the general strategies; over 10,000 draws, the claimant met reg. 5 in 59% of draws, the partner or dependant in 62%, and the dependants in 65% and 69%.
    - `council_tax_reduction_young_person.yaml` Cases 11 to 13: Scotland's working-age scheme keeps a young person who reports their own Housing Benefit, Income Support, income-based JSA or income-related ESA, and drops one who reports contribution-based JSA; Case 13 asserts the £1,512.40 award. `test_council_tax_reduction_young_person_properties.py` reads the reg. 5 variable for the Scottish branch, and `test_council_tax_reduction_severe_disability_family.py` supplies the reg. 5 status as its input.
    - `qualifying_young_person_reported_own_awards.yaml`: the three income-based JSA cases run in 2025, while it pays; a member's 2026 Income Support report leaves the UC route available.
    - `test_pension_credit_young_person_income_support.py`: the reform that removes Income Support restores the UC status as well as the Pension Credit one.
    - `test_benefit_cap_specified_benefits_properties.py`: the oracle reads a young person's income-based JSA report only in a year the model pays it, as the predicate does.
    - Against #2279's head (`c5b9850a3`), 9 of the 17 cases in the new UC YAML file, 5 cases in the reported-awards file, CTR Cases 11 to 13, 18 of the 19 neutralisation tests, the Pension Credit reform test and three of the five properties fail.
    - With the predicate's neutralisation checks for income-related ESA and Housing Benefit removed, and each tax credit checked only against `tax_credits`, 9 of the 19 neutralisation tests fail: those for `esa_income`, `housing_benefit`, `child_tax_credit` and `working_tax_credit`, and households D and G.
    - Mutants that each drop one benefit's neutralisation check (`income_support`, `jsa_income`, `esa_income`, `housing_benefit`, `child_tax_credit` or `tax_credits` for Child Tax Credit, `working_tax_credit` or `tax_credits` for Working Tax Credit) each fail at least one neutralisation test; the `income_support` mutant also fails the Pension Credit reform test.
    - Mutants that drop the Income Support, income-based JSA or tax credit year gate, or that make the couple sum read a dependant's tax credit or the partner's Income Support and Housing Benefit, each fail one or two YAML cases and the exact-equality property.
    
    ## Merge notes
    
    - **#2806.** Whichever of this PR and #2806 lands second makes `is_qualifying_young_person_for_uc_education` return `is_qualifying_young_person_for_universal_credit`, so that variable reads a report only in a year the model pays that benefit and no reform neutralises it. It also gives `test_uc_receiving_education_properties.py`'s oracle the same paying-year and neutralisation filter, and sets four assertions to `false` (`uc_receiving_education.yaml` cases 31, 33 and 34, and `test_examples_reach_the_cases` for people 9 and 10). On a tree that merges both heads, `uc_receiving_education.yaml` passes 35/35 and the property file 3/3.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    

    Generated by Claude Code

  9. MaxGhenis commented on Nov 3, 2026

    @MaxGhenis
    CollaboratorAuthor

    MERGE_2806.md

    # #2804 and #2806: the change whichever lands second carries
    
    **Trees.** #2804 at branch `claude/review-2804-gate-benefits-px4evr`, head `d5d32074d569331db3cda217701cdee5ed152d10`. #2806 (`fix-2346-uc-education`) at `4d363920686117ae58ce23fa284918964ab86bbc`; its last push merged `main` into the branch, and the three files below are byte-identical to `a5d1662666253304de5d3245903f097791b91950`, so the diff applies at either head. The merge of the two heads is clean.
    
    **What changes.** Under the hub's ruling, reg. 28 reads a 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` re-applies the own-award exclusions from the raw reports in every year, so in a closed year it disagrees with `is_qualifying_young_person_for_universal_credit`. The change makes it return that variable.
    
    Files (all #2806's; #2804 touches none of them):
    - `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`; Housing Benefit and income-related ESA have no closure date) 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 paying, so the predicate excludes each.
    
    ## Runs (merged scratch tree, `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: `test_education_condition_invariants` falsified in 2026 by a claimant reporting `working_tax_credit_reported: 1000` |
    | #2804's three YAML files on the merged tree | 17/17, 23/23, 13/13 |
    
    Probe on the merged tree with the change: a single claimant aged 18 on an upper-secondary course, North East, owned outright, reporting £3,000.
    
    | 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.
    
    ## For the PR that lands second
    
    Make `is_qualifying_young_person_for_uc_education` return `is_qualifying_young_person_for_universal_credit`, give `test_uc_receiving_education_properties.py`'s oracle and property 8 the paying-year and neutralisation filter, and set the four assertions above to false, as in the diff below.
    
    ## Diff against #2806's head (`4d363920`)
    
    ```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 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)
    ```
    

    Generated by Claude Code

  10. MaxGhenis commented on Nov 3, 2026

    @MaxGhenis
    CollaboratorAuthor

    AXIOM_ADDENDUM.md

    Addendum to TheAxiomFoundation/rulespec-uk#514 from PolicyEngine/policyengine-uk issue #2804, branch `claude/review-2804-gate-benefits-px4evr`, head `d5d32074d569331db3cda217701cdee5ed152d10`.
    
    ## Companion cases for `uk/regulations/uksi/2014/1230/28.yaml`
    
    Each case gives the inputs, the year and the outcome the law gives, worked by hand. Money is the annual Universal Credit award: 12 times the monthly amounts for the year (single claimant aged 25 or over £393.45 in 2024-25, £400.14 in 2025-26 and £424.90 in 2026-27; the child element for this young person £339.00 in 2025-26 and £351.88 in 2026-27). Closure dates are the ones PolicyEngine UK holds: Income Support and income-based JSA end on 31 March 2026, tax credits on 5 April 2025. PolicyEngine UK computed the same outcomes at the head above.
    
    The law the cases rest on: SI 2014/1230 reg. 28 (a person who would be a qualifying young person under UC Regs 2013 reg. 5 and "is entitled to an existing benefit" is not one for the purposes of the Universal Credit Regulations); reg. 2(1) (existing benefits: income-based JSA, income-related ESA, Income Support, Housing Benefit, Child Tax Credit and Working Tax Credit); UC Regs 2013 reg. 5(1)(b) (a person aged 16 to 19 on a non-advanced course of more than 12 hours a week, up to the 1 September after their 19th birthday); and Tax Credits Act 2002 s.3(3) (a couple claims jointly, anyone else as a single person).
    
    1. **Entitled to Income Support while it pays (2025-26).**
       - Inputs: a parent aged 45 claims Universal Credit; a 17-year-old on a non-advanced course lives in the household and is entitled to Income Support in their own right.
       - Outcome: the 17-year-old meets reg. 5(1)(b), but reg. 28(a) applies, so they aren't a qualifying young person. No child element: 12 × £400.14 = **£4,801.68**. Without the Income Support entitlement: 12 × (£400.14 + £339.00) = **£8,869.68**.
    2. **Report of a closed benefit (2026-27, and 2025-26 for Child Tax Credit).**
       - Inputs: the same household; the 17-year-old reports Income Support received in 2026-27. Variants: income-based JSA reported in 2026-27; Child Tax Credit reported in 2025-26.
       - Outcome: nobody is entitled to a benefit after it closes, so reg. 28 doesn't apply and the 17-year-old is a qualifying young person. Income Support or income-based JSA in 2026-27: 12 × (£424.90 + £351.88) = **£9,321.36**. Child Tax Credit in 2025-26: 12 × (£400.14 + £339.00) = **£8,869.68**.
    3. **Entitled to Housing Benefit (2026-27).**
       - Inputs: the same household; the 17-year-old is entitled to Housing Benefit in their own right. Housing Benefit has no closure date.
       - Outcome: reg. 28 excludes them. No child element: 12 × £424.90 = **£5,098.80**.
    4. **Joint tax credit award (2024-25).**
       - Inputs: a couple, both aged 18 and both on non-advanced courses, hold a joint Working Tax Credit award; either partner reports it.
       - Outcome: under TCA 2002 s.3(3)(a) both members are entitled to the award, so reg. 28 excludes both from the qualifying young person test, whichever partner reports it. With no award, both are qualifying young people.
    5. **A dependant's own tax credit award (2024-25).**
       - Inputs: a claimant aged 18 on a non-advanced course lives with a dependant aged 17 on a non-advanced course, who holds their own Child Tax Credit award, claimed as a single person (s.3(3)(b)).
       - Outcome: reg. 28 excludes the dependant, not the claimant. The claimant meets reg. 5 and isn't entitled to an existing benefit, so the claimant remains a qualifying young person.
    6. **A partner's own Housing Benefit (2024-25).**
       - Inputs: a claimant aged 18 on a non-advanced course has a partner aged 30 who reports Housing Benefit.
       - Outcome: the partner fails reg. 5's age condition, so reg. 28 has no one to exclude; the claimant reports no existing benefit and no tax credit is reported, so the claimant remains a qualifying young person.
    

    Generated by Claude Code

  11. MaxGhenis commented on Nov 3, 2026

    @MaxGhenis
    CollaboratorAuthor

    Ready for hub review

    Head: d5d32074d569331db3cda217701cdee5ed152d10 on branch claude/review-2804-gate-benefits-px4evr (the harness pins pushes to this name). It starts at 4b4d30df264e3951def2e597aed0aeca15f943ae and adds three commits. I opened no PR and pushed to no other branch, so no CI has run. The hub documents are the three comments above: PR_BODY.md, MERGE_2806.md and AXIOM_ADDENDUM.md.

    Before starting. No push to this branch or #2279's in the last 6 hours, and no comment said anyone was mid-change. #2806's head moved from a5d16626 to 4d363920 at 20:15Z on 2 Nov (4.6 h before I started), but only by merging main; its three files this job touches are byte-identical at both heads. I treated #2806 as a read-only input and went ahead. main has moved to 52b30809 since the brief.

    Findings

    P2-a (item 1).

    • test_legacy_award_readers_properties.py:690-692 and test_benefit_cap_specified_benefits_properties.py:34 and :283-286 carry the review's replacement comments.
    • git grep -n -i "5(5)" -- policyengine_uk at the head. The UC reg. 5(5) hits, and what each concerns:
      • uc_unearned_income.yaml:344: contributory ESA and JSA.
      • qualifying_young_person_no_education_route.yaml:152 and qualifying_young_person_reported_own_awards.yaml:2 and :430: contributory awards, contribution-based JSA and the PC reg. 4A(5) twin.
      • council_tax_reduction_young_person.yaml:407: contribution-based JSA.
      • benefit_cap.yaml:158, benefit_cap_specified_benefits.yaml:1235, is_uc_benefit_cap_exempt_specified_benefit.py:113 and test_benefit_cap_specified_benefits_properties.py:421: the own-right flag.
      • test_benefit_cap_specified_benefits_properties.py:34 and :283: now name reg. 28 for income-related ESA and income-based JSA.
      • is_qualifying_young_person_for_pension_credit.py:37: says reg. 5(5) doesn't list income support, which is right.
      • The property file's :49, :88, :90 and :120, and both UC variables: UC, contributory ESA and contribution-based JSA only.
      • The other hits are reg. 15(5), 25(5), 27(5), 55(5) and 74(5) of other instruments.
      • None puts income-related ESA or income-based JSA under reg. 5(5).
    • test_benefit_cap_specified_benefits_properties.py with YEAR = 2026 in a scratch copy fails 1 of 46: test_an_element_a_reform_gives_is_decided_by_its_value[uc_LCWRA_element]. That failure isn't the oracle's. 2026 is a rebalancing year, and the modifier sets the LCWRA element over the test's reform. The oracle-based properties passed at 2026, because no derandomised draw gives a young person an income-based JSA report where it decides the cap. The oracle is still wrong in principle at 2026, so I gave own_benefits (:282) the paying-year filter: it reads jsa_income_reported only while gov.dwp.JSA.income.active holds. With the filter, the file passes 46/46 at YEAR = 2025 and 45/46 at 2026, with the same unrelated LCWRA failure. I kept YEAR = 2025: 2026 adds the rebalancing failure and gains nothing.

    P2-b (item 2).

    • The body's Scotland paragraph now uses the review's correction with figures I ran.
    • New Case 13 at council_tax_reduction_young_person.yaml:411: the Case 11 household with would_claim_council_tax_reduction: true, council tax £1,800, the applicant's employment_income: 5_000, and the 19-year-old reporting £3,000 of income-related ESA in 2026.
    • By hand from the scheme's parameters:
      • The applicable amount is the lone parent's 95.55 × 52 = £4,968.60.
      • Applicable income is earnings of £5,000 (no tax or NI at this level, and no working-age earnings disregard in the model) plus the family's Child Benefit of 27.05 × 52 = £1,406.60, which council_tax_reduction_applicable_income counts: £6,406.60.
      • The withdrawal is 20% (scotland.council_tax_reduction.means_test.withdrawal_rate): 0.2 × £1,438.00 = £287.60. Maximum support is 100% of £1,800, and there is no non-dependant deduction.
      • Award: £1,512.40.
    • The run gives £1,512.40, with every intermediate equal to the hand figure.
    • At c5b9850a3 the household gives £1,305.44: the flag is false, so the applicable amount is the single under-25 75.65 × 52 = £3,933.80, and £1,800 − 0.2 × (£6,406.60 − £3,933.80) = £1,305.44. Case 13 fails there and passes at 4b4d30df.

    P2-c (item 3). See MERGE_2806.md.

    • On a scratch tree merging this head with Apply the Universal Credit education condition #2806's head 4d363920, Apply the Universal Credit education condition #2806's variable returns is_qualifying_young_person_for_universal_credit, and its docstring carries the sentence the brief gives. I also dropped two clauses the change makes false, its opening "and have no entitlement…" and its last sentence ("…which leaves reg. 28 out."), and the two unused constants.
    • The oracle and property 8 read only paying_reports: benefits the model pays that year whose variables no reform neutralises. The file runs under with_main_eligibility, so the neutralisation check reads the simulation's own system.
    • The four replacements are the review's: lines 824, 879 and 908, and property line 701.
    • Runs:
      • uc_receiving_education.yaml: 3/35 fail before the change, 35/35 pass after.
      • Property file: 3/3 pass (96.6 s).
      • Without the oracle filter: 1/3 fail, test_education_condition_invariants falsified in 2026 by a claimant reporting WTC £1,000.
      • This branch's three YAML files on the merged tree: 17/17, 23/23, 13/13.
    • Four-row probe: IS 2026 and IB-JSA 2026 give true / true / false / £4,062.96; CTC 2025 gives true / true / false / £3,803.76; IS 2025 gives false / false / false / £3,803.76.
    • The body's merge note says this in three sentences.

    P3-a (item 4).

    • is_qualifying_young_person_for_universal_credit.py:47-81: paying(active, *names) is false when the benefit is closed or any of its variables is neutralised. The checks are:

      • Income Support: income_support.
      • income-based JSA: jsa_income.
      • income-related ESA: esa_income (new).
      • Housing Benefit: housing_benefit (new).
      • Child Tax Credit: child_tax_credit or tax_credits.
      • Working Tax Credit: working_tax_credit or tax_credits.
    • The year gates are unchanged. The couple sum reads tax_credits_reported, which gates each credit by its own check (:64).

    • New test_uc_qualifying_young_person_neutralised_benefits.py (19 Python tests, no YAML):

      • :87: 2024, one young person per benefit, under no reform and under each of the 7 neutralisations. The expected vector is exact, so it also checks that each reform leaves every other report's exclusion in place.
      • :99: the same for a couple of 18-year-olds whose partner reports the joint CTC or WTC award.
      • :129: child_tax_credit alone gives [True, False] (CTC lifted, WTC not), and tax_credits gives [True, True].
      • :139: household G (own HB, 2026) goes from false / £5,098.80 to true / £9,321.36.
      • :153: household D (own CTC £1,000, 2024) goes from false / £4,721.40 to true / £8,721.36 under child_tax_credit and under tax_credits.
    • Row mutants (one check dropped each; the run covers the new file and the PC test):

      Check dropped Tests that fail
      income_support [income_support], PC reform test
      jsa_income [jsa_income]
      esa_income [esa_income]
      housing_benefit [housing_benefit], household G
      CTC, both names 6 tests
      CTC, child_tax_credit only 4 tests
      CTC, tax_credits only 4 tests
      WTC, both names 5 tests
      WTC, working_tax_credit only 2 tests
      WTC, tax_credits only 3 tests

      Every mutant is killed.

    • Updated to say "only in a year the model pays that benefit, and only if no reform neutralises it":

    • AXIOM_ADDENDUM.md leaves reforms out, as the brief says.

    P3-b (item 5).

    • member() takes reg_5_5_reports (:86), and qualifying passes False (:138).

    • Two further changes, both judgment calls:

      • qualifying draws ages 16 to 18, not 16 to 19. A 19-year-old never met reg. 5 in the draws, because they set no entry or terminal-date input: 0 of 4,375.
      • qualifying reports at most one existing benefit, none one time in three (one_existing_benefit, :117). With only the first fix, M1 survived the exact property and died only on its YAML case. Six reports, each positive one time in three, left a qualifying person outside reg. 28 about 9% of the time. With at most one report, the predicate is true for 32–44% of slots, up from 11–19%.
    • Over 10,000 derandomised draws for 2025, reg. 5 is met by the claimant in 59% of draws, the partner or dependant in 62%, and the dependants in 65% and 69%. Hypothesis's one_of doesn't pick branches uniformly, so this isn't two in three. The docstring (:176-179) and the body's Tests bullet give these figures.

    • Mutants after the change, each also run against the PC and neutralisation tests:

      Mutant YAML case that fails Other failure
      M1 "A dependant's own Child Tax Credit award leaves an 18-year-old claimant a qualifying young person." exact property
      M2 "A partner's own Housing Benefit report leaves an 18-year-old claimant a qualifying young person." exact property
      M7 "A young person's own Income Support report after Income Support stops paying leaves the child element." Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's "A member's Income Support report leaves the Pension Credit and UC routes available once Income Support stops paying."; exact property
      M9 "A young person's own Child Tax Credit report after tax credits stop paying leaves the child element." exact property
      M10 "A young person's own income-based JSA report after income-based JSA stops paying leaves the child element." exact property

    P3-c (item 6). The Scotland table reads "CTR before (#2279) / now" with no dbaf71872 column. The dbaf71872 Tests sentence is gone.

    P3-d (item 7). The docstring of is_qualifying_young_person_under_universal_credit_regulation_5.py:18-19 reads "reg. 6 of the Universal Credit Regulations (Northern Ireland) 2016 (SR 2016/216)", and the reference URL stays. The body's Northern Ireland line quotes reg. 6(5).

    P3-e (item 8). Case 6 in AXIOM_ADDENDUM.md uses the review's sentence.

    P3-f (item 9). I applied each voice row:

    Merges (item 10). git merge-tree --write-tree --name-only from the head is clean against main f4a31701 (and the current main 52b30809), #2279 c5b9850a3 and #2806 4d363920. I didn't merge main.

    Tests (from uv sync --frozen --extra dev)

    Set This head Review
    uc_qualifying_young_person_existing_benefits.yaml 17/17 17/17
    qualifying_young_person_reported_own_awards.yaml 23/23 23/23
    council_tax_reduction_young_person.yaml 13/13 (adds Case 13) 12/12
    Readers' YAML 1,071 passed: 8 + 574 + 14 + 18 + 116 + 7 + 334 (Case 13 is in council_tax_reduction/) 1,070
    The review's six Python files + the new file 114 passed, 799 s 95
    code_health 3,008 passed 3,008
    New neutralisation file alone 19/19 –
    Property file after the generator change 5/5 –
    • ruff 0.17.2 format --check . (2,099 files already formatted), check ., check --select F811,F821 policyengine_uk and git diff --check 4b4d30df HEAD: clean.
    • .github/check_fix_pr.py on the final title and body, with changes from the main merge base: PASS, "Changed executable tests: 15".
    • Item 4 reverted (the predicate file from 4b4d30df): 9 of the 19 new tests fail: the [esa_income], [housing_benefit], [child_tax_credit] and [working_tax_credit] rows of :87, the [child_tax_credit] and [working_tax_credit] rows of :99, and :129, :139 and :153. All three YAML files pass.
    • My one new YAML case, Case 13, fails at c5b9850a3 (with Cases 11 and 12: 3/13) and passes at 4b4d30df.
    • At c5b9850a3 the rest is:
      • 9/17 and 5/23 YAML cases fail;
      • 18/19 neutralisation tests fail;
      • 3/5 properties fail (test_any_own…, test_zero_… and test_the_predicate_is_reg_5_less_reg_28);
      • the PC reform test fails.
    • Cycles: 210 orders (households A–G, E under the income_support reform, × North East and Scotland × 15 readers, each before universal_credit) gave 0 errors and 0 UC mismatches. The readers were the UC child/QYP flag, uc_child_index, four child-limit variables, both individual disabled-child elements, is_responsible_for_child_or_young_person_for_uc_or_housing_benefit, lha_renter_has_non_dependant, the LHA shared-rate renter, is_uc_benefit_cap_exempt, the CTR young person flag, the reg. 5 variable and council_tax_reduction.
    • Probes at the head match the review exactly:

    Skipped

    • Survey-data tests and the Enhanced FRS question: no dataset access.
    • The full YAML and Python suites: left to CI.
    • I quoted no law beyond the brief's inputs.
    • An earlier helper loop (a pgrep wait) hit its background time limit and was stopped. It only waited for a probe whose output I had already read, so nothing was lost.

    Judgment calls

    • I proceeded although Apply the Universal Credit education condition #2806's head moved 4.6 h before I started, since that push only merged main and Apply the Universal Credit education condition #2806 is read-only here.
    • I added the oracle filter in the benefit-cap file although no 2026 draw exposed the error, and kept YEAR = 2025.
    • The neutralisation tests read 2024, when the model pays all six benefits. The parametrised exact vector covers "leaves every other report's exclusion in place", and separate tests pin households G and D.
    • The generator changes beyond the brief's first fix: ages 16 to 18, and at most one existing benefit for qualifying. Without the second, the exact property stopped killing M1.
    • The changelog's first sentence now carries the paying-year and neutralisation condition, because the r2 wording ("HB … in every year") stopped being true under a reform.
    • In MERGE_2806.md, besides the brief's sentence, I dropped two clauses of Apply the Universal Credit education condition #2806's docstring and two constants that the change makes false or unused.
    • The body ends with the 🤖 line only, as the brief's PR body rules say.

    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions