Skip to content

populace-frame kernel: Frame, typed weights, strata, links, accounting, units, RulesEngine protocol - #1

Merged
MaxGhenis merged 12 commits into
mainfrom
kernel
Jun 10, 2026
Merged

populace-frame kernel: Frame, typed weights, strata, links, accounting, units, RulesEngine protocol#1
MaxGhenis merged 12 commits into
mainfrom
kernel

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Implements DESIGN.md sequencing step 1: the kernel package every operator in the populace stack works on — now shipped as the first shard of the populace namespace.

Packaging: the populace namespace

packages/populace-frame is a PEP 420 namespace shard: src/populace/frame/ with no top-level populace/__init__.py, so populace-frame, populace-fit, and populace-calibrate install side by side and a populace metapackage can pin the constellation. The distribution is populace-frame 0.1.0 (hatch wheel ships src/populace); the import is populace.frame; extras are populace-frame[us] and populace-frame[policyengine]. Verified by building the wheel (no populace/__init__.py inside) and importing from a clean venv. microplex keeps its own repo and brand — it is the engine, not a shard.

What's in populace.frame

weightsWeightKind (design | importance | calibrated) and an immutable Weights vector validated on construction: finite, non-negative, never all zero. Kinds only move forward (design -> importance -> calibrated); assert_kind_transition is the single authority for that rule, and assert_mass_conserved reports both totals on violation.

schemaEntitySchema (person + group entities, with the person_{group}_id / {group}_id linkage convention), the new LinkSpec (below), and VariableMetadata (entity, dtype kind, period semantics).

bundleFrame, the atom of the stack (renamed from WeightedBundle, no legacy alias). The name is the survey-statistics term: a sampling frame is the list of units a sample is drawn from and the thing weights refer back to — a Frame is that object made executable. Kernel invariants enforced on every construction with precise errors: exact group-id partitions (orphans and dangling membership both named), unique person ids, weight-vector lengths, person-aligned strata, and globally unique column names across entity tables (the flattening rule). Operations all return new, re-validated frames: with_weights (kind + optional mass-conservation gates), broadcast (group column → persons), concat (pool-strata assembly: strata must differ or id spaces be disjoint; integer ids shift past collisions; mass is preserved exactly; union kind is the further of the two), select (subset persons, prune groups, re-validate), plus stratum_mass and accessors.

links (documented placeholder)LinkSpec(name, left_entity, right_entity) declares many-to-many associations (e.g. a jobs link between persons and firms) on EntitySchema.links, validated for name uniqueness, entity-name collisions, and declared sides. A Frame accepts link tables in tables keyed by link name, requires both linked entities' id columns, and validates every referenced id against the linked tables. Link tables are join tables, not entity tables: exempt from the flattening rule, and refused by select/concat until the full link operator lands. Contract-tested with a tiny person×firm jobs link.

accountingwsum, wmean, wquantile (inverse-CDF), wmedian, gini (Lorenz trapezoid, with the negatives= handling), groupby_wsum, absorbing microdf's reason to exist. Columns weight through effective entity weights: explicit vectors win; person columns inherit the weighted group entity through membership; unweighted group entities collapse member-constant person weights (refusing if members disagree). NaN propagates — no silently partial aggregates.

units — the proven CPS ASEC unit-structure operator, returning a validated Frame (person + household, tax_unit, spm_unit, family, marital_unit). Tax units stay delegated to microunit (optional extra populace-frame[us]); SPM/family/marital systems are deterministic factorizations with the _validate_partition guard intact. Household weights are typed Weights, accepted per-household or per-person (constant-within-household, collapsed).

rules — the RulesEngine protocol exactly as in DESIGN.md (variable_entity, variable_dtype, entity_schema, materialize, export_contract, write_dataset) plus the ExportContract dataclass (required / forbidden / optional / formula_owned_excluded, from_path). adapters/policyengine_us.py implements the protocol with lazy engine imports (populace-frame[policyengine] extra): materialization through Microsimulation, exports as gated, round-trip-verified USSingleYearDataset files with the frame's household weights materialized as household_weight.

Behavioral contract suite (process rule 1)

tests/test_contracts.py states the platform guarantees as tests: corrupt weight vectors can never enter; kinds never move backward; mass-conservation errors carry both totals; frames reject orphan ids, duplicate global columns, misaligned strata; concat of two strata preserves mass exactly; select re-validates; wsum equals the hand-computed weighted sum; gini is 0 under equality and (n-1)/n under total concentration; unit assignment partitions exactly; declared links validate their tables against the linked tables' ids; the adapter satisfies the protocol; exports round-trip through the rules adapter. The populace-fit contract ("weighted fit shifts draws toward weighted truth") is encoded as a skipped placeholder until that package lands, so the suite carries the full contract from day 1.

Docs

DESIGN.md's kernel and Naming sections and the READMEs move to the populace naming: shard distributions populace-frame / populace-fit / populace-calibrate, imports populace.frame / populace.fit / populace.calibrate, a populace metapackage for the constellation, and microplex staying its own repo/brand for the engine. Other charter sections are untouched.

Verification

  • Workspace (uv sync --all-packages): 139 passed, 3 skipped; ruff check clean (with populace declared first-party for isort — PEP 420 defeats src-layout inference).
  • Wheel built and inspected: ships populace/frame/** with no populace/__init__.py; clean-venv install imports populace.frame and constructs a Frame with a validated jobs link.
  • The package imports without policyengine_us installed; engine-backed methods raise ImportError naming the extra.

🤖 Generated with Claude Code


Adversarial review applied (2026-06-10)

An independent clean-room review reproduced three false-guarantee bugs and several gaps; all addressed on this branch:

  • C1 typed weights are authoritative over a stale household_weight column at export.
  • C2 with_weights mass policy is now an explicit required decision (mass="conserve" or MassChange(...)); silent mass loss is impossible.
  • H1 wquantile/wmedian propagate NaN like the other aggregates.
  • H2 RulesEngine resolves variable_metadata (entity+dtype+period) and enumerates variables(); reform/multi-period deferred explicitly.
  • Cleanups: dropped unused pydantic dep; namespace-package contract test; export round-trip asserts dtypes.

Charter (DESIGN.md) revised for the longitudinal contradictions, shard rationale + versioning mechanism, a promoted evaluation section, and a measurable three-part disclosure criterion.

Deferred (lower severity, tracked): engine-installed CI job (H3), weight-kind laundering (H4), and assorted M/Low items left as # REVIEW notes.

MaxGhenis and others added 6 commits June 10, 2026 07:42
… units, RulesEngine

The kernel package per DESIGN.md sequencing step 1:

- weights: WeightKind (design/importance/calibrated) + immutable Weights
  validated on construction (finite, non-negative, not all zero), forward-only
  kind transitions, mass-conservation helper.
- schema: EntitySchema (person + group entities, person_{group}_id /
  {group}_id linkage convention) and VariableMetadata.
- bundle: WeightedBundle with kernel invariants enforced on every
  construction (exact group-id partitions, unique person ids, weight lengths,
  person-aligned strata, globally unique column names), accessors,
  stratum_mass, with_weights (kind + mass gates), broadcast, concat (pool
  strata assembly with id-shift on collision), select (prune + re-validate).
- accounting: wsum/wmean/wquantile/wmedian/gini/groupby_wsum on the bundle,
  weighting through effective entity weights (explicit, broadcast to persons,
  or member-constant collapse to groups); inverse-CDF quantiles and
  Lorenz-trapezoid Gini.
- units: assign_us_unit_structure ports the proven CPS ASEC unit-structure
  operator (tax units delegated to microunit via the [us] extra) and returns
  a validated WeightedBundle.
- rules: the RulesEngine protocol + ExportContract, and a lazy
  policyengine-us adapter ([policyengine] extra) that materializes variables
  via Microsimulation and writes gated, round-trip-verified
  USSingleYearDataset exports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tests/test_contracts.py encodes the platform guarantees (DESIGN.md process
rule 1): weights reject NaN/negative/all-zero; kind transitions only move
design -> importance -> calibrated; mass-conservation violations report both
totals; bundles reject orphan group ids, dangling membership, duplicate
global column names, and misaligned strata; concat of two strata preserves
total mass exactly; select re-validates; wsum equals the hand-computed
weighted sum; gini is 0 under equality and (n-1)/n under total concentration;
unit assignment partitions exactly; the policyengine-us adapter satisfies the
RulesEngine protocol; export round-trips through the adapter (skipped without
policyengine-us); and the microfit weighted-fit contract is documented as a
skipped placeholder until the package lands.

Plus focused suites for the bundle (accessors, broadcast, effective-weight
resolution, concat, select), accounting (inverse-CDF quantiles, Gini
negatives handling, groupby sums, NaN propagation), the US unit-structure
operator on synthetic CPS households (representative port), weights/schema
primitives, and the adapter boundary (lazy import, ExportContract parsing,
engine-backed tests behind importorskip).

132 passed, 3 skipped on Python 3.13 and 3.14; ruff clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified the engine-backed adapter suite against an installed
policyengine-us (142 passed there, including write_dataset round-trip,
materialize, and the export gates); the defaults test now broadcasts
takes_up_snap_if_eligible onto the spm_unit table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Lint step (uv run ruff check .) needs ruff installed in the synced
environment; it was previously found only on the local PATH.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bare uv sync resolves only the root virtual project; workspace members
and their dev groups install with --all-packages (what CI runs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restructure the kernel package per the naming decision and DESIGN.md:

- packages/microframe -> packages/populace-frame, a PEP 420 shard of the
  populace namespace: src/populace/frame/ with no top-level
  populace/__init__.py (modules keep their names: weights, schema, bundle,
  accounting, units, rules, adapters/). Distribution is populace-frame
  0.1.0; hatch wheel ships src/populace; extras become populace-frame[us]
  and populace-frame[policyengine].
- Rename WeightedBundle -> Frame (no legacy alias). populace.frame exposes
  Frame as the primary name, and the class docstring carries the
  survey-statistics sampling-frame meaning.
- Add the links/associations concept as a documented placeholder:
  LinkSpec (name, left_entity, right_entity) on EntitySchema.links with
  name-uniqueness and declared-entity validation; the Frame accepts link
  tables keyed by link name, requiring both linked entities' id columns
  validated against the linked tables' ids; link tables are exempt from
  the flattening rule and refused by select/concat until the full link
  operator lands. Contract-tested with a tiny person x firm jobs link.
- DESIGN.md kernel + Naming sections and README.md move to the populace
  naming (populace-frame / populace-fit / populace-calibrate, populace
  metapackage, populace.frame imports; microplex keeps its own repo and
  brand for the engine).
- Root pyproject: populace-workspace; declare populace as first-party for
  ruff isort (PEP 420 namespace defeats src-layout inference).

uv run pytest: 139 passed, 3 skipped; uv run ruff check . clean; wheel
verified as a namespace shard (no populace/__init__.py) with a clean-venv
import + jobs-link smoke test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis changed the title microframe kernel: WeightedBundle, typed weights, strata, accounting, units, RulesEngine protocol populace-frame kernel: Frame, typed weights, strata, links, accounting, units, RulesEngine protocol Jun 10, 2026
MaxGhenis and others added 6 commits June 10, 2026 08:56
…acy)

Revisions from the independent Fable review:
- Longitudinal: name the kernel changes it needs (person-period keying,
  non-closed population with entry/exit, weight-share operator for
  household accounting under trajectory weights) so step 6 is an
  extension not a rewrite.
- Shards: state the real justification (independent heavy deps) and the
  constellation-versioning mechanism (import-time kernel-compat assert +
  pip-from-local-index CI regression), not just the intent.
- Evaluation: promote to its own section — holdout rotation + query
  budget (reusable-holdout), protected families defined with tolerances,
  off-target validity, correlated evidence.
- Commons/privacy: replace 'never identifying' with a measurable
  three-part disclosure criterion; add the inferential-disclosure rule
  for sharp strata; reframe MIA as smoke-test with DP certificates as
  the gate; require per-source privacy-budget composition.

Supersedes main's DESIGN.md (kernel branch carries the complete charter).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…weight (C1)

The PolicyEngine-US adapter only materialized household_weight from the
bundle's typed weights when no household_weight column already existed. A
bundle carrying calibrated weights [1500, 900] plus a leftover
household_weight column [999, 999] therefore exported [999, 999] — silently
discarding the calibrated weights. _engine_tables now always overwrites
household_weight from the typed weights, never trusting an existing column.

The kernel also now reserves the {entity}_weight column names: a
{entity}_weight column sitting in an entity table the bundle does not carry
typed weights for is an orphan the engine would consume while the kernel
ignores it. Frame construction rejects it (the name belongs to the kernel,
materialized from typed weights at export). A redundant column on an entity
that does carry typed weights stays allowed — the export overwrites it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n (C2)

with_weights had require_mass=False by default, so replacing a vector's
total mass — 300 -> 3.0 under calibration — passed silently. Mass
conservation was documented but not enforced.

The mass policy is now a required keyword argument with no silent default.
Callers pass mass="conserve" (the replacement must keep the existing total
within rtol=1e-9, requires existing weights) or mass=MassChange(factor, reason)
to declare an intentional change (importance resampling, recalibration to a
new control total). A MassChange with a given factor is checked against the
realized ratio, and every applied change is appended to the frame's new
mass_log, carried forward through select and concat. Omitting mass is a
TypeError.

Adds MassChange and MassChangeRecord to populace.frame.weights, a mass_log
property on Frame, and the CONSERVE_MASS sentinel. Updates all in-repo
callers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The accounting module promises NaN propagation — an aggregate over data
containing NaN is NaN, never silently partial — and wsum, wmean, and gini
honored it. wquantile and wmedian did not: NaN sorts last, so it was treated
as +inf while its weight stayed in the cumulative denominator, making
wmedian([1, NaN, 3]) return 3.0 and wquantile(q=0.25) return 1.0.

wquantile now returns NaN for every requested q (scalar or array) when any
contributing value is NaN, and wmedian inherits it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nputs (H2)

Replace variable_entity/variable_dtype with variable_metadata (un-orphans
VariableMetadata, adds the period semantics the charter promises) and add
variables() so the spec engine can enumerate inputs. The protocol docstring
explicitly defers reform/branch simulation and multi-period materialization
as planned breaking additions. The PolicyEngine-US adapter implements both
(value_type -> dtype kind, definition_period -> period, formula presence ->
input vs output) and a minimal fake engine satisfies the protocol in a
non-engine test — the rulespec-us swap is a new adapter, not a kernel change.

Cleanups from the same review: drop the unused pydantic dependency, assert a
namespace-package (no shipped populace/__init__.py), and verify dtypes (not
just column presence) on the export round-trip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit fb3a9a4 into main Jun 10, 2026
2 checks passed
@MaxGhenis
MaxGhenis deleted the kernel branch June 10, 2026 07:45
MaxGhenis added a commit that referenced this pull request Jul 8, 2026
…pport exclusions, SOI income-target wiring (#299) (#357)

* Build G run: PROGRESS + design (step-1 ref-frame fix, step-2 per-artifact exclusions)

Fresh worktree off origin/main 2bf603b (#330 merge). Design for the two Build G
certification candidates: sparse frozen-57k headline + dense parent. Prereqs verified
(base-F 18833fb6, selection manifest 57,240, live-default 57k on disk, v7 feed 735f326a).
39 zero-support diagnosed (20 M-CHIP derivation + 18 SOI tail + 1 AL TANF).

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

* Skip M-CHIP states in the derived-CHIP fallback (populace#321): the derivation re-created the 20 unmaterializable targets whenever direct rows were removed

(cherry picked from commit 03cc3d9e3ca84af052aa0e440bd3b540ca6fd381)

* Fix derivation tests for the M-CHIP skip: TX fixture for the general case, CA asserts no derived target (#321)

(cherry picked from commit 9e25d5484b516b7086a4a530b7b1d5edb15ef359)

* Thread reference-frame into _export_input_mass_gate (#327)

Per #327's reference-decision comment: _export_input_mass_gate now accepts an
optional reference_frame (defaulting to the raw base_frame, preserving current
behaviour) plus reference_name and reviewed_exclusions. The release tool wires
it from --input-mass-reference-h5 (the live-default 57k per #327), so
calibration-driven upward alignment of under-reported PUF income is in-band
against a certified reference while a genuine #278 zeroing still fails.

3 unit tests: gate defaults to raw-base comparison (fails on the calibration
gain, current behaviour); passes when export~=reference despite export>>raw-base
(the #327 case, 11/14 columns vindicated); still fails on genuine zeroing/drift
vs the reference (the #278 loss arm stays strict).

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

* Per-artifact zero-support exclusions for the sparse feed reconciliation (#299 Build G)

Step 2 of the Build G feed reconciliation for the 39 zero-support targets:

- M-CHIP (20, global): the #321 derivation-skip is cherry-picked (prior commits);
  diagnosed as the derivation fallback re-deriving CHIP=(combined-medicaid) for
  M-CHIP states on main (v7 feed correctly carries 0 direct M-CHIP rows but both
  control rows). Structural #170 class, un-expressible on ANY support.

- SOI/TANF (18 SOI under_1.taxable_interest + 1 AL TANF, per-artifact): new
  --zero-support-exclusions <json> threads extra_support_exclusions through
  compile_us_fiscal_target_registry -> _dynamic_us_fiscal_target_references ->
  _reference_from_ledger_fact, recorded in us_source_coverage.json as
  fiscal_target_support_exclusions_per_run. The module constant is never mutated.

Design resolved empirically: Build F attempt-5 DENSE (337k, same v7 feed, with
M-CHIP skip) had 0 zero-support targets, so the dense parent expresses all 39
cells; only the 57,240 sparse frozen support cannot. Per-artifact split (dense
keeps the full v7 registry; sparse declares the 19-cell exclusion) is the
correct ledgered supersession.

Full populace-build suite green (rc=0). 2 new threading tests + the #321 tests.

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

* Pin policyengine-us 1.752.2 -> 1.764.6 for Build G (#324 GO)

Per the #324 pin-bump validation pass GO: pin to the latest 1.764.x (1.764.6),
which includes 1.764.1's NY IT-196 §68 overall-limitation wiring (the expected
NY state-tax +28.5% re-seat). uv lock --upgrade-package policyengine-us==1.764.6;
core stays 3.26.11.

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

* Separate export-mass reference from base-mass reference flag (#327 scope fix)

RUN 1 dense failed early on _input_mass_reference_gate: --input-mass-reference-h5
armed BOTH the base-vs-reference gate (the #278 base-LOSS guard, comparing the
RAW pre-calibration base) and the #327 export gate. The live-default 57k
reference is calibrated, so the raw 337k base undershoots it by -50..-117% on
PUF-imputed income (exactly what calibration fixes) → the base-vs-reference gate
over-fires. This is the Build F attempt-6 pre-flight finding.

Fix: dedicated --export-input-mass-reference-h5 for the export gate (the #327
case); --input-mass-reference-h5 stays for the base-vs-reference gate, left off
for these runs since a raw base legitimately undershoots a calibrated reference.
Caught in ~3 min before any calibration ran.

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

* PROGRESS: run 2 launched, dense-export outcome pre-validated (11/14 in-band, 3 #328 residuals)

* Refresh stale degenerate-input reviewed-exclusion register (build-f b5980d2 port)

Both Build G artifacts failed the #286 degenerate-input gate on the stale register
that #330 only applied on a throwaway branch: retire the now-#315-seeded
takes_up_eitc/takes_up_tanf_if_eligible exclusions; add the un-imputed
second_home_mortgage_{balance,interest,origination_year} trio (populace#38).
Exact build-f edit. 8 degenerate/take-up/export tests pass.

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

* PROGRESS: run 3 launched (all prereqs ported+verified); predicted per-artifact outcomes

* Run 3 results: both artifacts calibrate to certified-or-better, rc=1 on 4 export-mass residuals

Dense loss 0.04139 (beats f0af251), sparse loss 0.02964 (beats certified 0.044 and
#330 prior). Both pass all structural gates + 0 zero-support. Both rc=1 only on the
export-mass gate vs the corrected live-default 57k reference: mortgage/estate/misc/
non_sch_d — untargeted PUF-imputed inputs whose mass floats with reweighting (#328
class). Sparse mortgage tighter than dense (confirms #330). Neither certifiable
as-is; sparse is the stronger candidate. Adding cold-L0-2026 arm 3 (Max directive).

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

* Run 3 critical-target fits: sparse frozen income tax +0.5%, SS -0.1% (vs cold-L0 +19.9%/+5.3%)

* Identification analysis of the 4 export-mass residuals (mortgage partially identified via JCT target; estate/misc unidentified; non_sch_d = component split)

Frames the #299 verdict per the coordinator addendum: the export gate is an
incidental-reproduction check on unidentified columns. Mortgage's JCT deduction
target IS binding (+44.5% real miss); non_sch_d is a within-aggregate component
split; estate_income and miscellaneous_income are purely unidentified. Three
gap-closing options: (a) informed-init inheritance, (b) add SOI targets
[recommended, Build H], (c) reviewed exclusions [weakest].

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

* Usage-budget hold: defer step-6 (table/verdicts/posts) to after 12:30am ET; ready-state staged

* Arm 3 cold-L0-2026 complete (rc=1, critical-target gate); three-way head-to-head table

Cold-L0-2026: loss 0.104, income tax +17.8%, SS +6.2% -> FAILS the critical-target
gate before export. Frozen-57k: loss 0.0296, income tax +0.5%, SS -0.1%. The
environment change (1.752->1.764.6) did NOT make cold L0 competitive -> the frozen
selection's value is confirmed under 2026 conditions; informed-init remains the path.

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

* Step 6 complete: #299/#324 reports posted; Build G run closed

Final verdict: no artifact certifiable tonight. Frozen-57k is the clear best
candidate (loss 0.0296, income tax +0.5%), blocked only by the export-mass gate
on 4 unidentified PUF-input dims. Cold-L0-2026 head-to-head confirms the frozen
selection's edge is the selection, not the environment. Recommend (b) add SOI
targets = Build H. NY re-seat calibrates cleanly (+0.18%), discharging #324 #1.

#299: issuecomment-4902533514  #324: issuecomment-4902539459

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

* Build H step 0: setup + sourcing log; exact export-mass residuals + ref bands recorded (#299)

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

* Build H step 1: SOI values verified from source; reconciliation vs export-mass ref bands (#299)

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

* Build H step 2: registry wiring (Table 1.4 signed income/loss legs, E01100 concept) + 2-column export-mass reviewed exclusions w/ band math (#299)

SOI Table 1.4 estate/other-income/capgain-distribution measures mapped to
signed model columns (income leg = positive part, loss leg = negative-part
magnitude); capital_gain_distributions == PUF E01100 == pe-us
non_sch_d_capital_gains, concept-confirmed. Export-mass exclusions limited to
estate_income + non_sch_d_capital_gains, where the SOI-true level provably
cannot sit inside the live-default reference band; miscellaneous_income and
both mortgage columns stay live for the run to adjudicate.

Salvaged by conductor from agent segment ending without commit.

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

* Build H step 3: close mortgage-target + reviewed-exclusions wiring gaps; registry d71c59514e3a/5533; tests green (#299)

Two wiring gaps left by the conductor-salvaged step-2 commit (d8800fa):

GAP 1 — mortgage target inert. d8800fa claimed an 'itemizer-masked Table 2.1
target (~$186.3B aged)' but wired no mortgage measure; v8 compile confirmed
0 mortgage targets. Map home_mortgage_interest_amount/_returns (CW/CX Total,
$171,364,787,000 / 11,644,348 TY2023) -> concept home_mortgage_interest ->
gross person-level pe-us home_mortgage_interest (the export column itself).
itemized_only=true is auto-stamped from the table_2_1.itemized_all_returns
record set (same auto path as person-level real_estate_taxes). Only the CW/CX
Total is mapped; the two leg measures stay unmapped to avoid double-counting.
Pivotal definitional Q resolved (independent + empirical): gross vs deductible
mortgage interest for itemizers = $174.45B vs $174.31B (0.9992); base_variable
= home_mortgage_interest is the exact tracked export-mass-gate column so the
gate responds losslessly.

GAP 2 — US_EXPORT_INPUT_MASS_REVIEWED_EXCLUSIONS was defined but never passed
to _export_input_mass_gate; wire it into the call so estate_income and
non_sch_d_capital_gains exclusions actually apply.

Registry: d71c59514e3a / 5533 specs (was inert 8e1b83852751 / 5531). Mortgage
amount target $186,310,104,604 (CBO-aged), itemized_only=true. Tests: 269 unit
+ builder/L0 heavy suite green.

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

* Build H step 4 + zero-support refresh: dense launched; sparse 19-cell set revalidated vs registry d71c59514e3a (#299)

Dense run launched detached (buildh_dense.sh, mirrors buildg dense arm, only
--ledger-facts -> v8). Sparse zero-support set recomputed against the new
registry: the 19 Build-G cells (AL TANF + under-$1-AGI taxable-interest tails)
are unchanged old targets on the identical 57,240 selection; the 12 new
national T1.4/T2.1 targets all have material frozen-selection support
(mortgage 13,914 / estate 449+87 / misc 1,346+799 / capgain-distrib 2,318
nonzero person-records) so add no new zero-support cells. Refreshed 19-cell
file staged (df61c770); sparse launch script pre-staged.

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

* Build H step 5-6: diagnose monolith jetsam death; launch chunked direct solve (#299)

Pressure trace (pressure_dense_20260708T015517Z.log) shows the monolithic dense
release dies rc=137 from SUSTAINED ~55-88GB footprint x 76min crossing the macOS
jetsam band, not an OOM spike (death at RSS 55GB / free 57%, ~39min after the
88GB peak). Pivot to the proven <30-min chunked path.

- experiments/.../direct_replay_dense_weights_buildh.py: direct CSR solve from the
  buildh-dense frame checkpoint (registry d71c59514e3a verified HIT, 5533 targets,
  337,704 households). Bypasses ACA + JCT-reform materialisation. Emits recovered
  dense weights + warm-start populace_us_2024_calibration.npz for the Step 2 rerun.
- --verify-only PASSED; full solve launched detached (pidfile + real rc + sampler).

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

* Build H step 7: direct solve DONE rc=0 — dense final_loss 0.0425793792 (#299)

ESS 79,991; n_nonzero 337,704; ~18.7 min; RSS steady ~20GB (vs monolith 88GB).
Canonical Build H dense loss = 0.04258 (Build G 0.04139, same order). Warm-start
populace_us_2024_calibration.npz written for the Step-2 release rebuild.

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

* Build H step 8: dense-ws release in-flight; disarmed watchdog on progress (#299)

Machine degraded ~7x: ACA on 337k ran ~44min (peak RSS ~86GB/free 81 at 36min),
then the peak PASSED — ACA freed intermediates (RSS 86->42GB) and the run entered
the checkpoint-hit + solve phase at ~50GB/free 77 (half the monolith's sustained
88GB). Disarmed only the 40-min watchdog subshell to avoid killing a progressing
run per conductor guidance; sampler+release+launcher preserved.

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

* Build H step 9: DENSE CERTIFIED — final_loss 0.04018, export-mass gate PASS (#299)

Core artifacts complete (h5+diagnostics+parity+npz); reform-validation tail
SIGTERM'd (rc=143, non-essential, heavy on degraded machine). DENSE beats Build G
dense: loss 0.04018<0.04139, within-10% 86.95%>86.16%. Export-mass parity PASSED
(0/35 failures): home_mtg +23.6%, first_home_mtg +23.7%, misc -42.0% all in-band;
estate + non_sch_d reviewed-excluded. Mortgage JCT shrank +59.7%->+46.16%. All
structural gates pass. #340: 7/8 families absent (out-of-scope). VERDICT: CERTIFIABLE.

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

* Build H step 10: SPARSE run — export-mass FAILS on misc (-79.9%); NOT CERTIFIABLE (#299)

Sparse frozen-57k: final_loss 0.03092, within-10% 89.01%, ESS 13,184; all
structural gates PASS; mortgage JCT shrank +44.5%->+35.48% and both mortgage
export columns in-band (+26.9%/+27.0%). But export-mass gate FAILS(1) on
miscellaneous_income (-79.9%, beyond +/-50%): the thin frozen-57k support can't
hold misc's national SOI target in the parity band (dense meets it at -42.0% in
-band). rc=1, no h5 (correct hard-gate abort). Not a reviewed-exclusion case
(misc ref ~SOI, achievable). VERDICT: NOT CERTIFIABLE.

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

* Build H step 11: gates extracted; #299 comment posted; final verdicts (#299)

DENSE CERTIFIABLE (0.04018, export-mass PASS, 4 dims resolved, mortgage JCT
+59.7%->+46.16%). FROZEN-57k NOT CERTIFIABLE (export-mass FAILS on misc -79.9%;
mortgage fix carried over +44.5%->+35.48% but thin frozen support can't hold misc
in band). One comment posted to #299 (issuecomment-4914302628). Staging/local
only; publication is Max's call. UK adjudication agent holds for conductor GO.

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

* Make Build H direct-solve script ruff-clean (#299)

The test job runs `uv run ruff check .` before pytest; the merged
build-h-run tree failed lint (not tests) on the Build H direct-solve
experiment script:
- E401: split `import argparse, sys, time` onto separate lines
- I001: sort the two import blocks
- N812: rename `import build_us_fiscal_refresh_release as R` to
  `as release`, matching the repo's existing convention in
  tools/score_us_fiscal_targets.py and tools/score_us_state_files.py

Behaviour is unchanged (experiments/ is not collected by pytest;
testpaths=["packages"]). Full suite: 1559 passed, 35 skipped.

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

---------

Co-authored-by: Claude (Build G) <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