Finish #515: per-record E19200 decomposition by published SOI 2.1 AGI-band components - #561
Conversation
The interim national carve cannot support a per-AGI-band decomposition unless every carried component is independently traceable. Capture TY2015 SOI Table 2.1 rows 10-32, all five interest-paid amount columns, band boundaries, official URLs, and workbook/report coordinates as a packaged source asset. This preserves the published four-part identity and makes explicit that the modeled non-mortgage residual includes points and mortgage-insurance premiums as well as investment interest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A proportional split is only safe if it preserves every donor record and every published band while routing the full non-mortgage residual exactly once. Add RED tests for the complete source-row identity, the open-ended negative-AGI first band, nonzero residual mass, explicit AGI input, deterministic order-independent bits, and exact E19200 conservation. The tests currently fail at collection because the decomposition module is intentionally not implemented yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The national carve discarded the non-mortgage residual and left the broader-interest and HMID measures structurally identical. Load the row-cited TY2015 component table, choose a deterministic mortgage share from each donor record’s explicit tax-unit AGI, preserve raw E19200 as mortgage plus the modeled non-mortgage residual, and persist that residual through PolicyEngine’s investment_interest_expense leaf. Keep the grouped-raw $10M screen ahead of the split and apply one share to every mortgage-lineage leg. The base builder now calculates aligned AGI from the pinned processed PUF and releases the transient engine simulation before QRF. Adding the new person target changes the production target-order digest, while schema v4 also invalidates custom-order standalone checkpoints whose manifests do not fingerprint donor construction. Focused decomposition, support, base-builder, and QRF tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Once the E19200 residual reaches investment_interest_expense, zeroing the broader interest_deduction would erase both source-backed components and misstate the mortgage tax expenditure. Point the JCT mortgage reference at deductible_mortgage_interest and pin that the reform can no longer neutralize the broader deduction. The focused JCT registry, reform-shape, and coverage tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: Once E19200 carries a non-mortgage component, the SOI home-mortgage target must compile from the mortgage-specific engine variable rather than remain byte-identical to the broader interest-paid target. Extend the target materializer fixture with both concepts and assert that their itemizer-filtered household vectors differ. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: The published Table 2.1 decomposition replaces the prior source-unavailability adjudication, so leaving investment_interest_expense exempt from coverage or export gates would let the repaired component disappear silently. Move ownership to the PUF tax-detail stage, remove the obsolete known gap, regenerate the 164-required/7-exclusion manifest, and require a nonconstant exported signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: Release notes must record the change from a national mortgage-only carve to a sourced per-band decomposition and the corresponding JCT concept correction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: Keep the changed runtime exports and base-builder imports compliant with the repository Ruff gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: The issue gate requires every changed Python file to pass the repository format check, including the new source-table loader and materializer fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: Country packages must enumerate every shipped source artifact, and the unrelated real-PUF tail test must supply an explicit band key now that nonzero E19200 fails closed without one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: TY2015 Table 2.1 shares must follow the originating PUF E00100 bands, and the new restricted source input must invalidate outer-stage checkpoints when its bytes change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The published TY2015 component shares are only valid against TY2015 E00100. Recover that source value by direct RECID alignment and the archived seed-42 aggregate-row allocator, while anchoring the Forbes tail in its evidenced source bucket. Pin the restricted input identity and make stale zero-investment base artifacts ineligible for reuse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: Source-year AGI reconstruction depends on the archived PUF having uprated the raw records before seed-42 disclosure-row expansion, including its Forbes-backed RECID 999999 tail. Correct the manifest's retired inverse ordering and disabled-tail claim, and pin both facts in the source-plan test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: The archived artifact used uprate-before-disaggregate and a Forbes tail, but those are provenance facts about the consumed H5 rather than supported parameters of Populace's executable source prefix. Record and test the artifact lineage separately while preserving the runnable manifest operation contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WHY: The shipped donor note contradicted the pinned processed-PUF lineage by reversing uprating and disclosure-record replacement and denying the Forbes tail. Align the note with the source manifest and pin that agreement in the plan test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…losed on invalid AGI Confirm round 1 (HOLD, two P2 edge contracts): - The naive (total*share, total-mortgage) pair is algebraically but not bit-exactly conserving (the reviewer's E19200=1.53 top-band reproducer summed to 1.5300000000000002). Recomputing mortgage as the exact complement of the rounded residual restores bit-for-bit conservation — proven on the reproducer and a 20,000-record adversarial sweep of cents-valued totals across the AGI range, plus the pinned-PUF exactness the review measured on all 208,611 rows. - The donor API's finiteness check was defeated upstream: _numeric_array coerces nonnumeric/NaN to 0.0, silently routing invalid AGI to the first band's share. AGI now converts strictly (errors=raise; NaN/inf reach and fail the explicit check), and the regression exercises the puf_tax_unit_donor_from_arrays seam the reviewer probed — not the split function, which always validated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round 2 pushed for both P2 findings: (1) conservation is now bit-exact by reconciliation — mortgage is recomputed as the exact complement of the rounded residual; proven on your 1.53/top-band reproducer and a 20,000-record adversarial sweep (cents-valued totals, full AGI range), with the residual keeping its published-share definition to one rounding; (2) the donor API converts AGI strictly (errors=raise), so NaN/inf reach the explicit finiteness check and nonnumeric raises — regression exercises the |
…nservation comment Confirm round 2 (HOLD, one P2 + two P3): - errors=raise is parse-strict, not real-number-strict: datetime and timedelta arrays (including NaT) convert to finite epoch/sentinel integers, complex drops its imaginary part with only a warning, and booleans pass — the reviewer routed $100 into band 0 through a valid donor seam via pd.NaT. Non-real dtype kinds are now rejected up front (TypeError) before strict parsing; NaN/inf still fail the explicit finiteness check. - The AGI regression could mask that hole: its fixture failed for unrelated reasons (wrong column names, underivable default outputs), so any raise looked like a pass. Rebuilt around a positive control — the same fixture succeeds with valid AGI and empty explicit outputs — plus cause-specific assertions per rejection class (NaN/inf -> 'finite'; NaT/timedelta/complex/bool -> 'real-valued'; string -> parse error). The first NaT probe was itself invalid numpy (np.asarray([pd.NaT], dtype=datetime64) raises in the test's own argument expression); both the pandas-inferred and numpy-typed NaT forms are now exercised. - The reconciliation comment claimed too little and too much: conservation is guaranteed, not empirical — with share in [0, 1] one of the two subtractions is exact by Sterbenz's lemma (case analysis verified: whichever of rounded mortgage or rounded residual lands in [total/2, total]), so the pair sums to total bit-for-bit for every accepted input, with -0.0 reconstructing as +0.0. And the mortgage deviation from total*share is bounded by the subtractions' roundings at the half-ULP-of-total scale — the reviewer measured 16-17 mortgage ULPs on the 1.53 reproducer, so 'one rounding' at mortgage scale was wrong and is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round 3 pushed for the round-2 findings: (1) P2 — non-real dtypes are rejected up front by kind ( |
…wording
Confirm round 3 (HOLD, one P2 + two advisories):
- Object-dtype payloads bypassed the round-3 kind gate: object-wrapped
True parses to 1.0 and routes $100 to band 0, object-wrapped complex
keeps its real component with only a warning, and categorical bools
ride kind 'O' too. The object branch now screens element types via
pd.api.types.infer_dtype, allowing only faithful float conversions
(integer/floating/mixed-integer-float/decimal/string/empty — Decimal
and numeric strings stay accepted per review; missing values still
die at the finiteness check) and rejecting boolean/complex/
categorical/mixed with the same real-valued TypeError the typed
regressions pin. Verified live before writing: pd.Series promotes
object-wrapped NaT/Timestamp to datetime64, so those hit the round-3
kind gate; bool/complex/categorical were the residual routes.
- The bad-string regression now matches the strict parser's own message
('Unable to parse') instead of any ValueError.
- Conservation wording: 'bit-for-bit for every accepted total except
signed zero' — the -0.0 -> +0.0 reconstruction is the one exception,
stated as such.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round 4 pushed: (1) P2 — the object branch now element-screens via |
…ll Build P2 pool (#565) * Registry-exclude VT under-$1 taxable interest: zero support on the full pool The dense arm cannot express what the pool does not carry: on the Build P2 dense frame (full 337,704-household pool, no selection) the registry aggregates for both Vermont under-$1 taxable-interest rows are exactly 0.0 initial and final, with household weights strictly positive and the signal column nonnegative — so zero aggregate proves zero carriers. This is the ia/nd class (pool-level unexpressibility, both arms), not the sparse-selection re-roll class the per-run registers handle; the Build P sparse register's VT pair hands off to this standing entry. Surfaced by dense-P2 attempt 2 (populace#507): the #561 donor-table insertion re-rolled the QRF joint model and the VT cell lost its last pool carriers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Round 2: repair wrapped release id; person-record phrasing; #507 receipt posted The textwrap pass split the release id across a line break and the join introduced an interior space (populace- us-2024-…) in both permanent reasons — a registry reason must carry the exact id. The carrier count now says what it counts (201,242 positive person records; 122,852 unique carrier households). The decisive dense attempt-2 receipt the PR body cites is now actually on populace#507. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Why
Issue #515 identified an inconsistent calibration surface: the processed PUF
carried all-zero
investment_interest_expense, so the broaderinterest_deductionand mortgage-specific HMID controls compiled identicalcarrier vectors even though their targets differ. This implements the
adjudicated repair.
Fixes #515
What changed
Package the TY2015 IRS SOI Table 2.1 rows 10–32 for total interest paid,
home mortgage interest, deductible points, qualified mortgage-insurance
premiums, and investment interest.
Select each record’s published share with source-year E00100 and split
E19200deterministically:Preserve the existing raw-E19200
$10Moutlier screen and allmortgage-only lineage columns.
Populate
investment_interest_expensewith the conserving non-mortgageresidual.
Align source-year AGI for the processed PUF’s seeded disclosure-record
replacements, while keeping the Forbes-backed open tail in its known final
SOI band.
Switch JCT HMID neutralization from
interest_deductiontodeductible_mortgage_interest.Require nonconstant investment-interest signal at release, advance the
primary QRF checkpoint to schema v4, update Build J source pins, add the
changelog, and add decomposition/provenance regressions.
The official sources are the
TY2015 Table 2.1 workbook
and Publication 1304, printed pp. 100–101.
The ledger feed has no TY2015 band rows. Every packaged row matches the
official printed table; the repo’s historical SOI asset independently matches
all CF/CH cells. The published band totals are
$304.461163Btotal,$283.004467Bmortgage, and$21.456696Bconserving residual.Allocation and real-input audit
The split is proportional within each half-open published AGI band, so each
record and each band preserve E19200 exactly. It is bit-deterministic and
order-invariant. On the pinned real inputs, 211,677 source-year AGIs align; the
existing outlier screen retains 208,611 donor rows and the weighted identity is
$418.251492B = $378.352506B mortgage + $39.898987B residual.Checkpoint discipline
This repair runs in the base builder. Its staged resume identity hashes the
builder and all package sources, plus the new TY2015 PUF input’s full SHA; the
inner QRF checkpoint advances to schema v4. It therefore requires and produces
a new base H5. Release target-frame checkpoints and reform-vector caches are
keyed by that H5’s full SHA (and the changed target-registry/reform identities),
so no release materializer or cache-schema bump is warranted. Unlike #557, no
release-time staged transform changes behind an unchanged base hash.
Validation
uv run pytest packages/populace-build/tests/ -q— rc 0Key regressions:
test_ty2015_e19200_component_rows_are_complete_cited_and_conservativetest_e19200_donor_split_preserves_each_band_and_published_sharestest_e19200_donor_split_is_bit_deterministic_and_order_invarianttest_source_year_puf_agi_matches_pinned_flat_array_artifacttest_soi_filtered_targets_keep_mortgage_and_broad_interest_distincttest_jct_mortgage_reform_neutralizes_only_the_mortgage_componenttest_outer_stage_resume_rejects_changed_builder_codetest_puf_stage_distinguishes_runtime_prefix_from_artifact_lineage