Skip to content

Carry the geography ladder by default; gate releases on it#289

Merged
MaxGhenis merged 1 commit into
mainfrom
geo-ladder-default
Jul 2, 2026
Merged

Carry the geography ladder by default; gate releases on it#289
MaxGhenis merged 1 commit into
mainfrom
geo-ladder-default

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Follow-up to #277 (populace #275). The ladder shipped opt-in: nothing stopped a base or a release from being built without the geography spine — the silent-degradation family (#225's everyone-is-a-citizen, #34's NYC-zero) with a new face. Carrying the ladder is now the default at both enforcement points:

  • Base builder (tools/build_us_puf_support_base.py): omitting --block-ladder-artifact is an error; diagnostic builds opt out explicitly with --without-block-ladder, and the opt-out is recorded in the build summary.
  • Release export (l0_refit_export): the geography spine (state_fips, congressional_district_geoid, and the seven ladder columns) joins US_RELEASE_REQUIRED_HOUSEHOLD_SOURCE_COLUMNS (presence-checked; value quality is the gate's job), and the us_geography_ladder gate runs on the selected support with its calibrated weights — a release whose spine is inconsistent or whose NYC mass collapsed fails by default. --allow-geography-ladder-gate-failures is the diagnostic escape hatch, mirroring --allow-input-mass-drift, and the gate result lands in the export summary either way.

Operational note: reconstructions of ladder-less bases (anything built before #277) now require the explicit escape hatches — that is the point. The national ladder artifact builds in minutes from cached sources via tools/build_us_block_ladder_artifact.py (18MB NPZ; validated at 5,769,942 blocks / population exactly 331,449,281 / 436 CDs / 3,143 counties).

🤖 Generated with Claude Code

The ladder shipped opt-in (#277): nothing stopped a base or release from
being built without the geography spine, which is the silent-degradation
family (#225 everyone-is-a-citizen, #34 NYC-zero) this repo legislates
against.

- tools/build_us_puf_support_base.py: omitting --block-ladder-artifact is
  now an error; diagnostic builds opt out explicitly with
  --without-block-ladder (recorded in the summary).
- L0/refit release export: the geography spine (state_fips,
  congressional_district_geoid, and the seven ladder columns) joins the
  required release source columns (presence; value quality is the gate's
  job), and the us_geography_ladder gate runs on the selected support
  with its calibrated weights — a release whose spine is inconsistent or
  whose NYC mass collapsed fails by default.
  --allow-geography-ladder-gate-failures is the diagnostic escape hatch;
  the gate result is recorded in the export summary either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis merged commit f281826 into main Jul 2, 2026
4 checks passed
@MaxGhenis MaxGhenis deleted the geo-ladder-default branch July 2, 2026 15:34
MaxGhenis added a commit that referenced this pull request Jul 2, 2026
The reviewer found two sibling scorer tools this branch broke:
score_us_fiscal_targets.py and score_us_state_files.py imported the
retired _load_ledger_facts and compiled without contract flags, so both
crashed at import and would then have raised PeriodContractError on any
real feed. They now load through the pinned artifact loader and expose
--age-targets / --allow-unaged-dollar-targets mirroring the release
builder, defaulting to the incumbent un-aged-waived surface so existing
releases score against the surface they were calibrated to.

Also from review: publisher-projection-backed dollar targets are never
re-aged (aging_factor_source=source_projection_level, mirroring the
period-contract exemption — re-projecting a published projection would
silently compound models); ledger_assertion metadata is recorded only
when the fact asserts it, matching the loader's no-stamp rule; the CBO
and SOI series indexers fail loudly on conflicting facts for the same
(series, year) instead of last-write-wins; and one misindented test
kwarg. Branch rebased onto current main (#289 et al.). The review
harness behind the PR's measured-implications section is checked in as
tools/diff_us_target_surface_aging.py so reviewers can reproduce it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Jul 3, 2026
* Consume pinned Ledger artifacts and enforce the period contract

Loads Ledger consumer artifacts (manifest.json + consumer_facts.jsonl)
with hash verification and optional build-config pins, and records the
artifact identity in both build and release manifests, so a release's
target values are reproducible from a named Ledger artifact (#160,
#271). Bare consumer-facts files still load, content-addressed by their
own hash.

The period contract (PolicyEngine/ledger#71) is now enforced at compile
time: an ageable observation dollar level whose fact period differs
from the build period raises PeriodContractError instead of silently
calibrating at the wrong period — the populace#212 failure mode.
Builds either age the value under the named cbo_growth_factor_aging
model (now the release-tool default, with --no-age-targets to disable),
or pass an explicit allow_unaged_dollar_targets waiver that stamps
period_contract_waiver metadata on every affected target. Aged targets
carry alignment_model_id/alignment_model_version alongside the existing
factor lineage, publisher projections consumed at their published level
are exempt by assertion, and CBO rows typed as observations are refused
as growth factors. Consumer rows' assertion and fact period now flow
into ledger_* target metadata and reference selectors.

Refs #116, #160, #212, #271; PolicyEngine/ledger#71, PolicyEngine/ledger#73.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Adapt existing target-compile tests to the period contract

Existing tests compile 2024/2025 surfaces from TY2022/23 fixture facts
un-aged — exactly the state the contract now refuses silently. Each
compile call declares the explicit allow_unaged_dollar_targets waiver
so the tested behavior is unchanged and the waived state is visible,
and the release-builder test stubs the artifact loader instead of the
retired bare-JSONL helper. A new compile-level test asserts the
unwaived path raises.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Do not stamp default assertions onto legacy fact rows

The artifact loader validated row assertions by writing the observation
default onto rows that omit the field. Measured against a real bundle
feed, that broke aging: pre-#73 CBO projection rows arrive unlabeled,
the stamp typed them as observations, and the growth-factor filter then
refused them — zero targets aged. Rows now pass through exactly as
published; an assertion is validated when present and never fabricated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Chain observed SOI growth into aging factors (model v1.1.0)

Measured against a real three-year Ledger bundle feed, v1.0.0 aged only
the 28 national TY2023 dollar targets: the CBO February 2026 projection
detail starts at TY2023, so all 1,480 TY2022 state rows had no
denominator and fell to period-contract waivers — an incoherent joint
surface (national anchors at 2024 levels, states at 2022).

cbo_growth_factor_aging v1.1.0 bridges years the projection detail does
not cover with the same series' national SOI actuals: factor =
soi(pivot)/soi(source) x cbo(build)/cbo(pivot), observed growth for
observed years and projected growth only where nothing is observed.
Chain sources are an explicit map (AGI via Table 1.1, wages and net
capital gain via Table 1.4); everything else keeps the AGI default,
now also chainable. Chained factors record both bridge facts in their
lineage.

On the same feed the full surface now ages: 1,480 targets, zero
waivers, aged-dollar total $184.0T -> $202.7T (+10.2%); the TY2022
US AGI row lands at $16.56T against $16.62T from the TY2023 national
path — independent vintages converging on the same build-year level.

Refs #116, #212; PolicyEngine/ledger#71.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix independent review findings

The reviewer found two sibling scorer tools this branch broke:
score_us_fiscal_targets.py and score_us_state_files.py imported the
retired _load_ledger_facts and compiled without contract flags, so both
crashed at import and would then have raised PeriodContractError on any
real feed. They now load through the pinned artifact loader and expose
--age-targets / --allow-unaged-dollar-targets mirroring the release
builder, defaulting to the incumbent un-aged-waived surface so existing
releases score against the surface they were calibrated to.

Also from review: publisher-projection-backed dollar targets are never
re-aged (aging_factor_source=source_projection_level, mirroring the
period-contract exemption — re-projecting a published projection would
silently compound models); ledger_assertion metadata is recorded only
when the fact asserts it, matching the loader's no-stamp rule; the CBO
and SOI series indexers fail loudly on conflicting facts for the same
(series, year) instead of last-write-wins; and one misindented test
kwarg. Branch rebased onto current main (#289 et al.). The review
harness behind the PR's measured-implications section is checked in as
tools/diff_us_target_surface_aging.py so reviewers can reproduce it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Retire the bare Ledger facts loader everywhere

The re-review found a third importer of the removed _load_ledger_facts:
audit_us_ctc_line19_proxies.py failed at import. An exhaustive sweep
then found build_us_puf_support_base.py carrying its own private copy
of the same bare loader. Both now load through the pinned artifact
loader, so no unverified feed path remains anywhere in tools/. Also
covers the SOI-chain conflict guard with its own regression test
(the CBO guard already had one).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

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.

1 participant