Repository navigation
Conversation
Copies, verbatim, the `countries` field and GREAT_BRITAIN constant (targets/schema.py) and `restrict_to_countries` (targets/build_loss_matrix.py) that #490 and #530 add, so all three PRs make the identical change. Also copies #526's hypothesis dev dependency (pyproject.toml and uv.lock hunks identical to #526's and #530's) for the property tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OBR EFO table 4.9's "DWP social security" lines are DWP's "DWP Social Security (GB)" figures, which leave out Northern Ireland; for disability benefits, Carer's Allowance and Winter Fuel Payment they also leave out Scotland (DWP benefit expenditure and caseload tables, Notes K, L and N). DWP's benefit, benefit cap, UC and two-child-limit statistics cover Great Britain, and its PIP statistics England and Wales. Each of these targets now sets `countries`, so its loss matrix column counts only households there. Child benefit (HMRC) stays UK-wide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every line in table 4.9's "DWP social security" blocks is DWP's GB figure, so the coverage map now names only the exceptions (the four benefits devolved in Scotland, and HMRC's child benefit) and defaults to Great Britain. #490, #510 and #530 drop the housing benefit, pension credit and in-cap universal credit lines, which would have left stale entries in a map of every line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Property tests for restrict_to_countries (in-scope households kept, others zeroed; complementary country sets add back to the column; restricting twice restricts to the common countries; None is the identity), the loss matrix end to end, table 4.9's block structure against each OBR welfare target's coverage, and that every DWP target declares its countries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loss-matrix test now runs create_target_matrix on a policyengine-uk simulation with one household in each country, so GREAT_BRITAIN and ENGLAND_AND_WALES are checked against what the `country` variable returns, not against a stub. obr.py imports the schema names in one statement: the split import did not avoid the textual conflict with #530 it was there for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 of 52 tasks
This branch has not been deployed
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.
What and why
Several national calibration targets are Great Britain statistics, but their loss-matrix columns summed over every UK household. Northern Ireland runs its own social security, so its households inflated the model side of every DWP target and every DWP line of OBR EFO table 4.9. For the benefits whose executive competence passed to the Scottish Government, DWP's figures cover England and Wales only, so Scottish households inflated those too.
This PR:
countriesfield,GREAT_BRITAINandrestrict_to_countriesnow on main through Calibrate Housing Benefit to DWP's GB figures by age group #490, Solve Pension Credit take-up over entitled benefit units #510 and Calibrate GB universal credit to one OBR total; reach the UC payment top band #530. AddsENGLAND_AND_WALESnext toGREAT_BRITAIN, while retaining main'scarry_forwardfield and_resolve_valuebehaviour.countrieson every target intargets/sources/dwp.py: Great Britain, except England and Wales for the two PIP claimant counts.dwp/scotland_uc_households_child_under_1gets("SCOTLAND",); its compute already filters to Scotland, so its column doesn't change.countrieson the OBR table 4.9 welfare targets in_parse_welfare. Each line in a "DWP social security" block defaults to Great Britain. The map names only the exceptions: England and Wales forpip(the "Disability living allowance and personal independence payments" line),attendance_allowance,carers_allowanceandwinter_fuel_allowance, and UK-wide forchild_benefit(HMRC). The surviving combinedobr/universal_credittarget from Calibrate GB universal credit to one OBR total; reach the UC payment top band #530 remains Great Britain.hypothesis>=6.168.3dev dependency for the property tests. The duplicate unpinned dependency was removed during the refresh. Main'suv.lockwas retained, thenuv lock --checkrequired one metadata update: the editable project version from 1.57.4 to 1.58.0. No dependency resolution changed (uv lock --checkpasses; only the editable project version changed). The locked engines remain policyengine-uk 2.122.2 and policyengine-core 3.32.13.This is the follow-up #530's owner filed (GB scope for the other DWP-GB targets).
Evidence
OBR EFO March 2026, detailed table 4.9 (committed at
storage/obr_efo/efo_expenditure.xlsx):DWP benefit expenditure and caseload tables, Spring Forecast 2026:
The "UK welfare" sheet labels these lines "DWP Social Security (GB)": £308.31bn in 2025-26. Table 4.9's two DWP blocks give £148.87bn + £159.34bn = £308.21bn. "Northern Ireland Social Security" is £10.659bn, and table 4.9's NI rows give £6.244bn + £4.414bn.
The line-by-line figures for 2025-26 agree:
Notes, geographical coverage: "Figures relate to Great Britain, or people resident overseas who are receiving United Kingdom benefits, except for Over 75 TV Licences, War Pensions and Financial Assistance Scheme payments which also cover Northern Ireland. All other benefit expenditure on residents of Northern Ireland is the responsibility of the Northern Ireland Executive. Great Britain figures include expenditure for Scotland, with the exception of benefits which are given as England and Wales spending only, from the point that executive competence was transferred to Scottish Government."
Note K: Carer's Allowance is England and Wales only from 2019/20, with Scotland transferred on 3 September 2018.
Note L: Disability Living Allowance, Personal Independence Payment, Attendance Allowance, Severe Disablement Allowance and Industrial Injuries Disablement Benefit are England and Wales only from 2020/21.
Note N: Winter Fuel Payment is England and Wales only from 2024/25.
Table 4.9 starts at 2024-25, so all of its years fall after these dates.
DWP statistics (each release's own coverage statement):
dwp/esa_*,dwp/jsa_claimantsdwp/pip_dl_*dwp/benefit_cap*dwp/uc/householdsdwp/uc/two_child_limit/*dwp/uc/claimants_*,dwp/uc_payment_dist/*,dwp/scotland_uc_*Historical diagnostic model side (policyengine-uk 2.93.0, locked when the pre-batch diagnostic below was run; the refreshed branch now locks 2.122.2):
winter_fuel_allowanceis zero in Scotland in every year.carers_allowanceis zero in Scotland from 2025, when Carer Support Payment replaces it.pipandattendance_allowancehave no Scotland condition, so Scottish households' disability benefits counted towards DWP's England and Wales totals.Historical diagnostic: how much of each column lay outside the target's countries, on pre-batch main's weights before any recalibration:
fit.py, which records each column before and afterrestrict_to_countries.obr/pipobr/attendance_allowanceobr/carers_allowanceobr/winter_fuel_allowanceobr/state_pensionobr/pension_creditobr/esaobr/housing_benefitobr/statutory_maternity_payobr/universal_credit_in_capobr/universal_credit_outside_capdwp/pip_dl_standard_claimantsdwp/pip_dl_enhanced_claimantsdwp/esa_claimantsdwp/esa_contrib_claimantsdwp/esa_income_claimantsdwp/uc/householdsOn main's weights:
dwp/uc/claimants_*targets shown have 1.9%–3.5% of their column outside Great Britain. Their mean absolute error falls from 7.5% to 5.1%.dwp/uc_payment_dist/*, 21 of 104 targets clear the threshold; their median share outside Great Britain is 1.4%.The historical diagnostic also exposed target-definition issues. #530 has since replaced the split UC targets; the surviving PIP and overseas State Pension issues are listed below.
Invariants
These hold for every input:
restrict_to_countriesleaves each household in the target's countries unchanged and sets every other household to zero.restrict(col, C) + restrict(col, all countries not in C) == col. A GB column plus its Northern Ireland and unknown-country remainder is the UK column, so nothing is lost or double counted.restrict(restrict(col, A), B) == restrict(col, A ∩ B). Restriction is idempotent, so the Scotland-only target, whose compute already filters Scotland, is unchanged.countries=None,restrict_to_countriesreturns the column object itself. Every UK-wide target's column is the same object main produced.countries: England and Wales for the four devolved benefits, Great Britain for the rest.dwp*source module declarescountries, and each is a subset of Great Britain. This includes Calibrate Housing Benefit to DWP's GB figures by age group #490'sdwp_housing_benefit.pyand Solve Pension Credit take-up over entitled benefit units #510'sdwp_pension_credit.py, which already setGREAT_BRITAIN.GREAT_BRITAINandENGLAND_AND_WALEShold values the livecountryvariable returns.Tests
Before the refresh,
policyengine_uk_data/tests/test_target_countries.pyhad 10 tests, all passing locally:create_target_matrixend to end on a real policyengine-uk simulation with one household in each country (invariant 7). Each household holds a distinct power of two, so each column's total identifies exactly which countries it counted: UK 15, GB 7, E&W 5, NI 8.obr/universal_credit), not by the parser's label lookup.Pre-refresh mutation check: nine single-line mutations, each caught by at least one test. The outside-the-cap UC mutation below refers to the old split target, since replaced by #530.
restrict_to_countriesas the identity.countries.pipdefaulting to GB.countries.ENGLAND_AND_WALESspelt as display names.At the old head f0a5dbc (pre-batch main),
test_target_countries.py,test_obr_efo_fallback.py,test_target_registry.py,test_esa_jsa_targets.py,test_target_db.py,test_obr_receipts_sheet.py,test_obr_nics.pyandtest_obr_council_tax.pygive 60 passed.A pre-refresh trial batch was reported as 106 passed, 2 skipped and 11 fallback-test failures. Those obsolete fallback assertions have since been fixed on main: the offline OBR target-set test now compares the offline set with the served set. The trial counts are historical, and the refresh results below are the current evidence.
Impact
Pending a real rebuild of the refreshed head against post-batch main 4cbedbe (1.58.0). No dataset was rebuilt for this refresh. The table above is a historical diagnostic on pre-batch weights, not an impact estimate. The hub runs the real seeded comparison after the push, under its build schedule.
The hub should build both sides with the same seed, authorised input donor and HTTP cache, then score both using this PR's refreshed target definitions. The commands below pin the baseline and proposed commits and use fresh labels so
run_build.shcannot reuse an older build. Run outside this restricted refresh workspace, with more than 60 GB free and no other build running.The old setup script's
uk-data-cvpaydonor no longer exists.uk-data-combined-1008currently has the required raw-input symlinks and seven trained-model files. Use it for both sides; this does not claim parity with the old diagnostic's donor. The command verifies the prerequisites, records the donor and model hashes, and makes a setup copy with that donor before building.setup_worktree.shuses the pinned commit's frozen Python 3.13 environment and copies the same trained models into each worktree. The runner still matches main'sfrs_2024_25filenames and builds with 512 epochs andPE_UK_DATA_OA_CLONES=1.fit.pystill matches the loss-matrix API, uses the refreshed branch's_resolve_value/carry_forwardrules and scores calibration year 2025. The HTTP cache pins target downloads; a new target URL is fetched live during a build and logged inbuild_<label>.http. No command in this block was executed during the refresh.Merge interactions
The former 10/8 batch landed on 10/7 as uk-data 1.58.0. This PR was held out and remains for the next batch, on Max's go (d833). The refreshed branch merges post-batch main 4cbedbe and retains its newer target set and download fallback.
countries,GREAT_BRITAINandrestrict_to_countries, and Calibrate Housing Benefit to DWP's GB figures by age group #490'scarry_forwardfield and_resolve_valuelogic.schema.pynow includes bothcarry_forwardand this PR'sENGLAND_AND_WALES.build_loss_matrix.pyanddwp.pyauto-merged; their resulting semantics were checked against main and the original PR diff.obr/universal_creditfor the summed inside/outside welfare-cap expenditure. No split UC target was reintroduced. The coverage test matches its two workbook rows independently.obr/housing_benefitandobr/pension_credittargets stay removed; the country map has no stale entries for them.obr/ni; previous trial-batch counts and descriptions of its removal do not describe this baseline.hypothesis;pyproject.tomlmatches main's dependency set. Main's lock check failed because its editable project still said 1.57.4.uv lockchanged only that project version to 1.58.0; every dependency entry, including policyengine-uk 2.122.2 and core 3.32.13, is unchanged.Post-batch refresh (10/7)
ec312d6fcc7d9b8222445c44bd051397dbaf8e75, a fast-forward descendant off0a5dbc66f2562f6f8bd6076142c1ac4efcbde3b. The merge commit5f6c55ba5c7dc0f8499ad07d422c828883e591bbhas the old head and main4cbedbecca352f52dffe47e849553c2759a00711as its parents.schema.pyby keeping main'scarry_forwardand the PR'sENGLAND_AND_WALES; resolved bothobr.pyhunks by combining the country-constant imports and preserving main's surviving targets and workbook fallback while applying this PR's source country scopes. Used main'suv.lock, removed the duplicatehypothesisfrompyproject.toml, and after the lock check failed refreshed only the editable project version to 1.58.0.test_target_countries.pyfor the batch targets and unchanged source coverage.-p no:cacheproviderand no xdist. The first fourteen files ran on Python 3.13.9. Fortest_calibrate_validation_targets.py, two startup attempts were interrupted and a diagnostic retry exited with a native SIGSEGV during Torch lazy Dynamo/SymPy imports. Both tests then passed on Python 3.13.15 with the identical locked dependencies, a real CPU Adam preloaded before pytest, and OMP/MKL/OpenBLAS threads limited to 2. Test sources and assertions were unchanged; failed startup diagnostics were retained.uv lock --checkpasses; only the editable project version changed. Installed policyengine-uk 2.122.2 and policyengine-core 3.32.13.test_target_countries.pytest_obr_efo_fallback.pytest_target_registry.pytest_esa_jsa_targets.pytest_target_db.pytest_obr_receipts_sheet.pytest_obr_nics.pytest_obr_council_tax.pytest_obr_universal_credit_target.pytest_dwp_housing_benefit_targets.pytest_pension_credit_takeup.pytest_lfs_employment_targets.pytest_hmrc_salary_sacrifice_targets.pytest_uc_payment_distribution_targets.pytest_calibrate_validation_targets.pyFollow-ups (not in this PR)
obr/pipcompares the "Disability living allowance and personal independence payments" line (£36.91bn in 2025-26) with the model'spipalone. There is no custom compute, so it falls through to_compute_simple_gbponpip. DWP's tables put DLA at £8.29bn of that. Withoutdlathe model side can't reach the line, and calibration pushes PIP up to make up the gap. Scoping to England and Wales doesn't change this, but it widens the pre-recalibration gap from −24.5% to −30.4%.obr/state_pensionincludes State Pension paid to people living abroad, which no UK household in the model receives. DWP's tables put it at £5.63bn in 2025-26.🤖 Generated with Claude Code