Skip to content

Eurostat pilot calibration packages: BE, DE, FR - #168

Merged
MaxGhenis merged 1 commit into
mainfrom
eurostat-pilot-packages
Aug 20, 2026
Merged

Eurostat pilot calibration packages: BE, DE, FR#168
MaxGhenis merged 1 commit into
mainfrom
eurostat-pilot-packages

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

First EUROMOD-country calibration facts through the provenance-classed pipeline: gov_10a_taxag tax revenue and spr_exp_func ESSPROS expenditure as administrative targets; ilc_li02 AROP and ilc_di01 income deciles ingested as survey_aggregate (EU-SILC), evaluation-only by class per the calibration doctrine — the journal convention applies (no "store"). Real API bytes fetched and hash-pinned (three offline-guessed queries corrected against live dataset structures: spr_exp_sum retired → spr_exp_func; SILC dimension names). Implemented by GPT-5.6 Sol, reviewed and integrated with live fetches by the surface owner.

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Rebased over the wave-3 and county merges (#185) and completed the production integration this PR was missing: the branch had fetched and hash-pinned real Eurostat API bytes for all four datasets, but the db/data manifests still wired the packages to the *_test_fixture.json sentinels, so fixture-labeled facts would have entered the bundle.

Now flipped to production end to end: manifests select the real artifacts (real sha256/size, content-addressed R2, official dataset labels from the JSON-stat cubes themselves), the sentinel files are deleted, and the fixture-marker test is inverted into a production guard (manifest-selected artifact must be the real dataset file, hash-match its pin, carry ESTAT source, and contain no fixture markers). Added a publisher-value spot test: D2 taxes on production and imports, 2023, decoded independently from the raw cube - BE 72,825.5 / DE 428,710 / FR 446,580 MIO_EUR (x1,000,000 to eur facts). The two Eurostat fetch-manifest lifecycle branches are now strict digest checks, and the Eurostat fetch manifest moved to FETCH-MANIFEST-EUROSTAT.json (FETCH-MANIFEST.json is the county workflow from #185). Merged-bundle pins re-measured over the union; SOL-D2B scaffolding notes removed from the tree.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis force-pushed the eurostat-pilot-packages branch from 6add75d to f00c5a0 Compare August 20, 2026 05:32
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Second adversarial review round (gpt-5.6-sol, ultra) independently decoded all 108 cube positions - 107 exact, and its findings are now fixed:

  1. Fixture identity in fact keys (high). Package vintages still carried *_test_fixture / *_live_shape_fixture, ilc_di01 kept fixture sheet names, and two notes claimed synthetic values - all propagating into source/fact identity keys. Purged everywhere (vintages now eurostat_<dataset>_<year>, sheet names are artifact stems, notes factual), and a new guard test scans every eurostat package spec, manifest, and artifact case-insensitively for fixture/sentinel/synthetic markers.

  2. Binary-float value mutation (high). spr_exp_func BE disability: publisher lexeme 16448.06 MIO_EUR emitted as 16448060000.000002 via float multiply in _scale_value. Scaling now goes through Decimal - publisher lexemes survive exactly - and the spot test uses exact integer equality (approx would have masked it), pinning this exact case.

  3. ESSPROS entity narrowing (medium). Facts claimed entity: government, but Eurostat defines ESSPROS over public and private schemes with the scheme as statistical unit. Added social_protection_scheme to the entity vocabulary and reassigned all 27 facts.

  4. EU-SILC universe and income reference (medium). Both survey packages now record the universe (private households, institutions excluded, France excluding Mayotte, per the ilc_sieusilc reference metadata) and a precise period_coverage (basis survey_reference, income reference span 2023-01-01..2023-12-31 behind the 2024 survey label - the previous-calendar-year rule for BE/DE/FR), replacing a vague lag note.

  5. Stale SOL-D2B-API-FIX.md scaffolding (dead paths, pin-overwrite instructions) removed from the tree.

Sol's round also independently confirmed: all artifact hashes/sizes match across fetch manifest, db manifests, packages, and R2 URIs; provenance classes defensible; rebase unions intact; bundle arithmetic exact.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis force-pushed the eurostat-pilot-packages branch from f00c5a0 to b7f1b9e Compare August 20, 2026 06:24
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Round-3 review confirmed all six prior findings resolved but blocked on the Decimal scaler's blast radius: full-Decimal scaling changed 32 non-Eurostat values across five packages (float(Decimal) can land on an adjacent double for non-integral results - e.g. a Welsh value shifted from 708199216.38 to 708199216.3799999).

Rescoped the fix to the actual mutation class: Decimal now decides integrality only. When the true decimal product is a whole number the scaler emits the exact int; every non-integral product returns the pre-existing binary result bit-for-bit. Proven by a full five-package fact diff against the pre-change tree: the changed set collapses from 32 to exactly 4, each an integral publisher value that binary multiplication had emitted with float dust - two SLC recipient headcounts (1028438.0000000001 -> 1028438; 16335.999999999998 -> 16336), Welsh W06000005 council tax income (131972546.99999999 -> 131972547), and the Scottish carer support payment (520700000.00000006 -> 520700000). OBR is untouched. All four corrections are pinned as regressions in tests/test_scale_value.py alongside direct unit coverage of the scaler (exact-int case, bit-preservation of non-integral products, passthroughs, rejections).

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis force-pushed the eurostat-pilot-packages branch from b7f1b9e to 2feebc7 Compare August 20, 2026 06:56
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Round-4 review found the regression coverage did not discriminate: the rejected all-Decimal scaler passed every new test (verified - my chosen "bit-preservation" cases all land on the same double under both implementations, and the four integral pins agree under both).

Replaced with coverage that fails under the rejected implementation by construction, verified with a falsification loop (apply all-Decimal -> 2 tests fail; revert to scoped -> all pass):

  • Unit pairs where binary multiplication and float(Decimal) provably pick different adjacent doubles (e.g. 247322.9728 x 1000: binary 247322972.79999998 vs all-Decimal 247322972.8), pinned to the binary reprs.
  • Fact-level pins across OBR expenditure/receipts, SLC, and Welsh council tax, drawn from a complete both-implementations five-package diff (28 facts differ; 7 representative ones pinned with the all-Decimal counterpart documented per pin) - including the Welsh 708199216.38 -> .3799999 case from the round-3 report and an SLC recipients value whose upstream lexeme is genuinely non-integral (1117591.0000000002), which the scaler must NOT round to a clean count.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis force-pushed the eurostat-pilot-packages branch from 2feebc7 to cb5ee03 Compare August 20, 2026 07:25
@MaxGhenis
MaxGhenis merged commit 9553be0 into main Aug 20, 2026
2 checks passed
@MaxGhenis
MaxGhenis deleted the eurostat-pilot-packages branch August 20, 2026 08:23
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.

1 participant