You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
populace/build/ledger_targets.py — LedgerTargetMapping / LedgerTargetReference / compile_ledger_target_references (identity-only value resolution; uprating/aging as populace-side declarations)
populace/build/us/fiscal_target_references.json — 11 references, including counterfactual targets via metadata.kind: neutralize_variable
tools/build_us_fiscal_refresh_release.py — --ledger-facts + --ledger-facts-sha256 / --ledger-manifest-sha256 pinning into the release manifest
The UK has exactly one Ledger consumer today (firm generation, uk_firm_source_data_from_ledger_facts) plus profile-aware local metrics (populace.build.uk.local_targets, #158). There is no UK household-sector target compilation from Ledger. This issue adds it, with a parity gate proving the Ledger-backed surface reproduces what production calibrated against.
Doctrine boundaries this issue must respect: Ledger owns facts; populace owns the active subset (porting a vintage never activates it), model-variable mapping, period alignment/uprating declarations, and all reconciliation/aging. value_operation stays identity.
Work items
populace/build/uk/uk_target_references.json + loader — mirror of the US shape: one LedgerTargetReference per active UK target (entity, measure/filter mapping, family, period, selector by record_set_id/measure). Counterfactual and cross-tab targets (two-child limit, UC family-type splits, benefit cap) use metadata.kind + a UK metric-provider registry, following the JCT neutralize_variable pattern — the reference declares, the provider computes model-side.
UK mapping module (populace/build/uk_runtime/ledger_targets.py or equivalent) — LedgerTargetMapping for UK concepts: measure_by_concept / measure_by_source_record_id → policyengine-uk variables, filter_by_domain, entity_by_ledger_entity (person/benunit/household), family_by_source_name (obr/dwp/hmrc/ons/slc/voa/…). Unmapped facts are reported as unsupported, never silently dropped.
Profile consumption — load uk_national (and keep uk_local_geography consumption aligned) through the existing profile loader; compile to TargetSpec rows via the mapping.
Compile-parity gate (the acceptance instrument):
Fixture A (production surface): the 149 rows in calibration_diagnostics.json of release populace-uk-2023-dd68c73-4aa4b14-20260619T023711Z (policyengine/populace-uk-private) — every (name, period, value) must resolve from the pinned Ledger artifact.
Fixture B (current registry):policyengine-uk-data's registry compile at pinned ref ebf733c for the current calibration year — Ledger-backed compile must match, with every intentional difference (vintage moved, target dropped/renamed) enumerated in a signed exclusion list. No silent gaps in either direction.
Gate output follows the existing GateResult conventions; wire into the UK release preflight beside the input-coverage gates.
Release-manifest provenance — UK build/release manifests record ledger_facts_sha256 (+ manifest sha), same as the US fiscal refresh, so a release pins exactly which Ledger data resolved its targets.
Running a calibration. The UK national build performs no calibration today by design (tools/build_uk_national_dataset.py: the staging path "performs no calibration and therefore has no real target-surface or target-fit evidence"). Wiring the compiled registry into a calibration seam is Calibrate the UK national build from Ledger-backed targets #623.
Any Ledger-side authoring.
Acceptance
uk_target_references.json compiles through compile_ledger_target_references against a hash-pinned UK consumer artifact with zero unsupported-fact surprises (unsupported list is empty or explicitly signed).
Parity gate green on Fixture A; Fixture B differences fully enumerated.
A UK release manifest carries the Ledger pin fields.
CGT specs resolve from Ledger with unchanged downstream coverage requirements.
Item 1 is done by #707 (per the scope adjudication): the re-homed contract uk/uk_national_targets.json (186 declarations, exact 651-row parity closure: 609 mapped + 42 excluded + 3 signed unmapped declarations) plus the typed uk/target_references.jsonfirst active subset — 13 references at period 2023 — and the ledger_targets.py selector extension (dimension_values strict-typed pins, list-form dimensions, list membership, record_set_id/groupby_dimension aliases). One resolver now serves the US, Belgian, and UK surfaces (#708).
Findings that reshape the remaining items:
Fixture-B year decision needed: the incumbent's effective surface at its current calibration year 2025 is 636 rows, not 651 — the 15 dwp/uc/two_child_limit/* rows carry only {2026} values and _resolve_value refuses forward extrapolation. Recommend Fixture B at 2026 → 651 (the full surface; avoids codifying the degraded year) with an explicit 636@2025 caveat. The contract's registry_parity.accounting_notes records this.
Never trust a live incumbent run as a parity instrument: create_target_matrix swallows per-target failures with a bare except + warning (no column-count assertion anywhere), and its surface is already degraded at the pin — the salary-sacrifice IT-relief source CSV is 410-Gone, silently suppressing rows whose dispatch branches remain as dead code. Fixture B must compile from the registry definitions, not from a network-dependent run.
Multi-fact sum resolution is the largest resolver gap (23 deferred targets): band/groupby targets are sums over matched fact sets (resolution_defaults.operation: "sum"); the shared compile path resolves exactly one fact per reference. The CGT totals also expose that _selector_period_invariant_key does not strip UK record-set year spellings (37 same-series year-rows fail to collapse).
Country-level geography ambiguity (49 deferred targets): UK-wide (K02000001) and the four constituent countries share level country; per-target geography_id pinning is the adjudication.
No fact ≤ 2023 (101 deferred targets): all OBR EFO lines and the 2025/2026-vintage DWP/VOA/Scotgov/SLC families. Activation grows via this issue's uprating/period declarations (uprating_index fields exist on LedgerTargetReference) or with the WS-E/F year move.
Depends on: PolicyEngine/chronicle#132 (parity surface) for the gate fixture; PolicyEngine/chronicle#133 + PolicyEngine/chronicle#134 and the
uk_nationalprofile for full coverage. Consumption code can land behind fixtures before all waves finish.Context
Populace's Ledger consumption layer is built and proven on the US side:
populace/build/ledger_artifact.py— hash-verified consumer-artifact loading (duck-typed, stdlib-only)populace/build/ledger_targets.py—LedgerTargetMapping/LedgerTargetReference/compile_ledger_target_references(identity-only value resolution; uprating/aging as populace-side declarations)populace/build/us/fiscal_target_references.json— 11 references, including counterfactual targets viametadata.kind: neutralize_variabletools/build_us_fiscal_refresh_release.py—--ledger-facts+--ledger-facts-sha256/--ledger-manifest-sha256pinning into the release manifestThe UK has exactly one Ledger consumer today (firm generation,
uk_firm_source_data_from_ledger_facts) plus profile-aware local metrics (populace.build.uk.local_targets, #158). There is no UK household-sector target compilation from Ledger. This issue adds it, with a parity gate proving the Ledger-backed surface reproduces what production calibrated against.Doctrine boundaries this issue must respect: Ledger owns facts; populace owns the active subset (porting a vintage never activates it), model-variable mapping, period alignment/uprating declarations, and all reconciliation/aging.
value_operationstaysidentity.Work items
populace/build/uk/uk_target_references.json+ loader — mirror of the US shape: oneLedgerTargetReferenceper active UK target (entity, measure/filter mapping, family, period, selector byrecord_set_id/measure). Counterfactual and cross-tab targets (two-child limit, UC family-type splits, benefit cap) usemetadata.kind+ a UK metric-provider registry, following the JCTneutralize_variablepattern — the reference declares, the provider computes model-side.UK mapping module (
populace/build/uk_runtime/ledger_targets.pyor equivalent) —LedgerTargetMappingfor UK concepts:measure_by_concept/measure_by_source_record_id→ policyengine-uk variables,filter_by_domain,entity_by_ledger_entity(person/benunit/household),family_by_source_name(obr/dwp/hmrc/ons/slc/voa/…). Unmapped facts are reported as unsupported, never silently dropped.Profile consumption — load
uk_national(and keepuk_local_geographyconsumption aligned) through the existing profile loader; compile toTargetSpecrows via the mapping.Compile-parity gate (the acceptance instrument):
calibration_diagnostics.jsonof releasepopulace-uk-2023-dd68c73-4aa4b14-20260619T023711Z(policyengine/populace-uk-private) — every (name, period, value) must resolve from the pinned Ledger artifact.policyengine-uk-data's registry compile at pinned refebf733cfor the current calibration year — Ledger-backed compile must match, with every intentional difference (vintage moved, target dropped/renamed) enumerated in a signed exclusion list. No silent gaps in either direction.GateResultconventions; wire into the UK release preflight beside the input-coverage gates.Release-manifest provenance — UK build/release manifests record
ledger_facts_sha256(+ manifest sha), same as the US fiscal refresh, so a release pins exactly which Ledger data resolved its targets.CGT single-homing — retire the hand-maintained
UK_CGT_TARGET_SPECSinpopulace/build/uk_runtime/fiscal_targets.pyonto references against the UK source packages, wave 2: national fiscal/benefit families + uk_national target profile chronicle#133hmrc/cgt_statisticspackage (keep the coverage-requirement machinery; only the values' home changes).Explicitly out of scope
tools/build_uk_national_dataset.py: the staging path "performs no calibration and therefore has no real target-surface or target-fit evidence"). Wiring the compiled registry into a calibration seam is Calibrate the UK national build from Ledger-backed targets #623.Acceptance
uk_target_references.jsoncompiles throughcompile_ledger_target_referencesagainst a hash-pinned UK consumer artifact with zero unsupported-fact surprises (unsupported list is empty or explicitly signed).Status update (2026-08-18, from #707)
Item 1 is done by #707 (per the scope adjudication): the re-homed contract
uk/uk_national_targets.json(186 declarations, exact 651-row parity closure: 609 mapped + 42 excluded + 3 signed unmapped declarations) plus the typeduk/target_references.jsonfirst active subset — 13 references at period 2023 — and theledger_targets.pyselector extension (dimension_valuesstrict-typed pins, list-formdimensions, list membership,record_set_id/groupby_dimensionaliases). One resolver now serves the US, Belgian, and UK surfaces (#708).Findings that reshape the remaining items:
dwp/uc/two_child_limit/*rows carry only {2026} values and_resolve_valuerefuses forward extrapolation. Recommend Fixture B at 2026 → 651 (the full surface; avoids codifying the degraded year) with an explicit 636@2025 caveat. The contract'sregistry_parity.accounting_notesrecords this.create_target_matrixswallows per-target failures with a bareexcept+ warning (no column-count assertion anywhere), and its surface is already degraded at the pin — the salary-sacrifice IT-relief source CSV is 410-Gone, silently suppressing rows whose dispatch branches remain as dead code. Fixture B must compile from the registry definitions, not from a network-dependent run.resolution_defaults.operation: "sum"); the shared compile path resolves exactly one fact per reference. The CGT totals also expose that_selector_period_invariant_keydoes not strip UK record-set year spellings (37 same-series year-rows fail to collapse).country; per-targetgeography_idpinning is the adjudication.uprating_indexfields exist onLedgerTargetReference) or with the WS-E/F year move.