Repository navigation
Calibrate GB universal credit to one OBR total; reach the UC payment top band - #530
Merged
Merged
Conversation
… band OBR EFO table 4.9 splits UC between spending inside the welfare cap and outside it. The outside row is DWP's UC equivalent of JSA (the Intensive Work Search group), not UC for households the benefit cap leaves alone, so computing it as UC where benefit_cap_reduction is zero asked for £12.9bn of nearly all UC. policyengine-uk cannot identify the Intensive Work Search group, so the two rows become one target, obr/universal_credit, restricted to Great Britain (the rows sit under DWP social security; Northern Ireland has its own). The GB restriction uses #490's countries field, copied verbatim. The DWP UC payment distribution's open top band ("£2500.01 or over") parsed to NaN bounds, so its four targets had a zero column. Parse it as (30,000, inf), make bands (lower, upper] so they meet without gaps, and reject overlapping summary bands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Making the bands meet without gaps ((lower, upper]) moved penny-a-month awards into the bottom band: policyengine-uk leaves 1p a month payable after deductions (SI 2013/380 Sch 6), and 44 records (136k benefit units in a seeded main build) sit there. The old lower bound of £0.12 a year leaves them out, and the seeded build of the gapless version then reweighted against them. Whether they belong in Stat-Xplore's lowest award band turns on whether its award amount is before or after deductions, which is outside this change. The bounded bands now parse and compare exactly as before; only "£2500.01 or over" changes, to [30,000.12, inf). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stat-Xplore's monthly award is the UC due after deductions ("Monthly Award
Amount (payment bands)" metadata), as universal_credit is, so a penny a
month left after deductions belongs in "£0.01 to £100.00". The previous
commit's reason for keeping the old [min x 12, max x 12) bounds was wrong:
their £0.12 lower bound left those awards out only through float32
rounding. Bands are (lower, upper] again, as in the first commit.
From the independent review of the first commit:
- reject NaN, infinite or negative lower bounds and inverted bands;
- scan the whole of table 4.9 for the UC rows, not the first 55 rows;
- test GB scoping through create_target_matrix itself, a year missing
from one UC row, and each payment band's membership against an
interval-search oracle (float64 and float32 awards, edges and their
neighbours); the old sum-over-bands property let an award move to the
neighbouring band unnoticed;
- parse the committed workbook once in the property test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
October 4, 2026 18:54
Contributor
Author
|
Hand-off to the UK hub (session "OBR UC welfare cap targets")
|
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>
21 of 52 tasks
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.
Summary
Two calibration-target fixes, both data-release changes for the d833 batch. Draft; do not merge outside the batch.
obr/universal_credit_outside_capasked for £12.9bn of nearly all UC. Its estimate was +460% in the seeded main build and +455% to +460% in the ten seeded IID builds of Weight FRS BRMA draws by census private-rented households #516 from 2-3 October.obr/universal_credit_in_capcompared £66.4bn with all UK UC (+12%). They are replaced by one target,obr/universal_credit. It is the sum of EFO table 4.9's two UC rows (£79.29bn in 2025-26), counted over Great Britain only.dwp/uc_payment_dist/*_nan_to_nantargets (1.4k to 83k households) had a column of zeros. They are now reachable as*_30_000_to_inf, and the bands meet without gaps.1. Universal credit inside and outside the welfare cap
What OBR's split is. The welfare cap is the Charter for Budget Responsibility's limit on welfare spending, not the household benefit cap. It excludes the State Pension and the payments most sensitive to the economic cycle: JSA, the Housing Benefit paid with it, and their UC equivalent (NAO, Managing the welfare cap, 2016). The sources agree on what that UC equivalent is:
The old column was UC for households whose
benefit_cap_reductionis zero, which is a different concept.Why one total rather than a corrected split. policyengine-uk 2.93.0, which this repo pins, has no UC conditionality regime. Its LCW/LCWRA input,
uc_limited_capability_for_WRA, isis_disabled_for_benefits. This repo sets that from reported AA, DLA, PIP, SDA, incapacity benefit, IIDB, AFCS or ESA receipt, not from a work capability assessment. I built proxies of the Intensive Work Search group from the legal tests and FRS employment status, and scored them on seeded builds against DWP's GB figures (£12.876bn, 1,323k):The close fit on main is partly luck. Main maps FRS EMPSTATI 11 ("other inactive") to
LONG_TERM_DISABLED; #526 corrects that. With the correction, about 356k "other inactive" UC benefit units leave the health group and the proxy is 27% over. Other reasonable choices, such as how to treat students or couples where only one partner is in the group, move it between −26% and +42%.Calibrating to a split this fragile would shift weight between kinds of UC household in a way no output reveals. The total, by contrast, is exactly OBR's figure. A chip tracks restoring the split once policyengine-uk models the work-related groups (#2081's
uc_work_related_group_apart_from_earnings) and this repo supplies their inputs (#525, #526, #527).Great Britain only. Both UC rows sit under table 4.9's "DWP social security", which DWP's tables label "DWP Social Security (GB)". Northern Ireland's UC is in the separate "NI social security" rows. The target therefore counts GB households only. In the seeded main build that excludes £1.65bn of the model's £74.4bn UC in 2025-26. This uses #490's
countriesfield andrestrict_to_countries, copied byte for byte, so the two PRs merge cleanly in either order (the review checked both). Other DWP targets have the same GB-versus-UK mismatch; a chip covers them.Parsing. The two rows are now located by section across the whole sheet: one "Universal credit" row either side of "Welfare spending outside the welfare cap". The code no longer takes the first and second matches. No UC target is produced, rather than a partial total, in any of these cases:
2. UC payment distribution
The Stat-Xplore extract (
storage/uc_national_payment_dist.xlsx, May 2025) ends with an open band, "£2500.01 or over".parse_bandonly understood "X to Y", so that band got NaN bounds anduc >= nanmatched nothing.Why the gaps mattered. The old test was
uc >= min × 12 & uc < max × 12. It dropped awards of exactly £100.00, £200.00, … a month, and anything in the 12p gaps between bands. It also dropped most penny awards. policyengine-uk leaves 1p a month payable after deductions (SI 2013/380 Sch 6), and 44 records (136k benefit units in the seeded main build) sit there. Subtracting the capped deduction in float32 leaves about £0.1199951 a year rather than £0.12, so the old £0.12 lower bound excluded 40 of them (134k benefit units) and kept 4. Stat-Xplore's "Monthly Award Amount (payment bands)" metadata says the award is "the total amount of Universal Credit due ... after any deductions", which is whatuniversal_creditis. So a penny award belongs in "£0.01 to £100.00".Scored against the corrected bands, the main build is too high in that bottom band: singles +71%, lone parents +61%, couples with children +273%. This PR's build fits them (see Impact). A middle commit briefly reverted to the old bands on the wrong premise that Stat-Xplore's awards are before deductions; the third commit restores the gapless bands. The target definitions at the head are identical to the first commit's (all 116 affected targets compared).
Target names are unchanged apart from the four top bands. The top bands rest on few records (2025): 5 singles, 34 lone parents and 32 couples with children have UC above £30,000 a year. No couple without children does, so that target (1.4k households) stays at zero whatever the weights.
Invariants and tests
create_target_matrixitself with a fake microsimulation.create_target_matrix;countries=GREAT_BRITAIN;>= lower & < upper(even keeping zero awards out);Impact (seeded production builds)
Origin/main b45c373 and this PR were built with the same
seeded_build.py, seed 0, and identical cached downloads (no network fetches). Main's two repeat builds were output-identical. Settings: 512 epochs, withPE_UK_DATA_OA_CLONES=1(a production build defaults to 10 OA clones), the setting the other batch PRs' seeded builds use.Builds compared:
Both are scored on this PR's target definitions, at 2025, with the build's own model (policyengine-uk 2.93.0).
Fit to the national targets
obr/universal_credit(GB)…SINGLE_annual_payment_30_000_to_inf…LONE_PARENT_annual_payment_30_000_to_inf…COUPLE_NO_CHILDREN_annual_payment_30_000_to_inf…COUPLE_WITH_CHILDREN_annual_payment_30_000_to_inf…_annual_payment_0_to_1_200: singles / lone parents / couples with childrendwp/uc/,uc_payment_dist): mean loss termAmong the remaining 627 targets, three move most:
ons/multi_family_households: +1,071% → +1,063%. The model misses this one tenfold either way.hmrc/cgt_taxpayers: +52.5% → +49.2%.Every other target's error moves by 2.1 points or less.
Microsimulation (policyengine-uk main 84f5ad43a, the same datasets)
Housing Benefit, Pension Credit and Child Benefit move by £0.03bn or less, and households and people by 0.01m or less.
What drives this. The calibration no longer pulls UC towards the old outside-the-cap target, and it now reaches the top payment band. Weight therefore moves to higher-award UC households rather than to more claimants: the number of GB benefit units on UC is broadly stable (−1.5k in 2025, +20k in 2026). That raises UC by about £3.9bn and lowers poverty by about 0.2m people (0.1m children).
Band edges. A build of the middle commit (4a1b1a6) has the OBR fix and the open top band but the old band edges. It gives UC +£3.62bn and AHC poverty −0.18m in 2025, so the OBR fix and the open top band together account for most of the change. Restoring gapless edges adds about £0.31bn of UC. No build separates the OBR fix from the open top band.
Scripts, logs and per-target tables are in
~/reviews/uc-welfare-cap-2026-10-02(fit.py, metrics.py, compare.py, impact/).Batch notes
hypothesis>=6.168.3instead ofhypothesis; every one of them locks Hypothesis 6.168.3.countriesmechanism is Calibrate Housing Benefit to DWP's GB figures by age group #490's, byte for byte.🤖 Generated with Claude Code