Skip to content

Read only the parent's and partner's ESA in extended childcare, and test two more legacy award readers - #2261

Merged
MaxGhenis merged 7 commits into
mainfrom
legacy-award-reader-switches-1944-2000-1908-1940
Oct 13, 2026
Merged

MaxGhenis merged 7 commits into
mainfrom
legacy-award-reader-switches-1944-2000-1908-1940

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #2026 and #2027.

Summary

#2027 added claimant_or_partner_esa_income, claimant_or_partner_jsa_income and the person-level is_on_* variables, and switched the readers on main that the law ties to the claimant and partner. PRs written in parallel added more readers of the benefit-unit totals esa_income and jsa_income. Those totals also hold an award that another member of the benefit unit (neither the claimant, the partner nor a child or young person they are responsible for) claims in their own right. This PR finishes the readers whose source PRs have merged:

Reader by reader

Source PR Reader Law (legislation.gov.uk) Before Now
#2079 extended_childcare_entitlement_limited_capability_or_specified_benefit, through gov.dfe.extended_childcare_entitlement.disability_criteria (from 1 December 2022) SI 2022/1134 reg 15(4): "A person who is the partner of a parent mentioned in regulation 14(3) and who— ... (c) is entitled to a specified benefit"; reg 14(4)(b)(iii) "a person entitled to a specified benefit" for the parent; reg 11A(1)(e) "employment and support allowance under— (i) section 1 of the Welfare Reform Act 2007" the list named esa_income, so any member's own income-related ESA counted for every member the list names claimant_or_partner_esa_income. The couple's award still counts for both members of the couple, as before; reading who the award is payable to (is_on_income_related_esa) would change that convention and is not part of this switch
#1944 housing_benefit_pension_age_regulations_apply HB Regs 2006 reg 5(1)(b): "has attained the qualifying age for state pension credit if he, or if he has a partner, his partner, is a person on universal credit, on income support, on an income-based jobseeker's allowance or on an income-related employment and support allowance"; SI 2006/214 reg 5(2) reads council_tax_reduction_relevant_income_based_benefit, which #2027 scoped to claimant_or_partner_* unchanged; YAML cases and a property entry added
#1940 has_mixed_age_couple_pension_credit_saving SI 2019/37 arts 2(3) and 4; SI 2006/214 reg 5(2) already reads only the claimant's and partner's Income Support, JSA and ESA reports (add_for_members over is_claimant_or_partner, #1940's 9423120) unchanged; YAML cases and a property entry added
#2000 in_receipt_of_income_support_jsa_ib_or_esa_ir HB Regs 2006 Sch 5 para 4, Sch 6 para 5 switched in #2027 (360aef8) unchanged
#1908 housing_benefit_applicable_income_disregard, through housing_benefit_on_passporting_benefit HB Regs 2006 Sch 4 para 12 scoped through the #2000 passport in #2027 unchanged

The only formula input that changes is the extended childcare list. The other changes are tests, a docstring, comments and the changelog.

Sweep of the remaining readers on main

git grep on main (e89b8abcf) for "esa_income" / "jsa_income" in variables and for unquoted names in parameters. Every remaining reader is one #2027 left on the totals on purpose, or is owned elsewhere:

No new reader turned up.

Still waiting, not in this PR: #1966 (Scotland and Wales personal allowance), #2019 (Pension Credit earnings disregard) and #2007 (non-dependant exemptions) are open, and #2075 owns the Income Support gate.

Invariants

  1. Adding a member who is neither the claimant, the partner nor a dependant, with any Income Support, income-based JSA or income-related ESA of their own, never changes any family reader or any existing member's reader. Property-tested (test_legacy_award_readers_properties.py) on the working-age families, and on three further shapes: pension-age claimants (some in mixed-age couples), single applicants under 25 in Scotland or Wales, and parents of a child aged one to four in England of whom only one works. The further shapes now also check the member readers. As Count only the claimant's and partner's legacy awards in means tests and passports #2027 notes, this holds while the benefit cap does not bind, since the cap counts every member's award (Lift the benefit cap only for the support component and the people HB reg 75F and UC reg 83 name, and cap only the family's benefits #2076).
  2. The claimant's or partner's own award still moves each reader (YAML controls).
  3. 0 <= claimant_or_partner_esa_income <= esa_income, likewise for JSA, with equality when nobody outside the couple reports the award (Count only the claimant's and partner's legacy awards in means tests and passports #2027's existing property, unchanged).

Tests

New YAML in policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/. Expectations come from the regulations and parameter values cited in each file.

  • extended_childcare_entitlement.yaml: 4 cases. Review r7's reproduction on Count only the claimant's and partner's legacy awards in means tests and passports #2027 is the first: a working parent, a non-working partner, a three-year-old and an outside adult with £5,000 of ESA. There is also a 2023 variant and two controls in which the partner's own ESA meets the condition.
  • housing_benefit_pension_age_regulations.yaml: 4 cases (another member's ESA or JSA; the partner's ESA or JSA).
  • mixed_age_couple_pension_credit_saving.yaml: 4 cases (another member's ESA or Income Support; the partner's ESA; no award).

Each case passes with the switch, and the cases fail without it (check_switch.sh, which restores only the switched file):

Switch Pre-switch code Result without the switch
extended childcare disability_criteria.yaml at the branch point e89b8abcf 2 of 4 fail
#1944 council_tax_reduction_relevant_income_based_benefit.py before #2027 (88ecf58b8) 2 of 4 fail
#1940 main's reader with the reports summed over every member 2 of 4 fail
#2000 (main's YAML) in_receipt_of_income_support_jsa_ib_or_esa_ir.py at 88ecf58b8 3 of 5 fail
#1908 (main's YAML) the same file at 88ecf58b8 2 of 4 fail

Properties: housing_benefit_pension_age_regulations_apply and has_mixed_age_couple_pension_credit_saving join FAMILY_READERS, with the pension-age and young-single shapes. The pension-age property enters is_mixed_age_couple, so the saving is tested on the award reports alone. The extended childcare reader joins MEMBER_READERS. The working-age generator seldom draws a family whose only award is the outside member's, and in 15 examples it did not catch the old list. So the further shapes now check member readers, and a working-parents shape is added. With the old code, each new entry fails its property in the generate phase:

  • the HB regulations reader and the saving fail the pension-age property;
  • the extended childcare reader fails the working-parents and pension-age properties.

On the working-parents shape the old list also moved targeted_childcare_entitlement_eligible, which is already listed: targeted childcare excludes a family eligible for extended childcare.

Run on this branch (the YAML and source pytest runs were on f5286b054, which differs from the head only in comments):

Enhanced FRS impact

Real Microsimulation runs on a private copy of the Enhanced FRS 2024-25 (sha256 e433e532…), one per package state: base main e89b8abcf and this branch at 4b43357d6, both clean trees. Each run stores 131 benefit-unit, person and household arrays per year. They cover every household benefit, the readers above, the extended and targeted childcare entitlements and eligibility, household net income, government spending and the poverty flags. They are compared exactly (no tolerance) for each year 2025-2030.

  • Every array is identical in every year.
  • The fiscal impact is £0 in every year, and no poverty rate changes.

Why: on this dataset claimant_or_partner_esa_income equals esa_income, and claimant_or_partner_jsa_income equals jsa_income, for every benefit unit in every year. About 18,000 people (weighted) are members outside the couple and its dependants, and none of them reports one of these awards. The YAML and property tests cover the case where one does. Review r1 made its own branch run at d5ce48f39 and also found every array identical against the same base.

Re-run on 2026-10-13 after the main merge, on the same dataset copy: base main 1d3abecef against main + 4b43357d6 (tree 8af097fcc, the tree of the head 123f06332), both clean trees. All 132 arrays are identical in every year 2025-2030, so the impact is still £0. The scoped awards still equal the benefit-unit totals for every benefit unit.

Review

  • Review r1 (Subfleet, GPT-6.1 Sol) approved d5ce48f39 with one nit, a comment that said the added member's award survives tariff income on every savings draw. The reviewer re-derived every YAML expectation, reran the YAML, check_switch.sh, the vacuity checks and the three further-shape properties, and made its own Enhanced FRS run.
  • 4b43357d6 fixes the comment (no code change).
  • Review r2 (GPT-6.1 Sol) approved 4b43357d6.
  • 123f06332 merges main 1d3abecef with no conflicts or hand edits. That drops a stale Test check from an old run, which the split Test / … jobs never supersede. Review r3 (Subfleet, Opus 5.5) approved 123f06332. It checked the merge tree, checked the YAML expectations against the law and main's current code and re-swept main for new readers (none). It reran the legacy-reader YAML (84 passed), check_switch.sh (OK for all three) and the property file (15 passed), and re-checked the Enhanced FRS records.

Axiom

axiom: TheAxiomFoundation/rulespec-uk#416 queued (addendum TheAxiomFoundation/rulespec-uk#416 (comment)); rulespec-uk#389 (HB reg 5), #375 (HB Sch 4 para 12, Sch 5 para 4, Sch 6 para 5), #359 and #370 (SI 2019/37); extended childcare: rulespec-uk#416 (addendum 2, companion test 19: TheAxiomFoundation/rulespec-uk#416 (comment)) with #364 (SI 2022/1134 regs 11A, 14, 15).

🤖 Generated with Claude Code

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/extended_childcare_entitlement.yaml, policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/housing_benefit_pension_age_regulations.yaml, policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/mixed_age_couple_pension_credit_saving.yaml, policyengine_uk/tests/test_legacy_award_readers_properties.py::test_other_members_awards_never_change_the_readers_at_pension_age, policyengine_uk/tests/test_legacy_award_readers_properties.py::test_other_members_awards_never_change_the_readers_for_young_singles, policyengine_uk/tests/test_legacy_award_readers_properties.py::test_other_members_awards_never_change_the_readers_for_working_parents

MaxGhenis and others added 5 commits October 10, 2026 10:52
housing_benefit_pension_age_regulations_apply (#1944) reads
council_tax_reduction_relevant_income_based_benefit, which #2027 scoped to
claimant_or_partner_esa_income and claimant_or_partner_jsa_income, so the
reader needs no change. Reg 5(1)(b) asks whether the claimant "or if he has a
partner, his partner" is on Income Support, income-based JSA or
income-related ESA; another member of the benefit unit claims in their own
right.

Adds YAML cases where another member's own ESA or JSA leaves a pensioner
under the pension-age regulations, with partner controls, and adds the reader
to FAMILY_READERS with the pension-age and young-single family shapes. The two
excluded-adult cases fail with the CTR passport as it was before #2027
(88ecf58).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
has_mixed_age_couple_pension_credit_saving (#1940) already reads only the
claimant's and partner's Income Support, JSA and ESA reports (#1940's
9423120), so the reader needs no change. SI 2006/214 reg 5 takes a couple
out of the pension-age Housing Benefit regulations only where the claimant
"or, if he has a partner, his partner" is on one of these awards; another
member of the benefit unit claims in their own right.

Adds YAML cases where another member's own ESA or Income Support leaves the
couple's Housing Benefit saving in place, with a partner and a no-award
control, and adds the reader to FAMILY_READERS, entering is_mixed_age_couple
in the pension-age property so the reader is tested on the reports alone. The
two excluded-adult cases fail when the reports are summed over every member
of the benefit unit.

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

gov.dfe.extended_childcare_entitlement.disability_criteria (#2079) listed
esa_income from 1 December 2022, read for each person by
extended_childcare_entitlement_limited_capability_or_specified_benefit. That
total also holds an award another member of the benefit unit claims in their
own right, so another adult's own ESA let a non-working parent or partner meet
SI 2022/1134 reg 14(4) or 15(4) ("a person entitled to a specified benefit",
reg 11A(1)(e)). The list now names claimant_or_partner_esa_income, which keeps
the model's convention that the couple's award counts for both members.

Adds YAML cases, among them review r7's reproduction on #2027 (a working
parent, a non-working partner, a three-year-old and an outside adult with ESA),
which fail before the switch, and partner controls. Adds the reader to
MEMBER_READERS. The working-age property seldom draws a family whose only award
is the outside member's, so it did not catch the old list; the further family
shapes now also check member readers and gain a working-parents shape, on which
the old list fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he YAML notes

The extended childcare file attributed reg 14(4)(b)(iii)'s words to reg
15(4); reg 15(4)(c) reads "is entitled to a specified benefit" and needs the
partner of a parent who meets reg 14(3). The mixed-age couple file now quotes
reg 5(2) as written. Comments only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 (nit): the further-shapes comment said the added member's award
survives tariff income on every savings draw, but the pension-age and
working-parents shapes also draw £20,000, above the £16,000 limit for
income-related ESA and income-based JSA. Comment only.

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

Copy link
Copy Markdown
Collaborator Author

CI status at head 4b43357d6: Lint and the four smoke-import jobs pass. The Test job hit the 60-minute limit twice (#2257):

  • Run 1: all tests passed (2,899 YAML; 3,506 Python, 1 skipped, 2 xfailed), and the job was cancelled in the documentation build.
  • The re-run was cancelled at 99% of the Python tests, with no failures.

#2258 raises the limit to 120 minutes. Once it lands, this PR gets a fresh run, and it merges when every check passes. Reviews r1 and r2 (GPT-6.1 Sol) approved d5ce48f39 and 4b43357d6; the second commit changes a comment only.

@MaxGhenis MaxGhenis closed this Oct 11, 2026
@MaxGhenis MaxGhenis reopened this Oct 11, 2026
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

CI status at head 4b43357d6 (unchanged; reviews r1 and r2 approved it), 2026-10-11:

  • Closed and reopened the PR after Raise the Test job timeout to 120 minutes #2258 merged. That started a fresh pull_request run, 38148277687, on merge ref 57e1ff696 (main 6d89338ab + this head) with the 120-minute limit.
  • Lint and the four smoke-import jobs pass. Test finished in 1 h 11 min: 1 failed, 3,752 passed (Python) and 3,007 YAML passed.
  • The one failure is test_dataframe_dataset.py::test_an_exported_frame_loads_back_and_exports_itself. It is main's own failure, not this PR's: main's push run 38143054188 at 7343f948b fails the same test with the same shape mismatch ((1, 38) against (1, 18)). Stop deciding a household's UC health element rate by its position #2251 and Load a DataFrame simulation input as the dataset it describes #2270 each passed alone, and together they make a reloaded export gain the identifier columns for 2026-2029. I reproduced the shrunk case on the merge ref; the 20 extra columns are person_id, benunit_id, household_id, person_benunit_id and person_household_id for 2026-2029. The UK hub has a fix job running for main. This PR does not touch that test or the export code.
  • Every test this PR adds or relies on passed on the merge with current main. That covers all 15 test_legacy_award_readers_properties.py tests, including the working-parents shape and Narrow the legacy award readers property: an adult non-dependant ends the severe disability premium #2294's narrowed invariant. It also covers all 10 files in legacy_award_readers/, among them extended_childcare_entitlement.yaml, housing_benefit_pension_age_regulations.yaml and mixed_age_couple_pension_credit_saving.yaml. Locally on the same merge ref, the reader YAML (84 passed) and ruff format --check (clean) agree.
  • None of main's 74 commits since the branch point e89b8abcf touches esa_income, claimant_or_partner_esa_income, is_claimant_or_partner or the extended childcare variables, so the Enhanced FRS result (every array identical, £0) still describes this PR's change.

Next: once main's round-trip fix merges, close and reopen this PR again (no push, so the approved head stands), and merge with --match-head-commit 4b43357d6f5c9a71996d3acc6fc637d87d72eead when every check passes. The reader-switches job does this on its next run.

@MaxGhenis MaxGhenis closed this Oct 13, 2026
@MaxGhenis MaxGhenis reopened this Oct 13, 2026
Brings the branch to main 1d3abec. No conflicts and no hand edits: the
tree (8af097f) is the one CI run 38345117240 tested as this PR's merge
ref, and the one the 2026-10-13 Enhanced FRS run used. The new head also
drops the stale 'Test' check from run 38148277687 (main's red test of
10/11), which the split Test jobs never supersede.

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

MaxGhenis commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status at 2026-10-13 05:40Z (lane run for the reader switches):

  • Main is green again. Its push runs 38324298985 (6ea4f9ffe) and 38314764471 (486ca6f38) passed. I closed and reopened this PR without a push. Run 38345117240, on merge ref 08adc2a83 (main 1d3abecef + 4b43357d6), passed every job: Lint, the YAML shard, Python shards 1–4 of 4, Test / Result, the docs build and the four smoke imports.
  • Why there is a new commit. gh pr checks still exited 1, because of the old single Test check from run 38148277687 (main's own red test of 10/11). The split Test / … jobs never supersede that check, and a re-run would reuse its old ref. So I pushed 123f06332, which only merges main 1d3abecef into the branch. It has no conflicts or hand edits, and its tree (8af097fcc) is byte-identical to the merge ref that run 38345117240 tested.
  • Re-checked on that tree:
  • Enhanced FRS, re-run today on the same dataset copy (sha256 e433e532…): base main 1d3abecef against main + 4b43357d6 (tree 8af097fcc), both clean trees. All 132 arrays are identical in every year 2025–2030, so the impact is still £0. claimant_or_partner_esa_income and claimant_or_partner_jsa_income still equal the benefit-unit totals for every benefit unit.
  • Next.
    1. Delta review r3 (Subfleet) of 123f06332 is running.
    2. When it approves and CI passes on 123f06332, this PR goes into the merge queue, pinned to that head.

@MaxGhenis
MaxGhenis added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 7672e2f Oct 13, 2026
15 checks passed
@MaxGhenis
MaxGhenis deleted the legacy-award-reader-switches-1944-2000-1908-1940 branch October 13, 2026 10:38
MaxGhenis added a commit that referenced this pull request Oct 13, 2026
#2261 said the benefit cap still counts every member's award, so a binding
cap could carry another member's award to a reader through Universal Credit.
#2076 merged right after it, in the same merge-queue group:
benefit_cap_reduction now counts only the couple's own awards and the
family's. The note now says so, and points to JSA_COUPLE_WITH_LODGER, which
the first property checks: it is £1,952.48 below the cap in 2025, so an added
member's £3,000 of JSA would take it over if the cap counted it. Comment only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant