Skip to content

Add the universal credit limb of the Housing Benefit passport - #2133

Merged
MaxGhenis merged 6 commits into
mainfrom
hb-universal-credit-passport
Oct 4, 2026
Merged

MaxGhenis merged 6 commits into
mainfrom
hb-universal-credit-passport

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2132

axiom: TheAxiomFoundation/rulespec-uk#375 queued

Summary

The working-age Housing Benefit passport now covers a claimant on Universal Credit, as the law requires. #1908 and #2000 implemented it for Income Support, income-based JSA and income-related ESA only.

  • Earnings disregard (housing_benefit_applicable_income_disregard, Sch 4 para 12): all earnings.
  • Applicable income (housing_benefit_applicable_income, Sch 5 para 4): nil.
  • Assessable capital (housing_benefit_assessable_capital, Sch 6 para 5): nil. Tariff income is then nil and the capital limit is met.

All three read a new variable, housing_benefit_on_passporting_benefit. With nil income Housing Benefit is the maximum: the eligible rent less non-dependant deductions.

No Enhanced FRS output changes, because the model does not yet pay Housing Benefit to a family on Universal Credit. The change takes effect when #1910 (or any other HB-alongside-UC route) lands; #1910 is open and on hold (d837).

Law

Read verbatim from legislation.gov.uk /data.xml on 2026-10-04.

  • SI 2006/213 Sch 4 para 12: "Where a claimant is on universal credit, income support, an income-based jobseeker's allowance or an income-related employment and support allowance, his earnings."
  • Sch 5 para 4: the same words, ending "the whole of his income".
  • Sch 6 para 5: the same words, ending ", the whole of his capital".
  • Reg 2(3B): "a person ("P") is on universal credit on any day in respect of which P is entitled to universal credit (whether it is in payment or not)."
  • In force: SI 2013/2070 reg 7(2), (7), (8) and (9) inserted the universal credit words, from 28 October 2013.
  • Northern Ireland: SR 2006/405 Sch 5 para 12, Sch 6 para 4, Sch 7 para 5 and reg 2(3B) have the same words, inserted by SR 2018/92 reg 4 from 8 May 2018. NI had no HB universal credit passport between the start of NI Universal Credit (27 September 2017) and 7 May 2018.
  • Entitlement: UC entitlement needs an award of at least one penny (WRA 2012 s.5(1)(b); UC Regs 2013 reg 17) and a claim (SSAA 1992 s.1(1) and s.1(4)(za)). Joint claimants are jointly entitled (WRA 2012 s.3(2)).
  • HB alongside UC: in law this is paid for specified or temporary accommodation (UC (Transitional Provisions) Regs 2014 reg 5(2)(a); NI SR 2016/226 reg 3(2)(a)). That rent is excluded from the UC housing costs element (UC Regs 2013 Sch 1 para 3(h) and (i)).

Change

  • New parameter gov.dwp.housing_benefit.means_test.universal_credit_passport, with boolean children great_britain (true from 2013-10-28) and northern_ireland (true from 2018-05-08). Model years are read on 30 April, so the limb applies from 2014-15 in GB and 2019-20 in NI.
  • New variable housing_benefit_on_passporting_benefit (BenUnit, YEAR, bool). It is true when in_receipt_of_income_support_jsa_ib_or_esa_ir holds, or when is_uc_entitled holds and the limb is in force for the household's country; Great Britain's date applies everywhere except Northern Ireland. It is the counterpart of rulespec-uk#375's planned hb_on_passporting_benefit.
  • The three passports read it. The earnings disregard previously summed IS, JSA(IB) and ESA(IR) itself; reading the shared variable also completes the follow-up Disregard the whole income and capital of Housing Benefit claimants on IS, income-based JSA or income-related ESA #2000 listed.
  • Docs: the passport paragraph in docs/book/programs/gov/dwp/housing-benefit.ipynb and the three variables' docstrings.

Why is_uc_entitled and not universal_credit

universal_credit is paid after the benefit cap, and benefit_cap_reduction depends on Housing Benefit. Reading it here creates a cycle; a negative control raised CycleError on housing_benefit_pre_benefit_cap.

is_uc_entitled is universal_credit_pre_benefit_cap > 0. That is UC before the cap and deductions, defined for a family that claims (would_claim_uc). It depends on no Housing Benefit variable:

  • a runtime tracer showed no HB variable in its tree;
  • a static graph cut showed no path to any HB variable;
  • test_calculation_order_does_not_change_results gets the same results from five calculation orders.

housing_benefit_pension_age_regulations_apply already reads it for reg 5. It also matches "whether it is in payment or not": an award that the cap or deductions reduce to nil still counts. The law has no cycle at all: Housing Benefit counts as nil in the UC cap (UC Regs reg 80(2A)), and the HB cap does not apply to a claimant receiving UC (HB Regs reg 75F(1)(g)).

Modelling choices and limitations

Tests

  • New YAML housing_benefit_universal_credit_passport.yaml, 9 cases hand-computed from the regulations (2025-26 amounts):

    These cases supply housing_benefit_eligible (and a nil UC housing element), standing in for a specified or temporary accommodation claim, which main cannot reach.

  • New YAML housing_benefit_on_passporting_benefit.yaml, 12 cases on the variable: each limb, the pre-cap reading, and the in-force dates (GB 2013 and 2014, Wales 2018, NI 2018 and 2019, and later).

  • Mutation check: with the limb switched off in the parameter, 13 of the 21 new YAML cases fail and the 8 controls pass.

  • Updated fixtures: existing fixtures that leave would_claim_uc at its default True describe low-income working-age families that are UC-entitled, so they are now passported.

    • 28 YAML cases in 7 files failed for that reason.
    • 18 more kept passing only because the passport gave the same value as the rule under test. One example is a £3-a-week earner, whose whole £3 is disregarded either way. I found them by rebuilding every YAML case that asserts a Housing Benefit means-test variable and computing it with the limb on and off.

    Each of these 46 cases tests a family not on UC, so each now sets would_claim_uc: false; expected values are unchanged. A mutation that removes the cap on the earnings disregard fails the pinned cases again. The detector flagged 25 more cases and I left them alone. Some supply the passported inputs directly (applicable income, pre-cap HB or entitlement). Some test an eligibility route that pays no HB. Others are deliberate UC cases. Scripts and outputs are in ~/reviews/uk-hb-uc-passport-2026-10-04/out/vacuity/.

  • Add the Housing Benefit earnings disregard for specified or temporary accommodation (Sch 4 para 18, from 5 October 2026) #2123's fixtures, after merging it (para 18 disregard): 19 more cases in its YAML file were pinned the same way; 13 failed and 6 passed only by coincidence. Its property fixture is pinned too, as the coordination comment on Add the Housing Benefit earnings disregard for specified or temporary accommodation (Sch 4 para 18, from 5 October 2026) #2123 proposed. In the conflicting return, Add the Housing Benefit earnings disregard for specified or temporary accommodation (Sch 4 para 18, from 5 October 2026) #2123's para 18 amount is kept, and its sentence saying the UC limb is not modelled is dropped.

  • Pre-existing defect in housing_benefit_eligible.yaml: absolute_error_margin: 5 voided its boolean checks. Two cases expecting True actually computed False, because the default would_claim_uc: True blocks the continuing-award route. The cases now pin that route and the margin is gone, so all four hold exactly.

  • Python tests: five files now pin or vary the claim state.

    • test_lifetime_isa_capital.py, test_carer_support_payment.py and test_housing_benefit_earnings_disregard_properties.py failed.
    • In test_council_tax_reduction_pensioner_earnings_disregard_properties.py, the HB statutory-sum invariant relied on £20,000–£40,000 earnings to keep families off UC. It now pins them off.
    • test_means_test_income_properties.py checks its invariant in both claim states, so the HB means test is still exercised.

    An instrumented run logged every Python test in which the UC limb fires. No other test has assertions the limb touches.

Invariants (property-tested in test_housing_benefit_universal_credit_passport_properties.py)

For generated populations of renting families, with optional non-dependants, across GB and NI. Each family is not on UC, on a supplied UC amount (nil, one penny or more), or on UC the model computes, with or without a legacy benefit. Every population also includes one family passported by UC alone.

  1. Definition: the passport holds exactly when IS, JSA(IB) or ESA(IR) is positive, or the family claims UC and UC before the cap is positive. is_uc_entitled equals the latter.
  2. Passport: a passported family has nil applicable income, capital and tariff income, and an earnings disregard equal to its net earnings. Its entitlement equals maximum Housing Benefit; every family's entitlement is at most that maximum.
  3. Metamorphic: raising a UC-passported family's earnings, pensions and savings changes neither the passport nor its entitlement or eligibility.
  4. Differential: the UC limb and the Income Support limb give the same disregard, applicable income, capital, tariff income and entitlement.
  5. Monotonic: going onto UC never lowers HB before the cap, the entitlement, or eligibility. After the cap it can, because UC counts towards the cap; that is intended.
  6. Dates: the limb holds in every year from 2014 in GB and 2019 in NI, and never before, whether read from the parameter or from the variable.
  7. Order independence: with both benefits paid, the calculation order makes no difference. Calculating Housing Benefit, Universal Credit, the benefit cap, the passport or household net income first gives the same means test, the same HB, UC and benefit cap reduction for every benefit unit, and the same household net income. Each population includes a family on both benefits whose cap binds; a lone parent with four children paying £18,000 private rent is the sentinel.

Mutation check: with the limb off, all six property tests fail.

Commands run

All at head 0d5e146 (main b1dd418, with #2123, merged in).

  • uv run --frozen ruff format --check . and uv run --frozen ruff check .: clean.
  • uv run --frozen policyengine-core test policyengine_uk/tests/policy -c policyengine_uk: 2,323 passed. Before the fixture pins, the passport alone failed 28 cases, plus 13 in Add the Housing Benefit earnings disregard for specified or temporary accommodation (Sch 4 para 18, from 5 October 2026) #2123's file, all from would_claim_uc left at its default True.
  • uv run --frozen pytest policyengine_uk/tests/ -n 6 --dist worksteal: 2,948 passed, 34 skipped, 7 xfailed, 0 failed.
  • Enhanced FRS runs: main b1dd418 against 0d5e146, and earlier main 2105816 against 407acbd. Both show exactly £0 (below).
  • Mutation checks, run with the universal credit limb switched off in the parameter:
    • all 6 property tests fail;
    • across the full YAML suite (2,323 cases at 0d5e146), exactly 13 cases fail, all of them new UC cases in the two new files. So no existing fixture depends on the limb.
  • Not run locally: make documentation. CI's documentation build passes on this branch. The notebook edit is a single markdown line.

Enhanced FRS impact

Real runs on a copy of the Enhanced FRS 2024-25 (sha256 e433e532…), 2024-25 to 2030-31. Scripts, logs and outputs are in ~/reviews/uk-hb-uc-passport-2026-10-04/impact/.

Comparison Change in every year
main b1dd418 vs this branch (0d5e146); also main 2105816 vs 407acbd and main 6ae9c5e vs e38e0cd £0.0m in Housing Benefit (before and after the cap), UC, the benefit cap reduction, council tax reduction, household and HBAI net income, and gov balance. No benefit unit changes HB eligibility or UC entitlement.
#1910 (06ef074) + main 6ae9c5e vs the same + e38e0cd (local merges) £0.0m, as above
The same, with #1910's in_specified_or_temporary_accommodation set for working-age benefit units reporting both HB and UC (a sensitivity, not data) £0.0m. Once the input zeroes their UC housing costs, none of the 38–39k weighted benefit units is UC-entitled, so the UC limb has no one to apply to.

Only HB intermediates move, for 3.6k–3.8k records each year. Every one of those benefit units is UC-entitled, not on a legacy benefit, and not HB-eligible in either run. An independent check recomputed the stacked aggregates from the saved arrays and confirmed the merge resolution of #1910 with main.

Coordination

Axiom

TheAxiomFoundation/rulespec-uk#375 already plans hb_on_passporting_benefit, with "claimant is on universal credit" as a supplied Judgment for Sch 4 para 12 and Sch 5 para 4 / Sch 6 para 5. An extension comment does the rest:

  • retires its labelled PolicyEngine difference ("the universal credit limb is left out") and corrects that difference's premise;
  • names this PR's variable as the Step 2 counterpart;
  • states the new labelled differences (yearly test, cap-to-nil reading, 30 April dates);
  • adds companion cases U1–U9, with the same hand-computed numbers as this PR's YAML;
  • adds the impact table.

Encoding is blocked for two reasons:

  • axiom-corpus has no rows yet for SI 2006/213 Sch 4–6 or SR 2006/405, and the dated reg 2 is only in open axiom-corpus#765;
  • signed encoder runs are OpenAI-API-billed and need approval.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 4, 2026 08:09
SI 2006/213 Sch 4 para 12, Sch 5 para 4 and Sch 6 para 5 (NI: SR 2006/405
Sch 5 para 12, Sch 6 para 4 and Sch 7 para 5) disregard the earnings, whole
income and whole capital of a claimant on universal credit, as for income
support, income-based JSA and income-related ESA. 'On universal credit'
means entitled, whether in payment or not (reg 2(3B)), read as Universal
Credit before the benefit cap (is_uc_entitled).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hand-computed YAML cases for the passport and its in-force dates, a
Hypothesis property file (definition, passport to maximum, metamorphic,
differential against the Income Support limb, monotonic, dates, order
independence), and would_claim_uc: false on existing fixtures that test a
family not on Universal Credit. Docs and changelog.

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

# Conflicts:
#	policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_applicable_income.py
#	policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_assessable_capital.py
MaxGhenis and others added 3 commits October 4, 2026 09:05
- Pin would_claim_uc: false on 18 more YAML cases whose families were
  UC-entitled, so the passport, not the rule under test, produced the
  expected value (found by rebuilding every case with the limb on and off).
  A mutation removing the earnings-disregard cap now fails them again.
- housing_benefit_eligible.yaml: its absolute_error_margin of 5 voided the
  boolean checks, and two cases expecting True computed False; pin the
  continuing-award route and drop the margin.
- test_means_test_income_properties checks both claim states, and the CTR
  pensioner property's invariant 6 pins families off Universal Credit.
- The order-independence property compares Housing Benefit, Universal
  Credit, the benefit cap reduction and household net income, with a
  capped sentinel family on both benefits.
- Explicit weekly hours in the hand-computed cases; docstring on the age
  condition; changelog wording.

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

Resolve the Sch 4 para 12 return in the earnings disregard: keep #2123's
para 18 amount and read housing_benefit_on_passporting_benefit; drop its
sentence that the universal credit limb is not modelled. Pin
would_claim_uc: false on the 19 para 18 YAML cases whose families were
UC-entitled (13 failed, 6 passed only because the passport gave the
expected value) and in the para 18 property fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
is_uc_eligible tests has_attained_state_pension_credit_qualifying_age since
#1907, not State Pension age.

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

Copy link
Copy Markdown
Collaborator Author

Hand-off to the UK hub. Head 1d478e2760bb is reviewed; only CI remains.

Everything verified at this head:

Pending: CI Test at 1d478e2. Land on green with a merge commit and --match-head-commit 1d478e2760bb.
Depends on: nothing. #2123 has merged and its conflict is resolved here.
Holds or decisions: none. The aggregates do not move, and this is a correction to the law (PR body).
Follow-ups owed:

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis merged commit 60d37e8 into main Oct 4, 2026
7 checks passed
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Merged by the UK hub, which took this PR on handoff from local_f51300bf, under the PE-UK merge-on-gates rule.

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.

Add the universal credit limb of the Housing Benefit passport (HB Regs Sch 4 para 12, Sch 5 para 4, Sch 6 para 5)

1 participant