Repository navigation
Calibrate Housing Benefit to DWP's GB figures by age group - #490
Merged
Merged
Conversation
Replace the OBR table 4.9 Housing Benefit target, which is GB DWP-funded spending but was compared with UK-wide modelled Housing Benefit, with DWP Spring 2026 targets for Housing Benefit spending and claims over Pension Credit qualifying age in Great Britain. Targets can now name the countries they cover. The figures under Pension Credit qualifying age are kept but not calibrated: the model pays working-age Housing Benefit to too few records for a target to be met without concentrating it on a handful of them. Stop drawing would_claim_uc for benefit units whose adults have all reached State Pension age, keeping every other unit's draw unchanged. Require policyengine-uk 2.102.5, which lets pension-age families make new Housing Benefit claims (PolicyEngine/policyengine-uk#1901). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
State in the release note that modelled GB Housing Benefit falls to about £8.0bn. Mark the mixed-age classification as an assumption, date the benefit-group equality from 2024-25, and quote the calibrated build's working-age claims. Add a test that the calibrated columns build on the dataset, since the loss matrix skips a target whose column raises, and note that would_claim_uc uses survey-year State Pension age. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flag The create_frs smoke test's fake simulation did not answer the variables the would_claim_uc rule now reads (person_benunit_id, is_adult, is_SP_age), which failed CI with KeyError: 'person_benunit_id'. Pick the claimant-and-partner flag the way the locked policyengine-uk does for Housing Benefit's pension-age route: is_claimant_or_partner where the release defines it, otherwise is_adult as in 2.102.5. Both the would_claim_uc rule and the age-group targets use it. Under 2.102.5 the base FRS is identical to the previous commit's in every column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 1, 2026
Contributor
Author
|
Interaction with #514. That PR rebuilds |
21 of 52 tasks
…accommodation Max ruled on d703 that working-age Housing Benefit should be calibrated too. policyengine-uk pays no Housing Benefit for specified (supported) or temporary accommodation, so the working-age awards it can pay are general-needs ones. DWP's working-age line includes both kinds, and DWP splits accommodation type only across all ages. The calibrated working-age target is therefore DWP's working-age line less all of its supported and temporary accommodation: £584.8m and 107k claims in 2025-26, a lower bound on working-age general-needs Housing Benefit. It exists for 2024-25 and 2025-26 only; from 2026-27 the all-age accommodation lines exceed the whole working-age line. Seeded calibrations of one saved 2025 input compared the options. Against the full working-age line, uncapped calibration loaded it onto about three effective records. Capping household weights at 20-40 times their prior reached 54-60% of DWP's spending and raised income-related ESA claimants to 1.6-2.0 times DWP's count. Against the net figure, the calibration meets it (1.04x) and no other national target crosses the 10% line compared with calibrating pension-age figures only. Tests: DWP's accommodation lines reconcile with its age split in every year; the net figure equals all-age general needs less pension-age Housing Benefit, the same quantity computed the other way; a property test of the netting; the calibrated working-age column equals the full line's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loss matrix fills a year a target doesn't list with the latest earlier value within three years (_resolve_value). That carried the 2025-26 net working-age Housing Benefit targets (£584.8m, 107k claims) into 2026-27 to 2028-29, where DWP's lines give a negative net figure. Targets gain a carry_forward flag (default True, so nothing else changes), and the two net targets set it to False. The build calibrates 2025 only, so the dataset is unchanged; any loss matrix built for 2026-28 now leaves the net targets out. Also reword the docstring: policyengine-uk has no supported or temporary accommodation rules, but the FRS can't identify those residents, so a reported award there is modelled under the general-needs rules and treating the model's working-age HB as general needs is an approximation. The unmodelled supported and temporary accommodation HB (£5.2bn, all ages) is recorded as a limitation (d946). "Within 12%" becomes "about 12% below". Tests: the net targets resolve in 2024-25 and 2025-26 only while the other HB targets still carry forward; a property test of _resolve_value for any values, year and flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…escribe the year resolver exactly The changelog and a test comment still said the model pays no supported or temporary accommodation Housing Benefit. It has no rules for it, and the FRS can't identify those residents, so treating its working-age HB as general needs is an approximation. The resolver docstring and the carry_forward comment now describe _resolve_value as it works: the nearest listed year, the earlier on a tie, used only if it is earlier and at most three years away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Hand-off to the UK hub (owner session e913bdc1, 2026-10-05).
|
MaxGhenis
added a commit
that referenced
this pull request
Oct 6, 2026
MaxGhenis
added a commit
that referenced
this pull request
Oct 6, 2026
Conflicts resolved per runbook A4 step 10: datasets/frs.py keeps #514's assign_reported_takeup(...) calls for would_claim_pc and would_claim_uc, keeps #490/#510's comments, and ANDs #490's ~derive_all_claimants_over_state_pension_age(...) term onto the would_claim_uc draw (draw count unchanged); .gitignore takes the union of both sides; uv.lock reset to main's (b45c373), relocked in R. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 6, 2026
Conflicts resolved per runbook A4 step 12: datasets/frs.py keeps both new functions, #490's derive_all_claimants_over_state_pension_age and #524's derive_is_claimant_or_partner_from_frs_microdata; uv.lock reset to main's (b45c373), relocked in R. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 6, 2026
Conflict resolved per runbook A4 step 13: datasets/frs.py keeps all three new functions, #490's derive_all_claimants_over_state_pension_age, #524's derive_is_claimant_or_partner_from_frs_microdata and #525's derive_uc_is_in_gainful_self_employment. #525's uv.lock edits auto-merged and are reset to main's in R. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 6, 2026
Conflicts resolved per runbook A4 step 14, both in tests/test_legacy_benefit_proxies.py: (a) the FakeMicrosimulation double keeps #490's person_benunit_id, is_adult and is_SP_age branches and #526's [66] * len(...) state_pension_age; (b) #526's create_single_adult_frs helper drops the lha_list_of_rents.csv.gz read_csv fake, because #516 deleted that file and its fake. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 7, 2026
The SPI-donor rules redraw would_claim_uc on SPI-synthetic rows, so a unit whose claimant and any partner have all reached State Pension age could get would_claim_uc there even though create_frs (#490) never gives it one. apply_spi_donor_benefit_rules now takes an optional per-benefit-unit mask, uc_pension_age_excluded, and clears would_claim_uc where it is set. Its one pipeline caller always computes the mask on the real target dataset with the same derivation as create_frs and passes it; a test checks that wiring. Integration commit for the 10/8 uk-data release (d833). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Queued in release PR #544 for the 10/8 uk-data batch. It lands only on Max's go (d833). |
MaxGhenis
added a commit
that referenced
this pull request
Oct 7, 2026
…ount (F1) test_full_target_set_available_offline (from #536) asserted at least 30 OBR targets offline. The release combines four member PRs that each replace OBR targets with better sources, which no single PR's CI saw together: #490 swaps obr/housing_benefit for DWP Housing Benefit targets, #510 swaps obr/pension_credit for DWP Pension Credit spend and caseload, #533 swaps the two OBR salary-sacrifice NI relief targets for HMRC's, and #530 merges the two OBR UC targets into obr/universal_credit. That leaves 29 OBR targets (34 on main), so release CI failed with 29 >= 30. The test now checks what it was for: offline, the committed workbooks give exactly the OBR targets that the same workbooks give when served, plus the receipts and NICs names. A broken fallback still fails it. Release-only fix for the 10/8 uk-data release (d833). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows up PolicyEngine/policyengine-uk#1901 (released in policyengine-uk 2.102.5), which lets families whose adults are all over State Pension age make new Housing Benefit claims.
What changes
uv.lockpinned 2.93.0, so a data build today would not include #1901.Target.countrieszeroes the loss-matrix column outside those countries, viarestrict_to_countries.would_claim_ucis False for benefit units whose adults have all reached State Pension age. They cannot claim Universal Credit. The draw still covers every unit, so the random stream and every other unit's value are unchanged.The full under-Pension-Credit-age line (£5,778.1m, 460k) is transcribed and tested but not calibrated. The working-age section below gives the reasons.
This PR changes no calibration settings. Household weight bounds and a prior-drift penalty were built and tested for d703 and then left out. They are on branch
calibration-weight-bounds, off by default.Age groups
DWP splits by benefit rules, not age alone (tables, Notes, note 5). Its over and under lines equal its Pension Credit + State Pension and ESA + other working-age benefit groups exactly from 2024-25.
Regulation 5 of both Housing Benefit Regulations 2006 (SI 2006/213 and 2006/214) applies the pension-age rules when the claimant or partner has reached Pension Credit qualifying age, unless either is on UC, Income Support, income-based JSA or income-related ESA. The model column therefore counts a benefit unit as over when an adult has reached State Pension age and the unit gets none of those four benefits. That puts mixed-age couples who kept pension-age Housing Benefit after May 2019 in the older group. DWP does not document its rule for them, so this is an assumption: they are taken to sit in its Pension Credit and State Pension groups. Stat-Xplore's Housing Benefit "Age of claimant" also uses "the eldest age within that couple".
Working-age Housing Benefit (d703, then d946)
Max ruled on d703 that working-age Housing Benefit should be calibrated as well. This PR calibrates it to DWP's working-age line less all of DWP's supported and temporary accommodation Housing Benefit. Max approved that figure as built on d946 (10/5), and asked that the unmodelled supported and temporary accommodation spending be recorded as a limitation (see "Limitations" below).
What the model can pay. policyengine-uk has no rules or input for specified (supported) or temporary accommodation. Its documentation says "Claims for specified or temporary accommodation are not modelled" (policyengine-uk#1911).
housing_benefit_eligiblehas two routes: wholly pension-age families, and continuing reported awards that don't claim UC. Every award is computed under the general-needs rules. The FRS doesn't identify supported or temporary accommodation, so a respondent there who reports Housing Benefit is modelled the same way. Treating the model's working-age HB as general needs is therefore an approximation.What DWP publishes. DWP's Spring 2026 forecast tables split accommodation type across all ages only. In 2025-26:
The working-age line less supported and temporary accommodation is £584.8m and 107k claims. Equivalently, it is general needs less all pension-age HB.
This is a lower bound. Part of supported and temporary accommodation is pension-age, so the figure understates working-age general-needs HB. It exists for 2024-25 and 2025-26 only; from 2026-27 the all-age accommodation lines exceed the whole working-age line.
Award size fits. DWP's working-age award averages £12.6k a claim, against £6.3k for general needs. The model's working-age awards average £5.3k–9.8k in every configuration tried.
Seeded calibrations of one saved 2025 input compared the options (report:
~/reviews/uk-hb-dwp-targets-2026-09-30/tuning/D703_FOLLOWUP.md; table:FRONTIER.md):Against the net figure, no other national target crosses the 10% line compared with calibrating pension-age HB only.
Results
Five real builds, each with production settings (
PE_UK_DATA_OA_CLONES=1, 512 epochs) and the same trained imputation models. Each dataset was then simulated with policyengine-uk 2.102.5. Dropout is unseeded (uk-data#507), so the differences between builds include run-to-run noise.GB Housing Benefit, FY 2025-26 (ratio to DWP in brackets):
Weights and side effects, FY 2025-26:
would_claim_ucC calibrates the full working-age line. It loads that line onto about three effective records (effective sample size 3). F calibrates the net figure: working-age HB rests on 8 effective records, but it is £0.65bn and nothing else moves.
policyengine-uk pays working-age Housing Benefit only as a continuing award to a family that reports it and doesn't claim UC. In 2025-26 that is 267 of the 770 working-age records that report it.
Other measurements:
Total GB Housing Benefit falls. In 2025-26 it goes from £11.67bn (1,459k claims) in B to £8.49bn (1,257k) in F; DWP's total is £12.89bn. The old total looked close only because pension-age Housing Benefit was 1.6x DWP's. About £5.2bn of supported and temporary accommodation Housing Benefit remains unmodelled as such. The changelog says this.
National fit. Constituency log, final epoch:
From C2 to F, two targets crossed out and four crossed in, all within 2pp of the line.
Calibration year vs saved file. At calibration, F puts pension-age Housing Benefit at £7.38bn and 1,154k claims (both 1.04x), and working-age at £603m and 110k (both 1.03x the net figure). Simulating 2025 from the saved 2024 base-year file gives 1.10x for pension-age spending, the round-trip effect described in Rail, bus and fuel calibrations run after base-year materialisation, so the shipped file is not uprate-invertible #479.
FY 2026-27. F has pension-age Housing Benefit at £8.11bn against DWP's £7.27bn (1.12x); B has £11.81bn (1.62x). F still pays £0.68bn of working-age Housing Benefit in 2026-27. DWP's working-age line that year (£5.21bn) is below its supported and temporary accommodation (£5.58bn), so these all-age forecast lines give no positive lower bound to compare.
Northern Ireland. Housing Benefit is no longer calibrated there. It falls from £1.44bn in B to £0.45bn in F, closer to the £0.31bn of rent Housing Benefit in DfC's 2025-26 accounts.
Target set. F's log has all 640 national targets, including OBR receipts. obr.uk served the workbooks for this build, and the run used the same committed-workbook fallback as Fall back to the committed OBR workbooks when obr.uk answers 200 with a non-workbook page #536 in case it didn't. The HMRC salary-sacrifice CSV returns 410 in every build here (Restore HMRC salary sacrifice relief calibration targets #533).
Limitations
Invariants and tests
carry_forward=False(a newTargetfield, default True). They resolve in 2024-25 and 2025-26 only, while the other HB targets still carry forward. A property test covers_resolve_valuefor any values, year and flag. The build calibrates 2025 only, so this leaves build F's dataset unchanged; it matters to any loss matrix built for 2026-28.would_claim_uc.would_claim_uc. That changed for 2,848 of 5,714 wholly pension-age units, all True to False; no other unit changed.Follow-ups (not in this PR)
calibration-weight-bounds). A 20x cap raised the effective sample size of all households from about 1.2k to 3.2k, at a cost to ONS savings interest and some marginal targets. It is a calibration-wide choice for its own PR.Reviews:
carry_forwardcomment's description of the resolver, is fixed in f4d8f95 (comment only).axiom: n/a: data/calibration-only change
Merging releases data (the push workflow builds and uploads the dataset), so the merge is queued as a decision.
🤖 Generated with Claude Code