Skip to content

Count only the head's and spouse's income in the EITC investment income test (26 U.S.C. 32(i)) - #9883

Merged
MaxGhenis merged 11 commits into
mainfrom
eitc-dependent-investment-income
Oct 7, 2026
Merged

MaxGhenis merged 11 commits into
mainfrom
eitc-dependent-investment-income

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

eitc_relevant_investment_income summed every tax unit member's interest, dividends, capital gains, rents and passive income with add. A tax unit dependent's income is on the dependent's own return, so it could deny the parent's EITC, and a dependent's loss could offset the parent's own income and grant it. The variable now sums the head and spouse only (tax_unit_non_dep_add). The capital gain line is the head and spouse's long- and short-term gains and losses plus their capital gain distributions, each floored at zero. The capital gain line is floored at zero, as Worksheet 1 line 5 requires. The tax unit's net_capital_gains is not read, so an amount supplied there no longer counts (below). Publication 596 Worksheet 1's separate baskets are unchanged: portfolio income is not floored (worksheet lines 1–3 are not negative), and the capital gain line and the netted passive line are each floored on their own.

This fixes item 1 of #9635. Items 2 and 3 (passive income that is also partnership self-employment earnings, and farm rent) are not in this PR, so #9635 stays open.

Every number below comes from running main (4c900b6d3d) and this branch. 2025, TX, head of household aged 30 with $20,000 of wages and one qualifying child aged 8:

Case eitc_relevant_investment_income main → branch eitc main → branch
No investment income (control) 0 → 0 4,328 → 4,328
Child has $15,000 of qualified dividends 15,000 → 0 0 → 4,328
Child has a $15,000 long-term capital gain 15,000 → 0 0 → 4,328
Child has $12,500 of taxable interest and $2,000 of tax-exempt interest 14,500 → 0 0 → 4,328
Head: interest 9,000, LTCG 4,000; child: STCG −4,000 9,000 → 13,000 2,785.93 → 0
Head: interest 9,000, rental 4,000; child: rental −4,000 9,000 → 13,000 2,785.93 → 0
Head: interest 9,000, passive income 4,000; child: passive loss −4,000 9,000 → 13,000 2,785.93 → 0
Head: interest 7,000, LTCG 5,000, distributions input −2,000 10,000 → 12,000 2,945.73 → 0
Joint: head interest 3,000, LTCG 6,000; spouse dividends 5,000, STCG −3,000; child interest 10,000, LTCG 10,000 31,000 → 11,000 0 → 4,243.31

AGI is the same on main and the branch in every row (20,000 in the first four rows; 33,000, 33,000, 33,000, 32,000 and 31,000 after that). irs_gross_income already leaves dependents out, and #9647 did the same for their losses. The 2025 investment income limit is $11,950.

Household sweep (another session, bbf31ca6):

  • Setup: 46d22d861f merged onto main b79705b4bd, compared with main, over 504 households. TY2021–2026 × TX/MN/NM/WI × heads aged 35, 70 and 72 with Social Security, each with a child aged 10 holding $50,000 of taxable interest, tax-exempt interest, qualified dividends, LTCG, STCG or rent, plus no-income controls.
  • On main, the child's income removes the head's federal EITC ($3,221.70–$4,427), MN child and working family credits ($2,100–$2,187.60), NM EITC ($644.34–$1,106.75) and WI EITC ($128.87–$177.08).
  • On the branch, the child's income moves none of eitc, eitc_relevant_investment_income, eitc_investment_income_eligible, mn_child_and_working_families_credits, nm_eitc, wi_earned_income_credit or AGI in any row. The controls match main.
  • The remaining differences are outside this PR:
  • Evidence: ~/reviews/mo-capital-gains-line-7a-2026-10-06/eitc-9883-check/.

The fix cuts both ways. Filers whose only disqualified income was a dependent's regain the credit. Filers whose dependents' losses had pulled their own investment income under the limit lose it.

Law and forms

  • 26 U.S.C. 32(i)(1) denies the credit if "the aggregate amount of disqualified income of the taxpayer" exceeds the limit.
  • 32(i)(2):
    • (A) counts "interest or dividends to the extent includible in gross income for the taxable year". A dependent's are not includible in the parent's gross income unless the parent elects Form 8814.
    • (D) counts "the capital gain net income (as defined in section 1222) of the taxpayer".
  • 2025 Publication 596, Worksheet 1 is filled in from the filer's own Form 1040:
    • Lines 1–3 take Form 1040 lines 2b, 2a and 3b.
    • A child's income enters only through a Form 8814 election: line 2 adds "any amount on Form 8814, line 1b", line 4 takes Schedule 1 line 8z "that is from Form 8814", and the elected dividends and distributions reach lines 3 and 5 through Form 1040 lines 3b and 7a. The model does not implement the election, which can overstate the credit for a parent who makes it.
    • Line 5 is Form 1040 line 7a, or zero "If the amount on that line is a loss".
    • Lines 11–13 net passive income and losses and floor the result at zero.
  • 2025 Form 8814, line 4: from $13,500 the child "must file his or her own return".
  • A negative non_sch_d_capital_gains input cannot be a Form 1099-DIV box 2a amount. As in irs_gross_income, loss_limited_net_capital_gains and filer_loss_limited_net_capital_gains, each filer's distributions are floored at zero. Before, the input was added unfloored and could cancel part of a real gain.

Code

policyengine_us/variables/gov/irs/credits/earned_income/eitc_relevant_investment_income.py:

  • Portfolio basket: tax_unit_non_dep_add over taxable interest, tax-exempt interest and dividends.
  • Capital gain line: max_(0, the filers' LT and ST gains + Σ filers' max(0, non_sch_d_capital_gains)). This is the filers' Schedule D line 16 with distributions on line 13, and Form 1040 line 7a when positive.
  • Passive basket: tax_unit_non_dep_add over rental_income and passive_partnership_s_corp_income, netted across the head and spouse before the zero floor.
  • Reference updated from the 2021 to the 2025 Publication 596; documentation and a Form 8814 reference added.

The tax unit's net_capital_gains is not read. The capital gain line sums the head's and spouse's person-level long_term_capital_gains and short_term_capital_gains, as the NIIT base (filer_loss_limited_net_capital_gains, #9784) already does. AGI also never reads the tax-unit total: irs_gross_income counts the person-level capital_gains (on the differences, see the follow-ups below).

  • Behavior change. On main, a net_capital_gains amount supplied for the tax unit counted here. Now it doesn't. A YAML case pins this: supplying 12,000 alone gave 12,000 on main and gives 0 here, with AGI 0 on both.

  • Nothing supplies it today. The pinned microdata (populace_us_2024.h5) has no net_capital_gains column; its capital gain inputs are person-level. A gh search code across PolicyEngine repos finds no input use (the only hits read it as a calibration target).

  • Why not keep it. Earlier heads tried: subtract dependents' person-level gains from the supplied total (the convention of Use the filers' Schedule D for the tax on net capital gain #9855's filer_schedule_d_lines line 16), and detect "supplied" by value, then by core's input record. Two problems made that unworkable:

    • Core stores the total in float32, so subtracting a dependent's large gain moved the filers' amount. Reproduced: a child's $20,000,000 gain flipped the head between 11,950 and 11,951.
    • Core records inputs per variable, not per tax unit, so no test can tell which units supplied a value.

    Four review rounds each found a case where a dependent's gain still moved the filer: float32 collisions (including a supplied total that moves with the child's gain), abolished values, stale cached values, and default-filled units in mixed situations. The person-level sum is the route the task specified while Use the filers' Schedule D for the tax on net capital gain #9855 is unmerged, and it makes the dependent invariant hold for every input. If aggregate support is wanted, the clean route is a per-unit interface or a filer-only net_capital_gains. That would be a separate PR.

  • Float32. Summing the filers' own gains is exact whatever a dependent has. Two YAML boundary cases with a $20,000,000 child pin it.

Downstream. Every state credit computed from the federal EITC moves with it. These read this variable, or its eligibility flag eitc_investment_income_eligible, directly or through the shared calculate_eitc_like_amount helper:

  • federal eitc_eligible
  • CA EITC (ca_eitc_eligible), and through it the young child tax credit, the foster youth tax credit and San Francisco's WFTC
  • CO EITC (the ITIN and under-25 paths)
  • DC EITC (both the with-child and without-child credits)
  • IL EITC
  • IN EITC (in_eitc_eligible)
  • MD EITC (federal_eitc_without_age_minimum)
  • ME EITC (me_pro_forma_childless_eitc)
  • MN WFC (mn_wfc_eligible)
  • MO WFTC (mo_wftc_eligible)
  • NJ EITC (nj_eitc_income_eligible)
  • NM EITC (nm_eitc_eligible)
  • OK EITC (ok_federal_eitc_eligible, ok_federal_eitc_investment_income_eligible)
  • WA WFTC and its age-expansion eligibility
  • WI earned income credit
  • the MT HB268 and NY WFTC contrib reforms

States with their own investment income limits can change where the federal one doesn't. Example, run on main and this branch: CA's 2025 limit is $4,814. A head of household aged 30 with $20,000 of wages, whose child aged 3 has $5,000 of interest, goes from CalEITC 0 → 388.21 and the young child tax credit 0 → 1,189. The federal EITC is 4,328 on both.

Invariants

These hold for every input with nonnegative interest and dividends, and the property test checks them:

  1. A dependent's investment income, of any sign and any finite size, never changes the filer's eitc_relevant_investment_income, eitc_investment_income_eligible, eitc, filer_loss_limited_net_capital_gains or AGI. That includes dependents' gains up to $64 million, where float32 rounding of a tax-unit total would show.
  2. Differential: eitc_relevant_investment_income equals an independent numpy computation of Worksheet 1 over the head and spouse: interest, tax-exempt interest and dividends, plus max(0, their LT + ST gains + per-filer max(0, distributions)), plus max(0, their rental + passive income). Eligibility is exactly that amount ≤ the limit, and eitc is 0 whenever it exceeds the limit.
  3. Bounds: the result is at least the filers' interest and dividends, so never negative. With interest in the drawn range, $1,000 more of the head's interest raises it by exactly $1,000. (Near $1 billion, float32 storage of the input itself rounds; Sol noted a $1,024 step there.)
  4. For tax units without dependents and without a negative distributions input, it equals the previous all-member formula, so those units see no change. This holds with filer gains in the drawn range, ±$12,000. Past about $16.8 million, the previous formula's float32 tax-unit total could be off by a few dollars, and the direct sum corrects that. Example from the Sol delta review: a head with LTCG −20,000,000, STCG 1 and distributions 20,000,000 had 0 on the capital line and now has 1. A unit with a supplied tax-unit net_capital_gains also changes, by design, because that total is no longer read.

Two intended exceptions:

  • A negative interest input passes through the unfloored portfolio basket, as on main. Worksheet lines 1–3 are not negative.
  • Amounts large enough to overflow float32 (about 3.4 × 10^38) turn into infinity or NaN and are out of scope.

Other routes by which a dependent's income still reaches the filer. These are outside this formula and present on main too. The bafe4fe reviews reproduced them, and each is queued as separate follow-up work (not in this PR, no GitHub issue yet):

  • tax_unit_is_required_to_file: a child's wages can turn off the filer's required filing, and with it the EITC.
  • taxable_unemployment_insurance: the 2020 exclusion pools a child's unemployment compensation, and a negative child amount changes the filer's.
  • tax_unit_social_security_for_taxability: subtracting dependents from a float32 total.
  • A person-level capital_gains set directly, as the capital-gains marginal-rate branch does, reaches AGI but not this test or the NIIT base. So that marginal rate misses the EITC investment-income cliff.

Tests

  • YAML. policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_relevant_investment_income.yaml has 12 cases, each with hand arithmetic in comments:

    • The 9 cases in the table above: 8 fail on main, and the control passes on both.
    • Two $20,000,000-child boundary cases. They fail on e993a95bc1, which subtracted from the float32 total.
    • One case pinning that a supplied tax-unit net_capital_gains is not read. It fails on main by design.
  • Downstream pins from session bbf31ca6's sweep, with hand arithmetic. Both fail on main and pass here:

    • mn_child_and_working_families_credits.yaml, 2025: a child's $50,000 of interest no longer removes MN's credits, 2,129.20 = 1,750 CTC + 4% × 9,480.
    • nm_eitc.yaml, 2025: a child's $50,000 LTCG no longer removes NM's EITC, 1,082 = 25% × 4,328.
  • Python. policyengine_us/tests/core/test_dependent_eitc_investment_income_invariants.py checks invariants 1–4 in 3 tests:

    • Hypothesis, derandomize=True, 5 examples, each a batch of 10–40 tax units: single, head of household, joint, and joint with dependents. Dependents' capital, rental and passive inputs take either sign, including negative distributions. Their gains sometimes reach $64 million in multiples of 16, which float32 holds exactly. Interest and dividends are drawn nonnegative, and the passive share equals the drawn partnership_s_corp_income.
    • A seeded population of 200 units for 2021 and 2025.

    Each batch runs as one vectorized simulation three times: as drawn, with the dependents' inputs zeroed, and with $1,000 more of the head's interest. YAML can't state a property over all inputs, which is why this one is in Python.

    It fails on main, where all 3 tests fail. With the current seeded population (which includes dependents' gains up to $64 million), 103 of 200 units differ from main, by up to $117,211,907; Sol's final review measured this. The 107-of-200 and $65,768 figures came from the earlier population, without the large gains. It also fails on e993a95bc1, the float32 subtraction, where all 3 fail: one Hypothesis batch is off by $1, and the seeded run has 19 of 200 units off by up to $5.

    Measured locally on macOS (Apple silicon, Python 3.13, host heavily loaded), not on Linux CI: the three tests' calls take 2.0 s (1.45 + 0.34 + 0.25 s), 30.5 s wall including import and system build, 1.13 GB peak RSS. It lands in the core group of make test-other-python-rest, which the Makefile times at 142 s.

  • Existing files that read this variable, run locally one file at a time. All pass.

    • All 17 listed below passed at e1d780bc96, whose capital gain line is the same as this head's: max(0, filer_loss_limited_net_capital_gains), which equals the filers' gains plus floored distributions when positive. None of the 17 supplies net_capital_gains (checked with grep).
    • At bafe4feeb2 I re-ran these, and all pass: eitc_investment_income_baskets (3), the partner file (15), mn_child_and_working_families_credits (18), nm_eitc (4), nj_eitc_investment_income_baskets (6), and this PR's YAML (12) and Python tests (3). At 257fe3444a I also ran nj_eitc_income_eligible (3).

    The 17 files:

    • federal: eitc_investment_income_baskets, eitc_investment_income_eligible, earned_income_tax_credit;
    • states: ca_eitc_eligible, co_eitc, dc_eitc, dc_eitc_with_qualifying_child, dc_eitc_without_qualifying_children, il_eitc, in_eitc_eligible, mo_wftc_eligible, mo_wftc, ok_federal_eitc_investment_income_eligible, wa_working_families_tax_credit, wi_earned_income_credit;
    • contrib: mt_hb268;
    • partners: partners/analytics_coverage/edge_cases/federal/tax_credits/eitc/federal.yaml. Its cases put the capital gain on the head, so nothing changes; no partner file is edited.

Reviews

Two independent reviews ran on the same brief at e993a95bc1. Both are in ~/reviews/eitc-dependent-investment-income-2026-10-06/.

  • Opus 5.5 (Subfleet --tier standard), review-opus.md: APPROVE, with three should-fixes. All three are fixed: the downstream list is now complete, the NJ files are run, and the changelog mentions the distributions floor. Its nits on the docstring and the Form 8814 wording are fixed. Its nit on the hard-coded gain components stands. The formula sums long- and short-term gains, the same components as net_capital_gains.adds, and irs_gross_income's capital_gains.

  • GPT-6.1 Sol (Subfleet --tier hard), review-sol.md: REQUEST_CHANGES.

    • Its blocking finding was the float32 subtraction. I reproduced it in the model, fixed it, and added tests.
    • Its should-fixes were to scope the invariant claims (done above) and complete the downstream list (done).
    • Its nit was the Form 8814 routing: an election also reaches Worksheet 1 lines 3 and 5 through Form 1040 lines 3b and 7a (2025 Instructions for Form 8814, lines 9, 10 and 12). The documentation is corrected.
  • Opus delta review of e993a95bc1 → 46d22d861f, review-delta-opus.md: APPROVE.

    • Its should-fix was that the value comparison misfires on an abolished or stale net_capital_gains. I reproduced it and fixed it in 069db9a27a with the input record.
    • Its nits are fixed: the INTEREST_SHIFT comment, the long YAML header line, a note that a supplied amount is stored in float32, and a test guarding the adds list.
  • Sol delta review of e993a95bc1 → 46d22d861f, review-delta-sol.md: REQUEST_CHANGES.

    • It confirmed that the float32 fix works (19 → 0 seeded mismatches).
    • Its blocking item. A supplied aggregate that equals the members' float32 total takes the direct-sum path, where its reading of the convention would subtract the child from the stored total. My reply: in that case aggregate minus dependents is the filers' own gains. The exact sum gives the head's true 1 or 3, while the subtraction would give 0 or 4 from rounding. So it is kept as intended, documented in the code and pinned in YAML (f4c4e66406).
    • Its should-fix (invariant 4 overclaimed at extreme values) is fixed above.
    • Its nits: the summary's portfolio-floor wording is fixed. The INTEREST_SHIFT comment was already fixed in 069db9a27a.
    • The stale-cache case it raised is handled by the input-record check in 069db9a27a, which came after the head it reviewed.
  • Final round at f4c4e66406:

    • Opus (review-final-opus.md): APPROVE. It agreed with the reply on Sol's collision point. Its should-fix: a value supplied for one tax unit gives the others a recorded default of 0.
    • Sol (review-final-sol.md): REQUEST_CHANGES. Its blocking case was a supplied total that moves with the child's gain (+2 each) yet switches authority at the float32 collision and flips eligibility. It also found a deleted-but-recorded input and abolition together with a supplied total.

    Both are right that value tests can't establish which units supplied the aggregate. The resolution is in bafe4feeb2: stop reading the tax unit's net_capital_gains (Code section above). That makes every one of those cases moot. The is_input helper and the aggregate-specific tests are removed.

  • Review of bafe4feeb2: Sol (review-final2-sol.md) and Opus (review-final2-opus.md) both APPROVE, with no blocking findings.

    • Sol reran the files: federal YAML 12 passed (11 fail on main), MN 18, NM 4, property tests 3 (3 fail on main).
    • Their should-fixes:
      • The changelog now discloses that a supplied net_capital_gains is not read.
      • The docstring now says a directly set person-level capital_gains is not read.
      • The code comment gives the actual reason.
      • In this body, invariant 4 is qualified, "any size" now says finite, and the property-test figures are refreshed. The property test's own docstring keeps its tested domain, which is finite inputs with gains entered per person.
      • The other dependent-income routes and the capital_gains marginal-rate gap are listed above as follow-ups, since they exist on main and lie outside this formula.
    • These changes are docs only, in 6dae716b9c.
  • Review of 6dae716b9c (the docs-only delta), by an independent in-session Opus 5.5 agent: APPROVE.

    • It confirmed that only the changelog, the documentation string and comments changed: an AST comparison of the formula is identical.
    • It confirmed the new text matches the code and policyengine-core 3.32.15: float32 storage, inputs recorded per variable rather than per unit, and default_array fill.
    • Its wording points on this body are fixed.

Microsimulation impact

One 2026 run of each, from lockrun.sh on the populace-us 2024 dataset (populace_us_2024.h5@populace-us-2024-spm-20260909). Main (4c900b6d3d) is compared with this branch (bafe4feeb2), record by record (compare.py).

Variable Records changed Weighted tax units changed Weighted total change
eitc_relevant_investment_income 1,742 (1,595 down, 147 up) 4,138,331 (3,649,134 down, 489,197 up) −$25.95 billion (largest $1,109,032)
eitc_investment_income_eligible 33 (all newly eligible) 77,404
eitc, income_tax, state_income_tax, household_net_income 0 0 0
ca_eitc, wi_earned_income_credit, mo_wftc, ok_eitc, in_eitc, wa_working_families_tax_credit 0 0 0
filer_loss_limited_net_capital_gains 0 0 0

What the numbers show:

  • Investment income. Taking dependents' income out lowers the test amount for 3,649,134 weighted tax units. It raises it for 489,197, whose dependents' losses had been offsetting the filers' own income.
  • Eligibility. 33 records (about 77,404 weighted tax units) pass the investment income test that failed it on main. None moves the other way.
  • Credits. No record's EITC, state credit, tax or net income changes in 2026. The newly eligible units get no credit in either run. These runs don't record which other condition keeps it at zero (earned income, phase-out, the filer test or take-up). So in this dataset the fix changes the investment test without moving dollars.
  • Where it does bind. The fix matters for households like those in the YAML cases and the bbf31ca6 sweep above, where it moves up to the full credit.

Raw output: ~/reviews/eitc-dependent-investment-income-2026-10-06/impact-9883-2026.json. Per-record arrays: ~/reviews/pe-us-merge-backlog-2026-10-02/impact/out/{eitcdep-base,eitcdep-pr}.2026.*.

Axiom

axiom: TheAxiomFoundation/rulespec-us#1416 queued

rulespec-us encodes only 32(i)(1): us/statutes/26/32.yaml rule eitc_investment_income_eligible compares an opaque eitc_relevant_investment_income input with the limit. 32(i)(2) is queued in TheAxiomFoundation/rulespec-us#1416 (pe-parity, dispatch-ready). Its review_finding already requires "Every amount is the taxpayer's own (both spouses on a joint return), not a dependent's". This comment adds the sources for that scope, a pasteable review_finding addendum and six hand-computed TY2026 companion cases (J1–J6) matching the YAML cases here. The signed-encoder dispatch is decision d439, which Max has parked. The one route by which a child's interest, dividends and distributions do reach a parent's Worksheet 1 is the §1(g)(7) Form 8814 election, which PE doesn't model. It is queued separately in TheAxiomFoundation/rulespec-us#1424.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 6, 2026 08:31
…come

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

Start from net_capital_gains, take out dependents' gains and losses, and add
the filers' floored capital gain distributions, so a tax unit amount supplied
for net_capital_gains still counts, as it did before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 6, 2026 10:00
… supplied

net_capital_gains is stored in float32, so subtracting a dependent's very large
gain from it could move the filers' capital gain line by a few dollars and flip
eligibility at the limit (independent review finding, reproduced in the model:
a child's 20,000,000 gain turned 11,951 into 11,950). Use the head's and
spouse's person-level gains, and read net_capital_gains only where it differs
from the members' own gains, that is, where a tax unit amount was supplied.

Also widen the property test to dependents' gains of 16 to 64 million, add the
two boundary cases, correct the Form 8814 explanation (an election also reaches
Worksheet 1 lines 3 and 5) and mention the distributions floor in the changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comparing values alone read an abolished or stale net_capital_gains as a
supplied aggregate and subtracted dependents' gains from it (Opus delta
review S1; with gov.abolitions.net_capital_gains a child's 5,000 gain made the
parent eligible). Check core's input record (is_input, next to the existing
_input_keys in tools/period_branch.py) as well.

Add a test for that branch and a guard that the formula's gain list matches
net_capital_gains.adds; fix a stale test comment and a long YAML header line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow invariant 4

A supplied net_capital_gains that equals the members' own gains and losses in
float32 adds nothing to them, so the filers' gains are summed exactly; two YAML
cases pin both rounding directions as intended (Sol delta review). Invariant 4
holds for filer gains in the drawn range; past about 16.8 million the previous
formula's float32 total could be off by a few dollars, which the direct sum
corrects.

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

Return to the capital gain line the task specified: the head's and spouse's
long- and short-term gains (tax_unit_non_dep_add) plus their floored capital
gain distributions. Reading a supplied tax unit net_capital_gains needs to
know which tax units supplied it, which core does not record, and every
value test for it let a dependent's gain move the filers' result somewhere
(four review rounds: float32 collisions, abolished, stale and default-filled
values). A supplied tax unit total is now not read, as in irs_gross_income and
the NIIT base; no PolicyEngine repo or the microdata supplies it. Drop the
is_input helper, the cases that pinned the aggregate reading and the
input-record test; pin instead that a supplied total is not read.

Add two downstream pins from a 504-household sweep (session bbf31ca6): a
dependent's income no longer removes MN child and working family credits or
the NM EITC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ad; clarify the capital gain comment

Docs only, from the bafe4fe reviews: the changelog now says the test reads
the head's and spouse's long- and short-term gains and no longer a supplied
tax unit net_capital_gains; the variable documentation also says a
capital_gains amount set directly on a person is not read; the code comment
gives the actual reason the tax unit total is not read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 6, 2026 20:36
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Handing this PR to the US + core hub at head 6dae716b9c. Code and reviews are done; CI is what's left.

Head: 6dae716b9c2b41d1282339ce269503faee32ecb6. It is ready for review, not a draft, and MERGEABLE.

Reviews. Everything is in ~/reviews/eitc-dependent-investment-income-2026-10-06/.

  • bafe4feeb2 (the last code change): APPROVE from both reviewers.
    • GPT-6.1 Sol: review-final2-sol.md. It reran the federal YAML (12 pass; 11 fail on main), MN 18, NM 4 and the property tests (3 pass; 3 fail on main).
    • Opus 5.5: review-final2-opus.md.
  • 6dae716b9c (docs only: the changelog, the documentation string and a code comment): APPROVE from an independent in-session Opus 5.5 agent. Subfleet was held for low disk, and CLAUDE.md allows an in-session agent for small checks. It confirmed by AST comparison that no executable line changed.
  • Earlier rounds (review-opus.md, review-sol.md, review-delta-*.md, review-final-*.md) raised the supplied-net_capital_gains edge cases. bafe4feeb2 resolved them by not reading the tax-unit total at all, as the task specified while Use the filers' Schedule D for the tax on net capital gain #9855 is unmerged.

Tests run locally, one file at a time, all passing at bafe4feeb2 or later:

  • eitc_relevant_investment_income.yaml (12)
  • tests/core/test_dependent_eitc_investment_income_invariants.py (3)
  • eitc_investment_income_baskets (3)
  • the partner EITC file (15)
  • mn_child_and_working_families_credits (18)
  • nm_eitc (4)
  • nj_eitc_investment_income_baskets (6)

Sol also reran these independently.

Microsimulation: 2026, main versus bafe4feeb2; see the PR body. Investment income changes for 4.1 million weighted tax units, and 33 records newly pass the test. No record's EITC, tax or state credit changes. JSON: impact-9883-2026.json.

Pending: CI (32 checks queued behind the Actions backlog; 3 pass so far, none failed).

Depends on: nothing. #9889 (items 2 and 3 of #9635) is stacked on this PR and must merge after it.

Holds or decisions: none for this PR. On the Axiom side, the encoder dispatch for TheAxiomFoundation/rulespec-us#1416 is Max's d439, which is parked. It doesn't block the merge (axiom: … queued).

Follow-ups owed, queued as session chips (outside this PR):

  • dependents' income in tax_unit_is_required_to_file, taxable_unemployment_insurance and tax_unit_social_security_for_taxability;
  • reading the person-level capital_gains in the EITC test and the NIIT base, so the capital-gains marginal rate sees the EITC cliff.

🤖 Generated with Claude Code

# Conflicts:
#	policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/credits/mn_child_and_working_families_credits.yaml
@MaxGhenis
MaxGhenis merged commit 199ede1 into main Oct 7, 2026
35 checks passed
@MaxGhenis
MaxGhenis deleted the eitc-dependent-investment-income branch October 7, 2026 20:38
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub sweep audit (2026-10-07): squash-merged #9883 as 199ede148d64ab63dc476bda775b60e19ce97b10.

Reviewed head: 33dca4af30c99ebaac95dca5c9d76914b0f27a32.
Independent approval: GPT-6.1 Sol, /Users/maxghenis/reviews/us-hub/reviews-gen/us-9883/review-r5.md.
Latest exact-head Pull request CI: https://github.com/PolicyEngine/policyengine-us/actions/runs/37666333052 (success).
Live gates: gh pr checks exited 0; MERGEABLE; ready; no CHANGES_REQUESTED; no unresolved Max hold; no semantic area overlap since CI base.
Merge used --match-head-commit to pin the approved head.
Approval-only --admin bypass under ~/.claude/CLAUDE.md; one required approving review was the sole branch-protection block.

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