Skip to content

Label IRS SOI N01000/A01000 as Form 1040 line 7 capital gain or loss - #304

Merged
MaxGhenis merged 3 commits into
mainfrom
ht2-net-capital-gain-less-loss-concept
Sep 30, 2026
Merged

MaxGhenis merged 3 commits into
mainfrom
ht2-net-capital-gain-less-loss-concept

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Historic Table 2 (HT2) labels IRS columns N01000/A01000 "Returns with taxable net capital gains" / "Taxable net capital gains". Their concept ids are irs_soi.returns_with_taxable_net_capital_gains / irs_soi.taxable_net_capital_gains, the ids Table 1.4 uses for columns 37/38 (Schedule D "Taxable net gain"). Those are different IRS counts: for TY2022 they are 30,465,850 and 12,915,122 returns. On main they still share one semantic_fact_key.

This PR gives N01000/A01000 the concept their IRS documentation defines, in every package that reads them:

Packages Column Old concept New concept New label
soi-historic-table-2, soi-historic-table-2-state-broad-2022 N01000 irs_soi.returns_with_taxable_net_capital_gains irs_soi.returns_with_form_1040_capital_gain_or_loss Returns with net capital gain (less loss)
same A01000 irs_soi.taxable_net_capital_gains irs_soi.form_1040_capital_gain_or_loss Net capital gain (less loss)
soi-congressional-district-2022 N01000 irs_soi.returns_with_net_capital_gains irs_soi.returns_with_form_1040_capital_gain_or_loss Returns with net capital gain (less loss)
same A01000 irs_soi.net_capital_gains irs_soi.form_1040_capital_gain_or_loss Net capital gain (less loss)
  • measure_id (net_capital_gains_returns / net_capital_gains_amount), values, columns, header guards and lineage are unchanged.
  • Table 1.4 keeps its Schedule D concept.

What the IRS files define

All files are in ~/PolicyEngine/_reviews/cg-returns-control-20260925/irs/. Doc guides were converted with textutil -convert txt; the line numbers refer to that text.

Doc guides. Every guide gives the same two rows, "1040:7" (TY2020/21: "1040: 7"):

N01000 | Number of returns with net capital gain (less loss) | 1040:7
A01000 | Net capital gain (less loss) amount | 1040:7

Guide sha256 Lines
HT2 TY2020 20incmdocguide.doc 4925939d… 241–248
HT2 TY2021 21incmdocguide.doc d7bf3d65… 260–267
HT2 TY2022 22incmdocguide.doc d99db44b… 267–274
HT2 TY2023 23incmdocguide.doc 94279069… 271–278
Congressional district TY2022 22incddocguide.docx 4e265d3c… 274–281
ZIP TY2022 22zpdoc.docx / county TY2022 22incydocguide.docx 8aedaa4d… / a6acb748… 229–236 / 284–291

Form 1040 line 7. "Capital gain or (loss). Attach Schedule D if required. If not required, check here" is line 7 on the 2020–2023 Form 1040. The line holds one of three things:

  • a Schedule D gain: Schedule D (2022) line 16 goes to Form 1040 line 7;
  • a Schedule D loss, limited to $3,000 ($1,500 married filing separately): Schedule D line 21;
  • capital gain distributions filed without Schedule D: 2022 Form 1040 instructions, p. 31, "Exception 1".

Publisher cells. Table 1.4 splits those three groups into separate columns. HT2's count matches their sum, not the gain column:

TY HT2 US N01000 (STATE=US, AGI_STUB=0) T1.4 distributions on Form 1040 T1.4 Sch. D taxable net gain T1.4 Sch. D taxable net loss Sum Residual N01000 / gain
2020 29,008,620 (20in55cmcsv.csv AI2) 3,919,950 (X9) 15,918,669 (Z9) 9,165,266 (AB9) 29,003,885 +0.02% 1.82
2021 32,996,180 (21in55cmcsv.csv AI2) 4,505,544 (X9) 20,497,375 (Z9) 8,074,079 (AB9) 33,076,998 −0.24% 1.61
2022 30,465,850 (22in55cmcsv.csv AK2) 3,980,047 (AJ9) 12,915,122 (AL9) 13,565,876 (AN9) 30,461,045 +0.02% 2.36
2023 29,481,840 (23in55cmcsv.csv AK2) 3,209,131 (AJ9) 12,392,020 (AL9) 13,833,037 (AN9) 29,434,188 +0.16% 2.38
  • Table 1.4 cells are in 20in14ar.xls–23in14ar.xls, sheet TBL14, row 9 "All returns, total".
  • Header paths: "Capital gain distributions reported on Form 1040"; "Sales of capital assets reported on Form 1040, Schedule D" > "Taxable net gain" / "Taxable net loss".
  • The capital-gain columns are IRS cols 23/25/27 in TY2020–21 and 35/37/39 in TY2022–23.
  • The TY2022 row is checked in CI against Chronicle's own registered bytes (see Tests).
  • HT2 is population data rounded to tens, while Table 1.4 is a sample estimate. The small residuals are consistent with that difference.

Why these ids.

  • Not the phrase "net capital gain (less loss)". Pub 1304 (Rev. 1-2025) Table A uses that phrase for Schedule D gain and loss returns without the distributions-only returns: 26,480,998 in TY2022, against HT2's 30,465,850. Chronicle's Table 4.3 package also already has irs_soi.*capital_asset_net_gain_less_loss for yet another count. Naming the Form 1040 line identifies the population exactly.
  • Not the congressional-district ids. irs_soi.net_capital_gains reads as a gain-only amount. IRC §1222(11) and the Pub 1304 Explanation of Terms (printed p. 331) both use "net capital gain" for a positive amount.
  • One pair for HT2 and the congressional-district file. Both files publish the same IRS variable. Before this change, 26 of the 38 IRS variables the national HT2 package shares with the congressional-district file carried one concept in both (24 of 36 for the state package); the capital-gains pair was 2 of the same 12 exceptions. The other 10 are tracked in chronicle#307.

Chronicle effects (measured)

Four affected packages, main vs this branch. Build: build-bundle --year 2023 over soi-historic-table-2, soi-historic-table-2-state-broad-2022, soi-congressional-district-2022 and soi-table-1-4, 30,768 facts. Diffed row by row on source_record_id:

  • 0 value changes and 0 lineage changes.
  • On the 1,084 capital-gain rows (HT2 22 + 102, congressional district 960), these fields change: observed_measure.source_concept, observed_measure_key, aggregate_fact_key, semantic_fact_key, legacy_fact_key, the generated label, and layout.measure_label.
  • layout.record_set_spec_hash changes on all 30,188 rows of the three relabelled packages, because it hashes the whole record-set spec.

Semantic duplicates.

  • Among the four packages, semantic duplicates go from 1,562 to 1,666 groups: +104, −0. The new groups are the TY2022 state and US rows (51 states + US, returns and amount), where HT2 and the congressional-district file now share a concept.
  • Every one of the 1,562 existing groups is already an HT2-vs-congressional-district pair of this kind.
  • The congressional-district values are lower. Its guide (22incddocguide.docx, section C) says its state totals "may not be comparable to State totals published elsewhere by SOI because of disclosure protection procedures or the exclusion of returns that did not match based on the ZIP code."
  • HT2 and Table 1.4 capital-gain rows for the same year can no longer share a semantic key. In the default bundle this collision was latent (HT2 builds at TY2022, Table 1.4 at TY2023, so the "−0" above); a same-year build of the two did collide, which test_line_7_and_schedule_d_gain_facts_are_different_semantic_facts now pins apart.
  • The default-bundle snapshot pin moves from 2,022 to 2,126. That pin is not re-derived locally: the local disk can't hold a full default bundle. CI confirms it.

Recorded in docs/concept-migrations.md, which docs/architecture.md now links, per its rule that "a deliberate rename requires a migration note and a regression test for the affected consumer selector".

TY2023 Historic Table 2 (#295)

#295 (stacked on #291) adds historic_table_2_2023 and historic_table_2_state_broad_2023. Both are cloned line by line from the TY2022 YAML, so they carry the old label. Two tests keep either merge order from shipping it:

Simulation: #295's head f55ae34 with this commit cherry-picked:

The TY2023 patch touches 8 lines in 2 files:

  • packages/irs_soi/historic_table_2_2023/source_package.yaml, net_capital_gains_returns and net_capital_gains_amount (label and concept each);
  • packages/irs_soi/historic_table_2_state_broad_2023/source_package.yaml, the same.

The generator gen_ht2_2023.py copies measure labels and concepts from the TY2022 twin, so re-running it on this tree produces the same 8 lines. The patch is posted on #295: #295 (comment)

Consumers (Microcosm)

Microcosm does not select on this concept id.

  • I grepped microcosm main (5187fce25), #1036 (2c811ab1c), #1040 (011a63b1a) and #1053 (67a36f5bc), excluding *.jsonl. None of them names any of the six ids.
  • On main, the capital-gains rebase control is keyed on (measure_id, filing_status, universe): _soi_capital_gains_control_key_from_fact, fiscal_targets.py:1611. A tie goes to the first row in the feed, but only between rows with an equal period: _prefer_candidate, :2603.
  • Since Refuse pinned-artifact builds that only relabel another year (#117) #292 the congressional-district rows are TY2022, so against Table 1.4's TY2023 row the period alone decides. This relabel cannot change that choice on any re-pin from current main.
  • The concept reaches microcosm only as spec metadata: ledger_measure_concept, ledger_source_concept and ledger_fact_label. The key fields ledger_*_fact_key / ledger_observed_measure_key come along too.
  • The one metadata consumer is _fiscal_target_concept_budget_key in tools/build_us_fiscal_refresh_release.py. For congressional-district specs it groups loss weights by metadata, including the concept.

Measured on the pinned feed (consumer_facts_us_c5e5bf8.jsonl). I relabelled the 1,084 rows and recomputed their keys with Chronicle's algorithm, re-sorted by aggregate_fact_key as tools/build_us_chronicle_feed.py writes it, and compiled the release registry (target period 2024, aged, packaged CD crosswalk, Medicaid substitution):

Microcosm Registry before -> after (full / national_state) Specs added / removed Target values Loss-weight budget partition Capital-gains controls
main 5187fce25 a0698bc97ff9 / d315c75804ef -> a4764a201fd4 / 6863092aa586 0 / 0 128 differ by at most 2.2e-16 relative (float summation order in congressional-district roll-ups after the rows re-sort) identical; no group mixes HT2 and congressional-district rows unchanged (returns: CD US row, factor 0.97964474977721; amount: Table 1.4 ty2023, 0.771900044145164)
#1036 2c811ab1c e02123644d42 / 65e4dde11c83 -> 966a52f02d64 / 903e3a40cbe3 0 / 0 124 differ by at most 2.2e-16 relative identical; no mixing unchanged (both Table 1.4 ty2023)
#1040 011a63b1a 059dc56d78db / 47dce807b412 -> 591c162264a4 / b146a1df2d18 0 / 0 165 differ by at most 2.9e-16 relative identical; no mixing unchanged (both Table 1.4 ty2023)
  • In every compile, loss weights move by at most 8.5e-16 relative and the exclusion receipt is identical.
  • The spec metadata that changes is ledger_measure_concept, ledger_source_concept, ledger_fact_label, ledger_observed_measure_key, ledger_aggregate_fact_key / ledger_fact_key / ledger_legacy_fact_key / ledger_semantic_fact_key, and hierarchy_raw_value (float rounding only).
  • Registry hashes change because they hash that metadata, so a re-pin updates the hash pins it would update anyway.
  • In the re-sorted feed the congressional-district US returns row still precedes Table 1.4's (lines 24,595 vs 38,091), and Table 1.4's amount row still precedes the congressional-district one (22,347 vs 36,821). So main's order-dependent tie resolves as before, even on this pre-Refuse pinned-artifact builds that only relabel another year (#117) #292 feed.
  • Scripts and outputs: ~/PolicyEngine/_reviews/ht2-cg-concept-20260927/microcosm-consumer/scratch/final/.

So, incremental to #292, a re-pin onto this change moves no Microcosm target value, only the ledger_* concept, label and key metadata. (A re-pin from the pinned c5e5bf8 feed onto current Chronicle main does move the returns control, because #292 restamps the congressional-district rows to TY2022; that is #292's effect, handled by microcosm#1036, not this PR's.) The code-level reading was re-checked on microcosm main fdee065e9: no concept id is named, and the chooser and budget key are unchanged.

Chronicle governance

  • Approved agent role: ledger-source-ingestor. docs/concept-migrations.md and the one-line link in docs/architecture.md sit outside that role's allowed_paths; they are the migration note docs/architecture.md requires for a concept rename.
  • Deterministic checks run:
    • python -m chronicle.harness validate-package <id> --year {2022,2023}: valid for all four packages with 0 errors (605, 2,703, 26,880 and 580 source records).
    • build-bundle --year 2023 over the four packages: valid, 30,768 facts, 0 errors, 0 aggregate-duplicate keys. Lineage coverage is 1.0 and agent-acceptance errors are 0 for each package. The only acceptance warning is the standard concept_alignment_validation_skipped.
    • Row-level differential against main: 0 value or lineage changes (above).
    • ruff check chronicle policyengine_chronicle db scripts tests is clean.
    • CI (uv run pytest -q, the full suite, plus the source DB and wheel builds) passes on 0f6b73c, including the new module and the 2,126 default-bundle pin; it re-runs on the review-fix head. Locally the new module passes (all 6 on the Add IRS SOI Historic Table 2 TY2023 national, state broad and state EITC facts #295 simulation tree).
  • LLM judge verdicts:
    • ledger-source-fidelity: PASS. Independent Opus 5.5 review via Subfleet (read-only lane), which re-derived the doc-guide rows, the HT2 and Table 1.4 cells and the identity table, and diffed the YAML against main. Posted below.
    • ledger-contract: no schema or consumer-contract change. Concept ids are package vocabulary; docs/concept-migrations.md records the rename.
    • ledger-boundary: PASS (same review): only vocabulary, docs and tests change; the tests compare published cells only.

Invariants (tests/test_chronicle_soi_capital_gain_concepts.py)

  1. One concept per IRS variable. Every irs_soi measure that names N01000/A01000 in its source_column_id or expected_column_header guard declares the line 7 concept and IRS label, with both header guards naming the variable. The rule holds for any vintage, and the known TY2022 packages must be among them.
  2. Schedule D gain stays on Table 1.4. Only Table 1.4 packages (table_1_4, or a later table_1_4* vintage) declare irs_soi.*taxable_net_capital_gains, and only on net_capital_gains_*.
  3. No retired ids. No package declares irs_soi.returns_with_net_capital_gains / irs_soi.net_capital_gains.
  4. Selector and values unchanged. The consumer rows keep source_measure_id and the published values: HT2 30,465,850 / $1,251,675,034k; congressional district 29,845,710 / $1,157,234,600k; Table 1.4 12,915,122 / $1,269,785,083k.
  5. Differential against publisher bytes. On Chronicle's registered 22in55cmcsv.csv and 22in14ar.xls, N01000 equals Table 1.4's distributions-only + Schedule D gain + Schedule D loss returns within 0.25%, and exceeds 2.3x the gain-only count.
  6. Distinct semantic facts. HT2 and Table 1.4 capital-gain rows for the same year and geography have different semantic keys.

Not changed here

  • HT2 and Table 1.4 still share measure_id net_capital_gains_* for two different measures, against docs/architecture.md's rule. Changing it would move Microcosm's selector (microcosm#1036 and #1040 tell them apart by record-set family), so it is tracked in chronicle#307 with the 10 other HT2/CD concept mismatches.
  • 10 other shared columns (N/A00900, N/A01700, N/A06500, N/A25870, N/A26270) still carry different concepts in HT2 and the congressional-district file.
  • The congressional-district limited_state_local_taxes_* and premium_tax_credit_* columns read the wrong IRS variables. Another session is fixing that separately; this PR leaves those blocks untouched.

Review follow-ups (commit 7c83e89)

  • The guard scan now yields every measure declaration, so a measure with no concept fails instead of being skipped; the Schedule D concept may sit on any table_1_4* vintage package.
  • Shared-column counts are per package; the HT2/Table 1.4 collision is described as latent in the default bundle; the shared measure_id has a tracking issue (chronicle#307), linked from docs/concept-migrations.md.
  • The consumer claim is scoped as incremental to Refuse pinned-artifact builds that only relabel another year (#117) #292 and re-checked on current microcosm main.
  • A second independent re-review of 7c83e89 (Opus 5.5, with a shell) approved and mutation-tested the guards: removing a concept, putting the Schedule D concept on another Table 1.4 measure, and restoring a retired id each fail; a copied table_1_4_2024 package passes. Its P3 wording points (shared-variable counts by IRS variable, "capital-gain rows", what the guard enforces) are fixed in the follow-up doc commit.

🤖 Generated with Claude Code

Historic Table 2 declared N01000/A01000 with Table 1.4's Schedule D
taxable-net-gain concept (irs_soi.returns_with_taxable_net_capital_gains /
irs_soi.taxable_net_capital_gains). Every IRS guide for Historic Table 2
(TY2020-TY2023) and for the TY2022 congressional-district, ZIP and county
files defines them as "net capital gain (less loss)" from Form 1040 line 7:
a Schedule D gain, a limited Schedule D loss, or capital gain distributions
filed without Schedule D. For TY2022 the two counts are 30,465,850 and
12,915,122 returns, yet they shared one semantic fact key.

Historic Table 2 and the congressional-district file now declare
irs_soi.returns_with_form_1040_capital_gain_or_loss /
irs_soi.form_1040_capital_gain_or_loss with the IRS labels. measure_id,
values and lineage are unchanged; Table 1.4 keeps its concept.

- docs/concept-migrations.md records the rename, as docs/architecture.md
  requires, and architecture.md links it.
- tests/test_chronicle_soi_capital_gain_concepts.py requires every irs_soi
  package reading N01000/A01000 (any vintage) to carry the line 7 concept,
  keeps the Schedule D concept on Table 1.4 only, pins the consumer
  selector and values, and checks the concept against the registered
  publisher cells (N01000 = Table 1.4 distributions-only + Schedule D gain +
  Schedule D loss returns within 0.25%).
- The default-bundle snapshot gains 104 semantic-duplicate keys (2,022 ->
  2,126): Historic Table 2 and congressional-district state and US rows now
  share a concept, as 19 of the 31 IRS columns they share already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The guard scan now yields every measure declaration, so a measure missing
  its concept fails the line 7 check instead of being skipped.
- The Schedule D gain concept may live on any Table 1.4 vintage package
  (table_1_4*), still only on the net_capital_gains_* measures.
- docs/concept-migrations.md: count shared columns per package (19 of 31
  national, 24 of 36 state), say the HT2/Table 1.4 collision was latent in
  the default bundle, and point the shared measure_id and the 10 other
  HT2/CD concept mismatches at chronicle#307.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Independent review (Opus 5.5 via Subfleet, read-only lane; job 20260929-080339-review-chronicle-304), of head 0f6b73c. The P2/P3 findings are addressed in 7c83e89 and the updated body; see "Review follow-ups".

ledger-source-fidelity: PASS
ledger-boundary: PASS
Recommendation: APPROVE

This approval assumes CI passes, in particular the 2,126 pin. None of the findings blocks the merge.

Answers to the seven questions

1. Source fidelity: verified.

The doc guides define both variables the way the PR says, in every vintage:

  • Definitions. N01000 is "Number of returns with net capital gain (less loss)" and A01000 is "Net capital gain (less loss) amount". In the reconverted guide text these rows sit at:
    • 20incmdocguide.txt:241-247 and 21incmdocguide.txt:260-266, source "1040: 7"
    • 22incmdocguide.txt:267-273, 23incmdocguide.txt:271-277, 22incddocguide.txt:274-280, 22zpdoc.txt:229-235 and 22incydocguide.txt:284-290, source "1040:7"
  • Form 1040 line 7. The 2022 Form 1040 instructions, p. 31, "Line 7 Capital Gain or (Loss)", say capital gain distributions go on line 7 without Schedule D under "Exception 1". This matches the PR.

HT2 US cells, checked in the IRS CSVs (row 2, US,0):

TY Column N01000 A01000 (thousands)
2020 AI 29,008,620 1,121,205,810
2021 AI 32,996,180 2,050,443,583
2022 AK 30,465,850 1,251,675,034
2023 AK 29,481,840 943,479,996

TY2020–21 has no N00400/A00400, which is why N01000 sits two columns earlier.

Table 1.4 cells:

  • TY2023 (Chronicle's registered 23in14ar.xls cells, checks/bundle/sources/soi-table-1-4/source_cells.jsonl:1260-1264): AJ9 = 3,209,131, AL9 = 12,392,020, AN9 = 13,833,037. The headers are AJ3 " Capital gain distributions\nreported on Form 1040", AL3 "Sales of capital assets reported on Form 1040, Schedule D [3]", AL4 "Taxable\nnet gain" and AN4 "Taxable\nnet loss".
  • TY2020–2022. I couldn't read the .xls files directly. Pub 1304 Table A (irs-citations/p1304.layout.txt:188,190) matches them. Its distributions row reads 3,919,950 / 4,505,544 / 3,980,047. Its "Net capital gain less loss" returns row reads 25,083,935 / 28,571,454 / 26,480,998, and these equal Schedule D gain + loss exactly:
    • 15,918,669 + 9,165,266
    • 20,497,375 + 8,074,079
    • 12,915,122 + 13,565,876 (also printed at :3950)
  • Sums, residuals and ratios. I recomputed every one in the PR table and all match: +0.016%, −0.244%, +0.016%, +0.162%, and ratios 1.82, 1.61, 2.36, 2.38.
  • Extra evidence the PR doesn't use. The amounts reconcile too. For TY2022, Table A net gain less loss is 1,240,910,675 plus 12,863,423 of distributions (:189,191), which is 1,253,774,098. HT2 A01000 is 1,251,675,034, a gap of −0.17%. That supports the "(less loss)" reading for the amount column as well.

Nothing but concept and label changed:

  • I compared against snapshot/wt-main/packages/irs_soi/*/source_package.yaml. In all three packages only the label: and concept: lines differ. Column, ordinal, source_column_id, header guards and value_scale are identical (branch: historic_table_2/source_package.yaml:561-584, historic_table_2_state_broad_2022/…:370-392, congressional_district_2022/…:14126-14148).
  • Table 1.4 is untouched (table_1_4/source_package.yaml:473-497).
  • snapshot/{main,branch}.json show row_count 30,768 on both.
  • One consumer row checked directly: the CD US fact keeps value 29,845,710 and the same lineage, with the new concept and label (checks/bundle/sources/soi-congressional-district-2022/consumer_facts.jsonl:14).

The new id and label describe line 7 accurately. The id names the form line but not the line number, so it survives line renumbering (line 6 in TY2019, for example).

2. Concept choice: sound.

  • One shared pair for one IRS variable follows docs/architecture.md:139-146.
  • The retired CD ids (irs_soi.net_capital_gains) do read as gain-only. Pub 1304 Explanation of Terms (p1304.layout.txt:19016-19020) and IRC §1222(11) both use "net capital gain" for a positive amount only, so dropping those ids is justified.
  • The "19 of 31" figure holds for the national historic_table_2 against CD. I counted 31 shared source_column_ids, with differing concepts on 00900, 01700, 26270, 25870, 06500 and 01000, which is 12. See finding 5 for the state-broad count.

3. Semantic duplicates: +104 is right.

  • snapshot/branch.json has 1,666 groups against 1,562 on main.
  • form_1040_capital_gain_or_loss appears 210 times: 104 groups × 2 members, plus 2 rows_by_concept entries.
  • Rows by concept go from main (CD 480 + HT2 62 + Table 1.4 20 per concept) to branch (542 line-7 + 20 Table 1.4). So HT2 has 62 rows per concept (11 US + 51 state), and the total is 1,084 relabelled rows.
  • No groups are removed, because in this bundle the HT2 rows are TY2022 and the Table 1.4 rows are TY2023, so they never collided.
  • 2,022 + 104 = 2,126, and the comment at tests/test_chronicle_bundle.py:142-151 is consistent. I could not derive the full default bundle myself.

4. Tests: the guard catches the TY2023 clone. The differential test is sound and does real work.

  • On Add IRS SOI Historic Table 2 TY2023 national, state broad and state EITC facts #295's branch, historic_table_2_state_broad_2023/source_package.yaml still has N01000 with label "Returns with taxable net capital gains" and concept: irs_soi.returns_with_taxable_net_capital_gains (fetched from GitHub).
  • _irs_soi_measures globs */source_package.yaml and walks nested measures. _line_7_variable matches on source_column_id. So test_every_line_7_column_carries_the_line_7_concept fails on the label and concept, and test_schedule_d_gain_concepts_stay_on_table_1_4 fails too.
  • The differential test reads the registered bytes, checks the header text, pins the sum, and checks the 0.25% tolerance and the ratio above 2.3.
  • Its header asserts match the TY2023 bytes. That the registered TY2022 file has the same row-3/4 headers and only one sheet is my assumption; I haven't checked it.
  • Brittleness and running the tests are covered in finding 4.

5. Consumer claim: verified by reading the code, with a scoping caveat (finding 2).

  • No concept selector. The six concept ids don't appear anywhere in the local microcosm checkout (*.py/yaml/json).
  • Current origin/main. Microcosm origin/main is now 7683c0978, not the 5187fce25 the PR cites. There, _soi_capital_gains_control_key_from_fact is still keyed on (measure_id, status, universe), and a new _is_stale_soi_historic_capital_gains_fact drops HT2 capital-gains rows by layout.record_set_id (fetched from GitHub). Nothing there reads a concept.
  • Tie-breaking. _prefer_candidate (fiscal_targets.py:2287-2297, local main) returns candidate > current. The later period wins and an equal period keeps the first row in the feed. Since Refuse pinned-artifact builds that only relabel another year (#117) #292, CD rows are TY2022 and Table 1.4 is TY2023, so the period decides and feed order can't flip the control.
  • Budget groups can't merge. _fiscal_target_concept_budget_key (tools/build_us_fiscal_refresh_release.py:6242-6267) uses the metadata-based key only for ledger_geography_level == "congressional_district" specs. HT2 facts are country- or state-level, so they fall in the spec.name branch. Within CD rows the relabel is one-to-one, from net_capital_gains to the new id, and no other CD measure uses the new id, so the partition can't change. The fact keys are in US_FISCAL_TARGET_CONCEPT_METADATA_EXCLUSIONS.
  • The earlier experiment shows the risk is real before Refuse pinned-artifact builds that only relabel another year (#117) #292. In comparison_table.txt:17-22 (the feed_B_end and feed_B_front rows), reordering the feed flips the returns control. In one direction the returns control moves from the CD row (factor 0.9796) to Table 1.4 (0.4068), a value ratio of 0.415. The PR's final measurement, on the real hash-sorted order, shows the order unchanged.

6. Boundary: PASS. The change touches only YAML vocabulary, docs and tests. No reconciliation, aging, imputation, target selection or PolicyEngine-computed value enters Chronicle. The tests only compare published cells.

7. Docs: accurate.

  • The key and field list in docs/concept-migrations.md:6-10 matches the PR's measured field changes.
  • The counts and the Pub 1304 figure of 26,480,998 check out.
  • The CD disclaimer quote is not re-verified here; the guide section wasn't opened.
  • The anchor architecture.md#source-facts-and-microcosm-targets exists (the heading is at architecture.md:112).
  • The architecture edit only adds the link (compared with snapshot/wt-main/docs/architecture.md:144-145).

Findings

  1. P2: the PR now documents a breach of the measure_id rule, with no follow-up issue.
    • docs/architecture.md:142-144 says that within a source_name, measure_id must distinguish different statistical measures.
    • This PR records in-repo that net_capital_gains_* names two different measures: HT2/CD line 7 and Table 1.4's Schedule D gain. test_consumer_selector_survives_the_concept_rename then pins that sharing.
    • Leaving the shared measure_id in place for now is justified, because changing it moves Microcosm's selector.
    • Fix: open a tracking issue and link it from docs/concept-migrations.md "Unchanged" and from the PR's "Not changed here".
  2. P3: the consumer claim should be scoped and use the current SHA.
  3. P2: the PR body still has placeholders. TESTS_LINE, RESULT_BEFORE/RESULT_AFTER and REVIEW_* remain. The 2,126 pin rests on CI alone.
  4. P3: test brittleness and coverage.
    • test_schedule_d_gain_concepts_stay_on_table_1_4 hard-codes the directory name table_1_4. A future Table 1.4 vintage package in another directory will need an edit.
    • _measures skips any measure dict without a concept key.
    • The differential test covers TY2022 only, while the docs claim "within 0.25% in every year TY2020–TY2023". TY2021's −0.244% is at the edge of that tolerance.
    • Runtime: the module builds CD for 2022 (26,880 records) and parses two whole workbooks' cells. That's acceptable, but it isn't session-scoped across modules.
  5. P3: "19 of 31" is ambiguous. It holds for the national historic_table_2 against CD. For historic_table_2_state_broad_2022 against CD, I count 36 shared variables, 24 sharing a concept.
  6. P3: the ingestor role doesn't cover the docs edits.
    • docs/concept-migrations.md and docs/architecture.md fall outside ledger-source-ingestor's allowed_paths (.github/chronicle-agents.yml:7-13).
    • This isn't enforced: tests/test_chronicle_governance.py:46 only checks that the list is non-empty. architecture.md:144-146 requires the note, so say so explicitly in the governance section.
  7. P3: "HT2 and Table 1.4 no longer share a semantic key" describes a latent collision, not one the bundle had. They never shared a key in the measured bundle (TY2022 vs TY2023, −0 groups). It is true at the key level, and test_line_7_and_schedule_d_gain_facts_are_different_semantic_facts shows it for same-year builds.

Not verified

  • I ran no tests and no builds (the session had no shell), including the new test module and the 2,126 default-bundle pin.
  • I couldn't run git diff origin/main...HEAD. I used _reviews/.../snapshot/wt-main as main and couldn't confirm it is at d48acbf.
  • Table 1.4 TY2020–2022 .xls cells weren't read directly (corroborated through Pub 1304 Table A instead). The registered TY2022 22in14ar.xls headers and single sheet weren't checked.
  • On microcosm pr/1036, pr/1040 and origin/main, I saw only partial function bodies (via GitHub). I didn't re-run the PR's registry compiles.
  • I didn't re-run Add IRS SOI Historic Table 2 TY2023 national, state broad and state EITC facts #295's simulation.
  • I didn't open Schedule D (2022) lines 16/21 or the Form 1040 face; I only checked the instructions, p. 31.

@MaxGhenis
MaxGhenis marked this pull request as ready for review September 29, 2026 21:54
From the second review of chronicle#304:
- Count shared IRS variables by the header each measure reads, not only by
  source_column_id: before this change 26 of 38 (national) and 24 of 36
  (state) carried one concept in Historic Table 2 and the
  congressional-district file, with the same 12 exceptions. The first
  commit's message said 19 of 31, counting source_column_id only.
- Only the capital-gain rows stop sharing a semantic key with Table 1.4.
- State what the guard enforces: measures that name N01000/A01000 in a
  source_column_id or expected_column_header guard.
- Give Table 1.4's pre-TY2022 column numbers and say which year CI checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Second independent review (Opus 5.5 agent with a shell, read-only), of follow-up 7c83e89. It superseded a Subfleet re-review job that sat unassigned in the queue.

Recommendation: APPROVE. No blocking findings.

  • All seven first-round findings are resolved, or deliberately left open (TY2022-only differential). None was renamed or suppressed.

  • Guards, mutation-tested in throwaway worktrees:

    Mutation Result
    Delete concept: from HT2 N01000 The line-7 guard fails. The pre-fix test passed this mutation; the loader also rejects it.
    Put irs_soi.taxable_net_capital_gains on Table 1.4 wages_salaries_returns The Schedule D guard fails.
    Copy table_1_4 to table_1_4_2024 Passes, as intended.
    Restore irs_soi.net_capital_gains on CD A01000 The line-7 and retired-id guards fail.
  • _measures scope. All 3,668 measure_id dicts across the 19 irs_soi packages are record_sets[*].measures[*] declarations, and every one has a concept.

  • "A TY2022 build of both did collide": verified.

    • On main's YAML, HT2 US and Table 1.4 all-returns share ledger.semantic_fact.v2:dcb34c4c… (returns) and …30c10b13… (amount).
    • At head the keys differ. Capital-gain collisions go from 14 to 0.
  • Consumer claim, re-checked on microcosm main fdee065e9:

    • None of the six ids is named.
    • The chooser, _fiscal_target_concept_budget_key and the exclusion set are byte-identical to 5187fce25.
  • Boundary: PASS. The diff touches only docs and tests.

  • P3 wording points, fixed in 8e4c3b7:

    1. Count shared variables by IRS variable (26 of 38 national), not by source_column_id (19 of 31).
    2. Only the capital-gain rows stop sharing a semantic key with Table 1.4. 70 other TY2022 keys still overlap by design (wages, IRA, pensions and more).
    3. The guard enforces measures that name N01000/A01000 in a source_column_id / expected_column_header guard. A measure declared by column letter alone is not covered. A cloned vintage keeps both guards, so Add IRS SOI Historic Table 2 TY2023 national, state broad and state EITC facts #295 is still caught.
    4. The differential pins TY2022 only; the doc now says so.

@MaxGhenis
MaxGhenis merged commit ce43336 into main Sep 30, 2026
2 checks passed
@MaxGhenis
MaxGhenis deleted the ht2-net-capital-gain-less-loss-concept branch September 30, 2026 00:44
MaxGhenis added a commit that referenced this pull request Oct 4, 2026
Resolves the default-bundle pins in tests/test_chronicle_bundle.py: main's
counts after chronicle#302/#304 plus this PR's 1,020 facts and one package
(fact_count 409,165; source_package_count 269), and both period-increment
blocks.

Co-Authored-By: Claude Opus 5.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