HMT distributional analysis (mode 2, distributional): Budget 2025 external side — chart-only scope, honestly - #68
HMT distributional analysis (mode 2, distributional): Budget 2025 external side — chart-only scope, honestly#68vahid-ahmadi wants to merge 5 commits into
Conversation
…nest chart-only scope - Vendors the real publication (Impact on households: distributional analysis to accompany Budget 2025, fetched 2026-08-17 from gov.uk, SHA-256 pinned in raw/README.md, source.json, registry and tests). - data/uk/hmt_da_packages.yaml: the Budget 2025 package entry — verbatim in-scope measure list (30 components) with per-component channel and computability triage (3 expressible / 9 partial / 18 not_expressible), published exclusions, counterfactual, horizon and income concept. Executable reform specs are never invented here; expressible components reference measure families for the OBR measure registry. - sources/hmt-distributional/adapter.py: verifies rather than parses — SHA gate, page count, figure inventory, and every registry component title verbatim-anchored in the document text. EMITS NO VALUE CLAIMS: HMT publishes the decile impacts as unlabeled chart bars with no data tables (confirmed against the Budget 2025 supporting documents, which carry XLSX only for costings tables 4.1/4.2), so per-decile external values wait for an HMT data release or a documented digitization. - tests/test_hmt_da_adapter.py: raw-file identity, provenance files, no-value-emission contract, meta-artifact honesty; registry schema checks run when pyyaml is importable (CI's bare pytest env skips them; the adapter enforces the same contract when executed). Suite: 167 passed / 5 skipped; ruff format clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review — HMT distributional analysis (mode 2, chart-only)The central call here — shipping the external side without value claims because HMT publishes no machine-readable decile data — is the right one, and it holds across every file: no fabricated or eyeballed decile value appears anywhere. Every number in Verified:
Should address
Minor
Reviewed with Claude Code assistance. |
test_document_anchoring_in_ci_when_pypdf_available loads the adapter and enforces the verbatim-title, figure-anchor and 20-page checks in any test env with pypdf+pyyaml (importorskip, mirroring the yaml-gated schema test); the adapter's own run() stays the local backstop. Also adds meta.json's missing trailing newline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed in the latest commit:
Full suite: 167 passed / 6 skipped; ruff clean. The unverified |
Re-review — addressed ✅The pypdf-gated CI test I asked for is in: SHA-256 pin, |
DTrim99
left a comment
There was a problem hiding this comment.
Re-reviewed — addressed: the pypdf-gated CI test now enforces the verbatim/figure/20-page guarantees, and the SHA pin + counts still hold. Approving.
Gate round 1 — the chart-only honesty holds; six findings before rows can landVerified clean: extraction is text-layer anchoring only — no OCR, no digitization, no numeric values at claimed precision — so there is not a single fabricated-precision row today; the vendored 20-page PDF's SHA-256 matches all four pins; and the current GOV.UK attachment list corroborates that the distributional analysis is PDF-only.
🤖 Generated with Claude Code |
1. Blocking — the lane no longer vanishes from a post-#74 build. The adapter wrote standalone metadata and nothing registered in build_db.py, so a fresh CI-built DB held no trace of HMT at all — not even a zero. scorecard_db/ingest_hmt_distributional.py is the missing step: it emits no external_scores rows (deliberately, and it raises if the metadata ever claims otherwise), writes the lane row with the exact chart-cell accounting as its detail, and mirrors it into the committed feed under UK. Registered in build_db after uk_deductions. 2. The omission is tallied, not asserted. The registry listed figures and series but not the GROUPS, so "0 emitted" had no denominator. The eleven income groups are now registry data, and the adapter enumerates and checks every cell: 132 source marks = 0 emitted + 132 chart_not_digitized, by figure / income group / series, with the arithmetic raising if it does not close. The 30 policy components still reconcile 3+9+18 = 16+9+5. 3. Decile identity is data. The ten equivalised-net-income deciles plus the all-households bar are a closed uk_aliases vocabulary, with housing_costs=bhc and equivalisation=modified_oecd pinned beside them (the HBAI/UKMOD pattern), and HMT deciles are recorded as DISTINCT from UKMOD quintiles so nothing can alias them later. 4. Baseline identity is machine-enforced and per figure. 1.A and 1.B are changes against the no-policy-change world, which is now registered in baselines.py with its paragraph citation; 1.C is a post-policy LEVEL and keys current law. The assignment lives in the registry, is emitted into the metadata artifact, and an unregistered chart baseline raises — so a future HMT row cannot silently default to current_law. 5. The anchoring gate actually runs in CI. It was importorskip-gated on pypdf and pyyaml while CI installed only pytest, so the lane's strongest honesty guarantee never executed there. CI now installs both and the skip path is gone. The adapter also writes the trailing newline the committed metadata has, so wiring it into the no-drift build no longer dirties the tree. 6. The held-out relationship is registered with its evidence, before any numeric row can land rather than in an emergency when the first one does; and the diagnosis guidance now names the citable-known-issue gate (class + rationale + action_link) instead of saying an engine defect "may be inferred". Suite 264 passed, two builds agree on content_hash, no-drift clean, ruff format clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HuXJFVme8HRbnke2Ey3Me
|
All six addressed in
Suite 264 passed, two builds agree on |
data/lanes.json and its app/public mirror are DERIVED feeds and both sides appended a lane, so the textual conflict is not a real one — regenerated from a build rather than hand-merged. 33 lanes now, including the BE country-report lane from #82 alongside this branch's HMT distributional lane. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016HuXJFVme8HRbnke2Ey3Me
First slice of the HMT DA lane. The headline finding shapes the whole PR: HMT publishes no machine-readable decile values. The Budget 2025 "Impact on households" document is a 20-page PDF whose Figures 1.A–1.C are unlabeled chart bars; the supporting-documents page carries XLSX only for the costings tables (4.1/4.2), and no chart-data file exists. So this PR deliberately ships the external side without value claims rather than digitizing charts by eye.
What's here
sources/hmt-distributional/raw/— the real publication, fetched 2026-08-17 from the gov.uk assets URL, SHA-2569f7e68f3…480eapinned in README, source.json, the registry and the tests.data/uk/hmt_da_packages.yaml— the Budget 2025 package entry: all 30 in-scope measures verbatim from pp. 7–9, each with channel (16 tax / 9 welfare / 5 benefits-in-kind) and computability triage (3 expressible / 9 partial / 18 not_expressible, each partial naming what's missing and each not_expressible why), the published exclusions (employer NICs incidence, NLW, devolved decisions, behavioural effects…), the counterfactual (no policy changes since AB2024), horizon (2028-29) and income concept (equivalised net BHC). No invented reform dicts: expressible components reference measure families for the OBR policy costings (mode 2): Policy Measures Database + EFO 3.17 → PE-UK static counterparts #54/OBR policy costings (mode 2): registry, offline certified compute, staging, descriptive comparison #56 registry, so one measure keeps one executable home.sources/hmt-distributional/adapter.py— verifies rather than parses: SHA gate, page count, figure inventory, and every registry component title verbatim-anchored in the document text (normalized for PDF line breaks and typographic quotes), so the registry cannot drift from the publication. Emits a meta artifact recordingvalue_claims_emitted: 0and the value-availability rule. Runs clean against the vendored PDF.tests/test_hmt_da_adapter.py— raw-file identity, provenance files, the no-value-emission contract, meta honesty; registry schema checks viaimportorskip("yaml")(CI's bare-pytest env skips those five assertions; the adapter enforces the same contract on every execution).What this defers, and to what
value_availability_rulestates this as a contract).Suite: 167 passed / 5 skipped; ruff format clean.
Builds #61.
🤖 Generated with Claude Code