Repository navigation
Conversation
The cash-receipts parser targeted Table 3.8 "National insurance contributions" (total NICs, £200.08bn in 2025-26) on the ni_employee variable, which _parse_nics also targets at the Class 1 employee figure (£49.54bn). The two targets on one matrix column contradicted each other; the seeded constituency calibration on main b45c373 ended with obr/ni at 0.242 of target. Drop the row rather than retarget it: the Table 3.4 class targets (employee, employer, self-employed) already cover every NIC class the data populates, and a total target on total_national_insurance would conflict with them through the statutory payment recoveries and "Other NIC" lines PE-UK does not model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test_obr_target_mapping: pin the variable of every target parsed from the committed receipts workbook, the Table 3.4 rows behind the NIC class targets, and the source identity that rules out a total-NICs target. - test_obr_target_conflicts: no two calibration targets whose matrix column is the same plain variable sum or count may disagree on a year's value, checked over the OBR targets offline and over every target the national matrix uses; it flags the removed obr/ni mapping, and a seeded random check holds the grouped check to its pairwise definition. - build_loss_matrix.calibration_targets: the target list create_target_matrix already built inline, extracted so the test checks the same set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
_parse_receiptsinpolicyengine_uk_data/targets/sources/obr.pyread the Table 3.8 (cash basis) "National insurance contributions" row asobr/nion theni_employeevariable. That row is total NICs: £200.08bn for 2025-26 in the committed March 2026 workbook._parse_nicsalso targetsni_employee, at the Table 3.4 "Class 1 Employee NICs" row (£49.54bn). Two targets on one matrix column with different values cannot both be met. The seeded constituency calibration on main b45c373 ended withobr/niat 0.242 of target, its estimate equal to the weightedni_employeetotal.Fix: drop the row rather than retarget it. PE-UK 2.93.0 has
total_national_insurance(Class 1 employee, Class 2, Class 3, Class 4 and Class 1 employer). Retargetingobr/nithere would still conflict with the class targets. Table 3.4 splits total NICs (£203.99bn accrued) into:A total target would therefore demand NICs no household carries, on top of the cash/accrued basis difference (£200.08bn against £203.99bn).
Also:
build_loss_matrix.calibration_targets()extracts, unchanged, the target listcreate_target_matrixbuilt inline, so the new test checks the same set.Invariants (tested)
countriesrestriction, if Calibrate Housing Benefit to DWP's GB figures by age group #490/Calibrate GB universal credit to one OBR total; reach the UC payment top band #530 land) disagree on a year's value, after_resolve_value's nearest-year fallback.INTENDED_SHARED_COLUMNS(empty) is where a documented exception would go._compute_columnitself, with every custom compute function swapped for a sentinel.obr/nimapping.random.Random(0).obr/niis absent even though its row exists.obr.py.pytest policyengine_uk_data/tests/test_obr_*.py: 33 passed, 5 skipped (the enhanced-FRS signal tests, skipped as on main).Impact, from real calibrations (2026-10-05)
The setup is the UK hub's #536 harness: one saved input built from main b45c373 (1 output-area clone), and
calibrate_local_areasfor constituencies with 512 epochs, 650 areas,torch.manual_seed(0)and 8 threads. The committed EFO workbooks are served, and one environment (policyengine-uk 2.93.0) is used for both runs. Base reproduces the hub's run D exactly: same 637 targets, identical totals.obr/niand fix doesn't. Nothing else differs (636 common).obr/ni_employee0.978 → 0.966: the false pull toward £200bn had been lifting it.obr/ni_employer1.006 → 0.997.VAT finding (reported, not changed here)
obr/vatends at 1.758× its target (£316.8bn against OBR cash VAT of £180.17bn).Mechanism (PE-UK 2.93.0,
variables/gov/hmrc/vat.py):vat = (full_rate_vat_consumption × 0.20 + reduced_rate_vat_consumption × 0.05) / gov.simulation.microdata_vat_coverage. The parameter is 0.38, its only value, from 2010-01-01. Its description says it scales survey VAT up to HMRC receipts because LCFS under-reports consumption.vat(÷0.38)impute_vatis not the cause. It imputes a share (full_rate_vat_expenditure_rate, weighted mean 0.349), not a level. One unverified second-order point: the share is taken over VAT-exclusive spending (expdis − totvat), but PE-UK multiplies it byconsumption. If that is VAT-inclusive, the base is about 9% high.microdata_vat_coverage: the value has been 0.383 since 2010 and overshoots OBR VAT receipts on current datasets policyengine-uk#1996 proposes re-deriving the constant. Changing it moves every published PE-UK VAT figure, so this PR leaves it alone.obr/vatexcluded from training (fix_novat) is running, to measure what the unattainable target costs the other targets.Batch interplay (uk-data lands as one release, d833)
_RECEIPTS_AND_NICS_TARGETSintest_obr_efo_fallback.pylists"obr/ni". Whichever of Fall back to the committed OBR workbooks when obr.uk answers 200 with a non-workbook page #536 and this PR lands second must drop that entry (one line).test_full_target_set_available_offlineassertslen(names) >= 30. On main there are 34 OBR targets. This PR takes them to 33 (−1). The rest of the batch removes 5 more: Calibrate Housing Benefit to DWP's GB figures by age group #490 −1, Solve Pension Credit take-up over entitled benefit units #510 another −1 (stacked on Calibrate Housing Benefit to DWP's GB figures by age group #490), Calibrate GB universal credit to one OBR total; reach the UC payment top band #530 −1 and Restore HMRC salary sacrifice relief calibration targets #533 −2. The combined tree has 28, so the last of these to land trips the threshold.test_obr_*passes on the combined tree, except Calibrate GB universal credit to one OBR total; reach the UC payment top band #530's new hypothesis-based test, which needs the hypothesis dev dependency that Calibrate Housing Benefit to DWP's GB figures by age group #490/Solve Pension Credit take-up over entitled benefit units #510 add.The FRS-derived inputs, outputs and harness stay local in
~/reviews/uk-hub/jobs/obr-ni-target/. Only aggregates appear here.axiom: n/a: calibration-target mapping in the data build; no policy rule changes.
🤖 Generated with Claude Code