OBR costings divergence decomposition: axes registry + identity-checked pipeline (#59) - #67
OBR costings divergence decomposition: axes registry + identity-checked pipeline (#59)#67vahid-ahmadi wants to merge 2 commits into
Conversation
…ts from primary sources, identity-checked pipeline Builds #59. Consumes PR #56's COMPARISON.csv by path (no shared commits); a verbatim fixture copy makes the arithmetic executable now. Sized from fetched primary sources: the employer NICs package's missing Employment Allowance construction (+4,581m/+4,631m) and static-vs-post-behavioural difference (+723m/+703m) from the OBR's 8 May 2025 supplementary static-costing release (Tables 1.3-1.5), and HICBC's missing welfare head (+391m/+426m) from the comparison's own head rows. Everything else is registered unsized with an explicit sizing recipe; the pipeline forces decomposition_status=partial and labels the remainder residual_plus_unsized so a partial decomposition never presents as explained. gap = sized + residual holds exactly and is test-pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review — OBR costings divergence decompositionThe mechanical scaffolding (schema, "never explained while unsized" gate, JSON hygiene) is sound, but I think the decomposition has a substantive correctness problem that the current test locks in rather than catches. Requesting changes. The identity "check" is a tautology
Working the employer-NICs 2026-27 row by hand: Sign / attribution of the sized componentsBoth employer-NICs sized components ( Should
To be clear on what's fine: the "never mark Reviewed with Claude Code assistance. |
… diagnostics, quoted-vs-derived contract The identity residual = gap - sum(valued) stays as bookkeeping but no longer masquerades as a check. Every valued component is classified explains_gap / masks_gap against the gap's sign; any masking component sets divergence_understated with the widened like_for_like_gap_gbp, |residual| > |gap| is flagged, and explained_share is emitted only for complete decompositions with no masking components. The behavioural component is re-statused 'derived' (formula mandatory, travels into the artifact); sized now means verbatim-quoted only and load_registry rejects a sized component carrying a derivation. The employer-NICs measure gains its dominant missing component (employee_incidence_and_base, unsized with a paired-run recipe: PE's employee_incidence=1 drives the -8.5bn IT head vs OBR's -0.3bn) and the EA component's provenance now carries the head-attribution caveat from the review. The review's hand-worked row is a pinned regression: gap -7.364bn, both valued components masking, residual -12.668bn exceeding the gap, no explained claim. Tests 10 -> 17 (synthetic masking/overshoot cases that fail on the old semantics); full suite 180 passed / 4 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All three points addressed in ff38e0e, including the substantive one. Point by point: The tautology. Agreed — Sign / attribution. I re-checked the orientation chain and the two components' signs are individually defensible (EA-absence contributes +4.58bn to PE−OBR because the missing elements are revenue-reducing in OBR's leg; static−post-behavioural is +723m by Table 1.5 arithmetic) — but you're right that presenting them as decomposition steps on this row was wrong, and right about the head attribution: the EA effect lands on the NICs head where PE is only +0.83bn above OBR, so ~−3.75bn of offsetting NICs-base factors must coexist, and the total gap is dominated by the IT head (PE −8.51bn vs OBR −0.31bn) — which is PE's Derived vs quoted. The behavioural component is re-statused Tests 10 → 17; full suite 180 passed / 4 skipped; ruff clean. |
Re-review — request-changes resolved ✅This fully addresses my earlier review — nicely done, and thank you for the rework. Verified against the live pipeline (17 tests pass; 12 records, all
Remaining bits are honestly disclosed and non-blocking (the EA head-attribution still awaits the paired run to confirm; a render-only unicode console quirk). Happy to see this move forward — clearing my change request. |
DTrim99
left a comment
There was a problem hiding this comment.
Re-reviewed — my earlier change request is fully resolved: the identity is de-tautologized with failable checks, the employer-NICs components are reclassified masks_gap (I reproduced the arithmetic), and the behavioural term is status: derived under an enforced sized-vs-derived contract. Approving — thanks for the thorough rework.
The #30 pattern applied to the mode-2 UK lane: every #56 comparison row decomposes into signed GBP components summing exactly to gap = PE − OBR, with a residual, under two honesty rules enforced by the pipeline and tests — a sized component must carry per-FY values with verbatim fetched-primary-source provenance, and any unsized component forces
decomposition_status: partialwith the remainder labelledresidual_plus_unsized, neverexplained.What's sized with real published numbers (fetched, quoted in the registry)
Files
data/uk/obr_divergence_axes.json— 6-measure registry (JSON, not YAML: CI installs pytest only). Axes: behavioural_adjustment, baseline_vintage, cy_proxies_fy, head_scope, construction_scope, residual.pipeline/decompose_uk_obr_divergence.py— engine-free csv/json arithmetic; emitsresults/uk/obr_divergence/DECOMPOSITION.{json,md}; a measure whose registered row_kind is absent errors rather than decomposing a different scope.tests/fixtures/obr_costings_comparison_20260817.csv— verbatim copy oforigin/obr-costings-mode2:results/uk/obr_costings/COMPARISON.csv(provenance in the test module docstring) so the pipeline is executable before OBR policy costings (mode 2): registry, offline certified compute, staging, descriptive comparison #56 merges.tests/test_obr_divergence_decomposition.py— 10 tests: registry contract (sized⇒provenance, unsized⇒recipe), the exact identity, partial-never-explained, the pinned employer-NICs and HICBC sizings, missing-row refusal.Suite: 173 passed / 4 skipped; ruff clean.
Dependency: consumes #56's
COMPARISON.csvby path only — no shared commits; when #56 merges, running the pipeline against the real artifact replaces the fixture path. 12/12 decompositions arepartialby design until the unsized axes get their runs.Builds #59.
🤖 Generated with Claude Code