Skip to content

UK externals → DB ingest path (16,924 claims, five sources) - #48

Merged
MaxGhenis merged 5 commits into
mainfrom
uk/db-ingest-uk-externals
Aug 19, 2026
Merged

UK externals → DB ingest path (16,924 claims, five sources)#48
MaxGhenis merged 5 commits into
mainfrom
uk/db-ingest-uk-externals

Conversation

@vahid-ahmadi

@vahid-ahmadi vahid-ahmadi commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Part of #33 — the UK ingest path itself. (Not "Closes": the campaign-results attachment half stays open, for the reason documented below.)

What's here

  • scorecard_db/ingest_uk_externals.py — the UK analogue of the harvest adapters: maps the five UK adapter outputs (data/externals/, from DWP take-up adapter (mode 1): FYE 2024 external side #43UKMOD statistics adapter (mode 1): Country Report validation tables #47) onto ExternalScore under the same contract (fail loudly on unmapped vocabulary, tally deliberate drops, values never re-derived). Per-source stagers are individually testable, mirroring the stage_scores() pattern.
  • scorecard_db/models.py — deliberate vocabulary extensions: 7 new Metric members (taxpayer_count, average_tax_rate, average_tax_amount, unclaimed_benefit_amount, gini, income_statistic, income_share), 3 UnitConcepts (gbp, benefit_units, children), and 8 documented UK STANDARD_CONDITIONS keys (country, fy, housing_costs, poverty_line, basis, welfare_cap, quantile, per).
  • tests/test_uk_externals_ingest.py — mapping tests on synthetic rows (run on any branch) + a full round-trip integration test that self-activates once the adapter outputs merge.

Verified end-to-end

Run against the real adapter outputs (pulled from the #43#47 branches): 16,924 claims — dwp_takeup 1,638 / dwp_hbai 12,888 / uk_hmrc 1,213 / obr 259 / ukmod 758 — with zero claim_id collisions through the finish() gate, and deliberate drops tallied (1,456 DWP CI-range variants; 373 UKMOD non-primary variants — score against primaries, never transcriptions).

Honesty decisions

  • DWP take-up rates land consumed_as_target (pe-uk take-up parameters cite the same publication); HBAI is held_out under the census-spm doctrine; OBR 2024-25 outturn is consumed_as_target, forecast years held_out.
  • HMRC ready-reckoner rows are reform claims: policy_ref worlds slugged from the verbatim change description against hmrc_indexed_baseline_spring_2025, with the description kept as conditions["option"] — so each of the 75 changes × 3 years is a uniquely-keyed world.
  • UK financial years normalize to one fy label + integer end-year period; HBAI 3-year regional averages ride period_start/period_end with window_kind=annual_average.

Finding: the campaign descriptors need re-emission

The staged hmrc_reckoner_t2 family (14 PE results in sources/campaign-20260802/uk/) cannot attach yet: its descriptors under-specify. Two personal-allowance rows carry byte-identical conditions (tax_section + fy + direction_semantics) — the actual change (£100 vs 1% vs 10%) lives only in free-text pe_construction. The exactly-one-match contract in ingest_campaign.py correctly refuses this, and the repo's never-guess doctrine says the fix is re-emission with the change label, not inference. The DB claims here are keyed (option, fy) ready for that join — the integration test demonstrates the exactly-one query. The other UK families (free_joins → OBR receipts lines, obr_measures → policy costings, two_child/uprating → Resolution Foundation) additionally need their harvest claims, which are not vendored in this repo.

Sequencing

Merge after #43#47 (the module's inputs). CI is green on this branch standalone — the integration test skips itself until the externals exist.

🤖 Generated with Claude Code

@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

Three review defects fixed

1. Two different reforms shared one reform world. HMRC prints "Change standard rate by 1 percentage point" under both the VAT and the Insurance Premium Tax sections, and the policy slug was built from the description alone — so six claims shared one reform_key across worlds 14× apart in magnitude (£9.2bn vs £640m in 2027-28). A single PE compute of "+1pp VAT" would have been joined as the counterpart to the IPT claim. finish() could not catch this: the claim ids differ by program, but ReformRef is the executable descriptor the PE side computes against, and scorecard_db/README.md requires identical worlds to share a key and distinct worlds not to. The slug now carries the tax head, and both a unit test and a DB-level assertion (no reform_key spanning two programs) pin it.

2. Calibration decisions bypassed relationships.py. They were inline per-metric constants, even though that module's own docstring calls itself the canonical home and says the assignment is made "not defaulted". Added uk_relationship() there — keyed by (source, metric), because the UK families are publication-shaped rather than program-shaped, and the OBR's outturn/forecast split turns on the year. It raises on an unassigned pair instead of defaulting to held_out.

3. A derivative of a consumed target was labelled held-out. Entitled-non-recipient counts and unclaimed amounts were held_out while the take-up rate they are an exact algebraic function of (takeup = R/(R+ENR)) was consumed_as_target. If pe-uk is fitted to the rate and calibrated to the admin caseloads supplying R, it reproduces ENR by construction — an apparent validation win that the 2026-08-02 doctrine's "nor anything derived from such" already covers. The whole DWP take-up family is now consumed, including recipient counts. Where the per-parameter audit of pe-uk's provenance is still owed, the conservative direction is taken: consumed, never held_out.

Plus a doctrine gap this PR would have opened. PERMANENT_HOLDOUT_METRICS contained POVERTY_RATE but not POVERTY_COUNT — the same survey-derived quantity with the denominator multiplied back in. This ingest is the first population to carry poverty counts (6,528 HBAI rows), so without the addition a future ingest could mark them consumed and the guard would not fire. Added, with a test.

Re-verified end to end

Against the fixed adapter outputs from #43#47: 16,924 claims (dwp_takeup 1,638 / dwp_hbai 13,056 — +168 from the rows #44 recovered / uk_hmrc 1,213 / obr 259 / ukmod 758), zero reform_key collisions, zero poverty claims marked consumed. Full suite 137 passed, ruff clean.

Note for merge order: this now depends on #44's and #47's fixes for its claim counts, and #47's corrected values (32 of the wrong Table 4.7 cells previously reached the DB through this path). Re-run the ingest after those merge.

Still open from the review, deliberately not done here: upsert_scores has no delete-by-registry, so this slug change orphans the 225 previously-ingested HMRC claims in any DB built from the old code. That is the idempotency contract ingest_reform_validation.py implements and this module does not — worth its own PR rather than bundling.

@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

Position on the feed-architecture question this PR is half of (now tracked as #57): I propose #32 lands first — it is the superset (eight sources vs five, Ledger routing, and the baselines registry #13 that the mode-2 lanes #54/#56 need) — and this PR then reduces to a rebase of its three review-hardening deltas that #32 does not carry:

  1. the tax-head reform_key disambiguation (VAT/IPT collision) + the no-reform_key-spans-two-programs DB assertion,
  2. uk_relationship() in relationships.py with raise-on-unassigned (no silent held_out default),
  3. the consumed-target-derivative audit (entitled-non-recipient counts and unclaimed amounts must inherit consumed from the take-up rate they algebraically determine).

I'll do that rebase as a follow-up PR once #32 merges, and this one can close in its favour. If maintainers prefer the opposite order, those three deltas are the part that must survive either way. Either resolution un-HOLDs #50 (its reviewer parked it on exactly this adjudication).

@DTrim99

DTrim99 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Review — UK externals → DB ingest path

Strong foundation PR. I ran the ingest + the full test suite against the real data/externals/ files: all 10 tests pass, finish() raises no collisions, and values pass through verbatim (spot-checked the OBR 137273241932.22 — un-rederived). Fail-loud paths (unknown variant/metric, unassigned (source, metric)) are raised and tested; per-source stagers are individually tested. One real issue and a few should-fixes below.

Should fix

  • OBR aggregate_level / parent silently dropped → double-count hazard (ingest_uk_externals.py:422-447, stage_obr). The OBR file carries aggregate_level (component/subtotal/total) + parent, but the stager ignores both — all 259 rows land as sibling benefit_cost claims (total_welfare, dwp_social_security, pension_credit, …) with no marker, so any consumer summing OBR benefit_cost by FY double-counts. Add aggregate_level to conditions (and register it), or filter to leaf components.
  • No require_fields gate in the UK ingest. The US harvest path fails loud on unhandled staged fields (harvest.py:41-48); the UK path has no equivalent, which is exactly why the OBR columns above pass unnoticed. Porting that gate would catch this class of drop going forward.
  • component condition key used but not registered (ingest_uk_externals.py:196,202 emit conditions["component"], but it's missing from STANDARD_CONDITIONS, models.py:115). Harmless at runtime (the frozenset is documentary) but breaks the "keys adapters MUST use" registry contract.
  • Headline count mismatch: the PR says 16,756 claims; the ingest actually produces 16,924 (dwp 1638 / hbai 13056 / hmrc 1213 / obr 259 / ukmod 758). Worth reconciling the number.

Minor

  • HBAI poverty counts for children/pensioners/working-age all get UnitConcept.PERSONS with the subgroup riding in subgroup only — no collision, but the unit label is imprecise.

Heads-up on merge order: #52 depends on this PR's UnitConcept.GBP, and #65 also adds UnitConcept.GBP to models.py — those two will collide, worth deconflicting.

Reviewed with Claude Code assistance.

@vahid-ahmadi vahid-ahmadi changed the title UK externals → DB ingest path (16,756 claims, five sources) UK externals → DB ingest path (16,924 claims, five sources) Aug 19, 2026
@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

All four should-fixes addressed in 0a8bc91 (plus a merge of main so the #43#47 adapters live under this branch and the integration tests run against regenerated outputs):

  1. OBR hierarchy: stage_obr now carries aggregate_level on every claim and parent on every non-total row — the double-count hazard is marked on the rows themselves. Tested (test_obr_hierarchy_lands_in_conditions).
  2. require_fields gate: _load() enforces the harvest.py contract against a per-file known-field registry; an unhandled adapter column now raises (test_load_gate_rejects_unhandled_adapter_fields). The HMRC sign-convention columns are declared known explicitly.
  3. Condition registry: component, aggregate_level, parent registered in STANDARD_CONDITIONS with comments; membership tested.
  4. Count reconciled: the ingest produces 16,924 (dwp 1638 / hbai 13056 / hmrc 1213 / obr 259 / ukmod 758) — title and this thread now say so; 16,756 was a stale pre-rerun figure.

On the minor: HBAI subgroup counts keep UnitConcept.PERSONS with the cut in subgroup — changing the unit label would churn claim ids for a labeling nicety, so leaving as-is unless you feel strongly. On the UnitConcept.GBP collision with #65: acknowledged — whichever merges second rebases; the enum addition is identical text so it's a trivial conflict either way.

@DTrim99

DTrim99 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Re-review — main findings addressed, two loose ends ✅⚠️

Applied the diff locally and ran it: all 13 tests pass and the full ingest produces exactly 16,924 claims (dwp 1638 / hbai 13056 / hmrc 1213 / obr 259 / ukmod 758).

Addressed:

  • OBR aggregate_level/parentstage_obr now emits both into conditions, with test_obr_hierarchy_lands_in_conditions, closing the double-count hazard.
  • require_fields gate_load now fails loud on unhandled adapter fields against per-file _KNOWN_FIELDS.
  • component key — now registered in STANDARD_CONDITIONS, asserted by a test.

Two loose ends:

Substance looks good.

@DTrim99

DTrim99 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Holding a full approval only on the two loose ends from my re-review: reconcile the PR body to 16,924 (code and title already agree), and coordinate the models.py GBP = "gbp" line with #65 so whichever merges second doesn't conflict. Substance is solid — happy to approve once those are squared away.

@vahid-ahmadi

Copy link
Copy Markdown
Contributor Author

Both squared away: the PR body was reconciled to 16,924 earlier today (the comment likely crossed with the edit — it reads 16,924 now, matching title and code). On the GBP line: proposed sequence is this PR merges first (it is the base of the #52 stack), and #65 rebases immediately after, dropping its duplicate GBP = "gbp" line — committed to on #65 as well.

MaxGhenis and others added 2 commits August 19, 2026 16:08
relationships.py rebuilt from the literal consumption surfaces read at
the certified pins (pe-uk-data@dd68c73 targets/sources/{obr,dwp,
hmrc_spi}.py; policyengine-uk takeup.yaml parameters): OBR consumed =
the 12 named Table 4.9 forecast lines (outturn cells must route to
Ledger and RAISE here); PC take-up = seed_source (parameter cites the
FYE-2020 edition); HB take-up = held_out (engine's stated
by-definition-1.0 design; uk#1813 comparator); HMRC liabilities =
seed_source (shared SPI 3.6/3.7 base), reckoner = held_out; unknown
combinations raise. models.py = main's baseline machinery + pr-48's UK
vocabulary (3-way applied). Remaining: alias registry, Ledger router,
atomic ingest, exact accounting, tests, regen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Evidence-based rebuild on the #71 schema foundation. Every relationship
now traces to a consumption surface read at the certified pins
(2026-08-19): pe-uk-data@dd68c73 targets/sources/{obr,dwp,hmrc_spi}.py
and policyengine-uk's takeup.yaml parameters.

- Ledger routing (boundary rule 2026-08-02): 749 admin outturn cells —
  DWP recipient counts/amounts claimed (546), HMRC 2023-24 SPI-outturn
  liabilities (166), OBR FY2024-25 column (37) — route to
  data/ledger/uk_admin_outturns.jsonl with deterministic fact ids and
  the consuming pin named where pe-uk-data literally reads the cell.
  The relationship registry RAISES if an outturn ever reaches it.
- Relationships keyed exactly: OBR consumed = the 12 named Table 4.9
  forecast lines (adapter vocabulary, pe-uk-data labels quoted); PC
  take-up family = seed_source (the engine parameter cites the FYE-2020
  edition); HB = held_out (engine's stated by-definition-1.0 design;
  uk#1813 comparator); HMRC levels = seed_source (shared SPI 3.6/3.7
  base), reckoner = held_out; UKMOD = peer-held. Distribution:
  78 consumed / 1,836 seed / 14,261 held.
- Closed identity registry (scorecard_db/uk_aliases.py): (source, axis,
  value) -> canonical, raising on unknowns; HBAI/UKMOD poverty lines
  canonicalized to (basis, percent, median-vintage) so HBAI-relative
  and UKMOD-60 share a world deliberately; winter_fuel alias; explicit
  DISTINCT pairs (HB-pensioners vs HB; benefit_units vs families).
- Units repaired: gbp value_kind (never 'usd'), GBP_PER_WEEK /
  GBP_PER_MONTH / INDEX_0_1 unit concepts (Gini is an index).
- Atomic: stage + validate everything, then ONE transaction replaces
  the five sources wholesale; rollback verified by injection.
- Exact accounting: 16,175 claims + 749 ledger + 1,829 drops = every
  adapter row (the adjudication's corrected 16,924); drift raises.

Registry: 52 worlds (+hmrc_indexed_baseline_spring_2025, gate-caught).
DB 54.3 MB. Suite: 213 tests (21 UK: triangles, ledger routing,
rollback injection, accounting drift, pinned integration).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the uk/db-ingest-uk-externals branch from 0a8bc91 to 2eb5576 Compare August 19, 2026 20:19
@MaxGhenis

Copy link
Copy Markdown
Contributor

Repaired against the adjudication's eight blockers and re-pushed (dual-gate flow, same as #16/#24): every relationship now traces to a consumption surface read at the certified pins — pe-uk-data@dd68c73's targets/sources modules and policyengine-uk's take-up parameters. Headlines: 749 admin outturn cells route to data/ledger/uk_admin_outturns.jsonl per the boundary rule (with consuming pins named); OBR consumption is per-line (the 12 Table 4.9 rows pe-uk-data reads), not per-basis; PC take-up flipped to seed_source (the parameter cites the FYE-2020 edition), HB held out on the engine's stated design; a closed identity registry (uk_aliases.py) canonicalizes cross-source axes (HBAI-relative ≡ UKMOD-60 deliberately; explicit DISTINCT pairs) and raises on unknowns; units repaired (gbp value kinds, per-week/per-month/index concepts); ingestion is one transaction with rollback verified by injection; accounting is exact (16,175 + 749 + 1,829 = 16,924, drift raises). Suite 213. Note: the committed DB is 54.3 MB — past GitHub's 50 MB warning, still under the 100 MB hard limit; the storage decision (LFS/shard/untracked) is flagged for Max before the long-tail sources land.

…d-to-end

All ten findings:
1. Period convention: the branch's stale start-year comment contradicted
   the repo contract — main keys FY claims by END year (RV parser:
   'period is the ending year'; live claims: CT notes 2027, CA AB2591
   FY2027-28 -> 2028). Comment corrected; _fy() was already right.
   Engine start-year periods are result-side provenance, never claim
   identity.
2. HMRC: the workbook's own description sheet marks every year through
   2023-24 as outturn — the full 1990-91..2023-24 span (490 facts) now
   routes to Ledger; claims 723, seed 498. Accounting: 15,851 claims +
   1,073 ledger + 1,829 drops = 16,924, drift-gated.
3. Zero GBP-as-usd remains (reckoner + OBR value kinds fixed; verified
   in the DB).
4. HBAI absolute line: anchor-aware per the Notes sheet — poverty_line
   'absolute_60_fixed_median' + poverty_line_anchor fye_2011 (5,632) /
   fye_2025 (480) / mixed_fye2011_fye2025 (416 straddling windows,
   explicit never averaged).
5. Reckoner rows keep their machine-readable orientation
   (sign_convention/direction/change_direction in conditions).
6. Ledger facts carry aggregate_level + parent (the anti-double-counting
   hierarchy).
7. The identity gate is end-to-end: units validate through canon() in
   every stager, reckoner geography/program canonicalized, Ledger
   identities canonicalized; obr NI aliases to Northern Ireland.
8. ingest() runs the deliberate-registration baseline gate.
9. Equivalisation conditions per the vendored methodology quotes: HBAI
   modified_oecd / modified_oecd_companion_ahc; UKMOD distribution,
   gini and poverty rows bhc + modified_oecd (the three-way triangle is
   well-posed by construction).
10. Lanes unified: DB and feed share the adapter lane ids at 'ingested'.

Suite: 213. HBAI adapter's rounded-text precision (finding 9b) is an
upstream #44 adapter defect — follow-up, not this ingest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits August 19, 2026 17:14
… persistence

Sol's round-2 probes found two mechanism defects, both fixed:

1. HMRC reckoner rows bypassed the closed unit registry (the
   revenue_effect branch returns before the canon call). Unit
   validation now runs at the top of the loop, before ANY branch;
   regression test injects an unknown-unit reckoner row.

2. register_baselines ran AFTER the claims transaction committed —
   it validated the DB but did not guard it (an unregistered-baseline
   probe raised with all 15,851 replacement claims already committed).
   Split into register_baselines_txn (bare executes, caller owns the
   transaction — nested `with conn:` would commit early, which is why
   the txn form may never open one) + the standalone wrapper. ingest()
   now runs delete + insert + registration gate + lane rows in ONE
   transaction: an unregistered world rolls back everything. Rollback
   test: poison claim -> raise -> DB byte-identical, poison world
   never registered.

Also per the round-2 pass:
- equivalisation + poverty_line_anchor promoted into
  STANDARD_CONDITIONS; semantic pins added to the integration test
  (anchor distribution 5,632/480/416, every mixed anchor is a window,
  equivalisation tracks housing costs on all 13,056 HBAI rows, UKMOD
  gini/income all BHC + modified-OECD, none vacuous) — count-only
  accounting cannot detect semantic condition loss.
- Stale accounting comment corrected: 15,851 + 1,073 = 16,924
  admitted; + 1,829 drops = 18,753 adapter rows.
- Period comment narrowed: END-year is the LIVE claim convention;
  archived harvest NOTES staged some sources by start year, so
  archive-built ingests (#52) must translate at staging time.
- Two pre-existing unused imports removed (ruff check; CI only gates
  format).

Committed DB unchanged: this round changes validation and transaction
mechanics, not staging semantics. Suite: 215.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sol round 3 confirmed both production blockers resolved but caught the
UKMOD semantic pin being unsound: json_extract of a MISSING key is SQL
NULL, and NULL <> 'x' is not true, so a bare <> counted zero mismatches
even with both load-bearing conditions stripped from all 64 rows.
Mutation-probed: the old guard caught 1/3 defect classes (wrong value
only); the NULL-safe form catches 3/3 (stripped either/both + wrong).

Also per the round:
- mixed-anchor guard asserts non-null period_start/period_end, not
  just the window_kind label (1/1 on a labeled-but-columnless probe).
- rollback test now compares every claim row (claim_id, source, value,
  conditions, baseline_key), not source counts, and its docstring says
  "logically identical" — which is what is actually asserted.

Test-only delta. Suite: 215.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit 63cfdac into main Aug 19, 2026
2 checks passed
@MaxGhenis
MaxGhenis deleted the uk/db-ingest-uk-externals branch August 19, 2026 21:32
MaxGhenis added a commit that referenced this pull request Aug 19, 2026
…es, one transaction

Rebased from the pre-repair #48 branch onto main (the two original
harvest commits cherry-picked intact) and adapted to the foundation
contract, fixing four defects:

1. value_kind was "usd" on GBP rows -> "gbp" (sol's #48-round-1 class).
2. The FRR reform rows rode the DEFAULT baseline (= current law), but
   the costing's own counterfactual is the 25%-cap law the FRR
   replaced — today's current law INCLUDES the FRR, so a PE result
   computed against it would be a cross-world comparison. The rows now
   carry the registered pre_frr_uc_deductions world (baselines.py
   entry with AB2024/DWP-PR provenance), mirrored in
   conditions["baseline_policy"].
3. The £420 average annual gain was bare GBP — a per-household average
   a query could sum. New GBP_PER_HOUSEHOLD unit concept (mirror of
   USD_PER_HOUSEHOLD, same value_kind rule).
4. ingest() was a bare upsert with no gates. Now the foundation shape:
   ONE transaction (delete both sources + insert + the deliberate-
   registration gate + lane row — commit or nothing), lane-feed mirror
   after commit.

Also: period asserted = fy END year per row (the live claim
convention; staged rows verified clean, all 8); identity values
(program/subgroup/geography/unit) route through the closed registry
(hm_treasury + dwp registered in uk_aliases); measure + fiscal_measure
promoted into STANDARD_CONDITIONS; staged unit labels validated
against the expected concept per metric.

Committed DB ingested: 58,461 -> 58,469 claims; registry 52 -> 53
worlds; uk-deductions-frr lane 'ingested' in DB + feed. Suite: 224.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Aug 20, 2026
Rebase completion (the branch forked before the whole UK arc — #43-#48,
#52, #71, #72 all landed under it):

- uk-deductions-frr (merged today, unknown to this branch) was the one
  committed lane without a country tag -> UK.
- The UK ingests' appended lane metas now carry country explicitly
  (ingest_uk_externals' five lanes + ingest_uk_deductions' one), so a
  fresh-feed append passes the new sync_lane_feed guard and never files
  a UK lane under the app's missing-key US default.
- app/public/data copies refreshed from data/ (they had drifted to a
  270-row populations.json vs 284) and pinned: new test asserts the
  committed copies byte-match data/, and a second asserts every
  committed lane carries US|UK — the two drift classes this rebase
  surfaced.

Suite: 241 python + 3 bun; oxlint + vite build clean; committed DB
byte-stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Aug 20, 2026
…uard (#73)

The #44 follow-up from the #48 gate: cells now carry the workbook's unrounded
office:value (the 16% headline is 0.15911572...), with the display text kept
only as a rounded-rendering consistency guard that aborts mispaired cells.
Headline checks become rounds-to and are mutation-tested non-vacuous.
Regenerated externals + DB: 12,342 dwp_hbai values changed, nothing else —
accounting, anchors, conditions, claim ids all identical; zero rate rows left
at exact 2dp.

Dual gate: fable + sol, one round (sol reparsed the vendored ODS and matched
all 13,056 rows exactly).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
vahid-ahmadi pushed a commit that referenced this pull request Aug 20, 2026
…e the country config with the executed-baseline provenance

The promised one-line GBP resolution (this branch was second to land, so
its duplicate UnitConcept.GBP goes; #48's commented one stays). The
rebase also reconciles #71's executed-baseline machinery with the
per-country config: ENGINE_VERSIONS lives inside COUNTRIES["US"] with
the back-compat alias, and _obbba_results carries both #71's position
argument (chain_pos construction) and the country/run_prefix parameters.

Suite: 248 passed, 6 skipped; ruff format clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

3 participants