Fix silently-dropped OBR receipts targets (sheet 3.9 is APD, not current receipts)#439
Merged
Conversation
The cash-basis receipts rows were read from a hard-coded sheet "3.9". In the March 2026 EFO tables current receipts are on 3.8 and 3.9 is the APD forecast, so every lookup raised ValueError, was caught, logged a warning, and produced no target. All five cash-basis receipts targets - NICs, VAT, fuel duties, capital gains tax and SDLT - were therefore missing from the calibration surface with nothing failing. Locate the sheet by its title so a future renumbering cannot repeat this, and raise when none of the rows are found: silently dropping every receipts target should never be a passing outcome. Verified against the March 2026 receipts workbook, which now yields all five (2026-27: NICs 210.45, VAT 187.70, fuel duties 24.63, CGT 20.80, SDLT 18.15 GBP bn). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 19, 2026
Two gaps in the first cut, both variants of the silent failure this PR
exists to prevent:
- The guard counted caught exceptions, so a vintage that shifted the value
columns would find every label, produce no targets, and still pass. It
now keys on rows that actually yielded values.
- Several sheets mention receipts ('Other receipts', 'Property transactions
taxes: Receipts by sector'), and the matcher took the first substring hit.
It now matches the full 'current receipts' phrase, prefers the cash-basis
sheet when a vintage makes that ambiguous, and raises rather than guessing.
Also corrects the docstring, which still referred to Table 3.9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 20, 2026
Closed
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.
Problem
_parse_receiptsreads the cash-basis receipts rows from a hard-coded worksheet"3.9":In the March 2026 EFO tables — the vintage currently configured in
sources.yaml— sheet 3.8 is "Current receipts (on a cash basis)" and sheet 3.9 is "APD forecast - projection of passenger numbers". None of the five labels exist on 3.9, so every_find_rowcall raisesValueError, is caught, logs a warning, and appends no target.The result is that all five cash-basis receipts targets are missing from the calibration surface — NICs, VAT, fuel duties, capital gains tax and SDLT — with nothing failing. Verified against the configured workbook:
Note the existing
cols_39/fy_39column mapping (D→2024-25 …J→2030-31) already matches 3.8's layout exactly, which suggests the code was written against 3.8 and the sheet was renumbered by a later vintage without the reference being updated.Fix
"current receipts"in cell B2) rather than by number, so a future renumbering cannot silently repeat this.Verification
Against the March 2026 receipts workbook the fix resolves sheet 3.8 and recovers all five rows (2026-27, £bn):
Three unit tests cover the March 2026 layout, a hypothetical future renumbering, and the loud-failure path.
Why I noticed
Capital gains came up in a CGT reform analysis: the Enhanced FRS baseline produces ~1.29m CGT taxpayers holding £112bn of gains against HMRC's 378k and £66bn, and CGT revenue of ~£25bn against the OBR's £20.8bn forecast for 2026-27. Tracing why the OBR figure wasn't constraining the calibration led here. Restoring this target should pull CGT revenue toward the forecast directly.
Separately,
populacehas no capital gains targets at all in its UK surface — PolicyEngine/populace#459 — so gains totals and taxpayer counts remain unconstrained even with this fixed. The two are complementary: this PR restores the OBR revenue target; that issue is about HMRC gains and taxpayer targets.