Skip to content

HMT distributional analysis (mode 2, distributional): Budget 2025 external side — chart-only scope, honestly - #68

Open
vahid-ahmadi wants to merge 2 commits into
mainfrom
uk/hmt-distributional
Open

HMT distributional analysis (mode 2, distributional): Budget 2025 external side — chart-only scope, honestly#68
vahid-ahmadi wants to merge 2 commits into
mainfrom
uk/hmt-distributional

Conversation

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

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-256 9f7e68f3…480ea pinned 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 recording value_claims_emitted: 0 and 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 via importorskip("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

Suite: 167 passed / 5 skipped; ruff format clean.

Builds #61.

🤖 Generated with Claude Code

…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>
@DTrim99

DTrim99 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

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 hmt_da_packages.yaml sits inside a verbatim HMT measure title (rates, dates, "450,000 children" — HMT's own text), never a per-decile £/% chart read, and value_claims_emitted: 0 is asserted in tests with no emit path for value rows by construction.

Verified:

  • SHA-256 pin is consistent — one 64-hex hash across meta.json:10, yaml:63, README:394, test:465 (and source.json deliberately omits it, which test_lane_provenance asserts). It's actually checked: adapter.py:319-321 recomputes-and-compares and test:470 recomputes the raw file hash.
  • Counts sum both ways — channel 16 tax / 9 welfare / 5 BIK = 30; triage 3 expressible / 9 partial / 18 not_expressible = 30 (both match meta.json).

Should address

  • The strongest honesty guarantees aren't in CI. The verbatim-title, figure-presence, and "20-page" checks live only in adapter.py (needs pypdf) and run outside the default pytest path — so tests/test_hmt_da_adapter.py doesn't enforce that titles are actually verbatim or figures actually present; only a manual uv run of the adapter does. A pypdf-gated test (importorskip) mirroring the registry-schema test at :499 would catch drift in CI rather than locally.

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>
@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

Addressed in the latest commit:

  • Honesty guarantees now in pytesttest_document_anchoring_in_ci_when_pypdf_available loads the adapter and enforces the verbatim-title anchoring (all 30 components), figure anchors, and the 20-page identity against the real committed PDF, gated with importorskip("pypdf")/importorskip("yaml") exactly like the schema test. Verified passing locally under uv run --with pypdf --with pyyaml (6/6 in the file); it skips in CI's bare env — same limitation as the yaml-gated test, and the reason the adapter's run() remains the local backstop. If CI ever grows pypdf/pyyaml, the guarantee turns on with no further change.
  • meta.json trailing newline added.

Full suite: 167 passed / 6 skipped; ruff clean. The unverified measure_family pointers stay as-is per your note — they resolve when the #54 registry merges.

@DTrim99

DTrim99 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Re-review — addressed ✅

The pypdf-gated CI test I asked for is in: test_document_anchoring_in_ci_when_pypdf_available importorskips pypdf+yaml and enforces all three guarantees in CI — the 20-page identity, every FIGURE_TITLES anchor present, and verbatim titles (empty unanchored set). Skips gracefully in a bare env with the adapter run as local backstop.

SHA-256 pin, value_claims_emitted: 0, and the 16/9/5 = 30 / 3/9/18 = 30 counts all still hold. Nothing left from my side.

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed — addressed: the pypdf-gated CI test now enforces the verbatim/figure/20-page guarantees, and the SHA pin + counts still hold. Approving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants