Skip to content

Make household state tax totals equal each state's own income tax - #10039

Merged
MaxGhenis merged 8 commits into
mainfrom
fix-wi-retirement-exclusion-household-accounting
Oct 18, 2026
Merged

MaxGhenis merged 8 commits into
mainfrom
fix-wi-retirement-exclusion-household-accounting

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Household net income now subtracts each state's own final income tax. Before this change, five state taxes applied a rule that the household totals never saw, so household_net_income disagreed with state_income_tax or with the state's own xx_income_tax.

#10026 (merged) moved the Wisconsin retirement income exclusion election into wi_income_tax_before_refundable_credits and wi_refundable_credits, which closed the Wisconsin gap. This PR closes the same gap in four other states, removes the overrides that hid it, and adds a test that holds every state to the identity.

Mechanism (read from code)

household_net_income adds household_refundable_tax_credits and subtracts household_tax_before_refundable_credits. The state parts of those are household_state_tax_before_refundable_credits (the gov.states.household.state_income_tax_before_refundable_credits list plus state_use_tax) and household_refundable_state_tax_credits (the gov.household.household_refundable_state_credits list). Neither reads state_income_tax. The state list reads each state's before-refundable-credits amount, with one exception: North Carolina's entry is its final nc_income_tax. So any rule a state other than North Carolina applies only inside its own xx_income_tax, or only in an override inside state_income_tax, never reaches household net income.

State Rule missing from the household totals (and, except for Wisconsin and New Jersey, from state_income_tax) Fix
Wisconsin Retirement income exclusion election: state_income_tax used an override to wi_income_tax, which took min_(standard, exclusion); the household path used the standard tax #10026 put the election in the components. This PR drops the WI override in state_income_tax and household_state_income_tax, which become plain adds/subtracts
New Jersey None: the override to nj_income_tax was redundant, since nj_income_tax_before_refundable_credits already applies the filing-threshold zero-out Override dropped
Vermont vt_income_tax adds vt_child_care_contributions (Form IN-111 line 21); the list did not Added to the list
Colorado co_income_tax adds co_amt (DR 0104 line 14, inside the line 16 subtotal, before refundable credits on line 31); the list did not Added to the list
Mississippi ms_income_tax subtracted nonrefundable credits (Form 80-105 lines 18-19); the list held ms_income_tax_before_credits_unit, before those credits. ms_income_tax also ignored ms_refundable_credits New ms_income_tax_before_refundable_credits = max(0, tax before credits − nonrefundable credits); ms_income_tax = that − ms_refundable_credits; the list uses it
Michigan (contributed surtax) The mi_surtax reform adds mi_surtax to mi_income_tax; the list did not, so the surtax never reached household net income. Zero unless gov.contrib.states.mi.surtax.in_effect Added to the list

The abolish-federal-income-tax and abolish-payroll-tax reforms sum a separate hand-kept list, gov.household.household_tax_before_refundable_credits, with the same gaps and more: it still holds ms_income_tax_before_credits_unit and lacks co_amt, vt_child_care_contributions and mi_surtax. #10071 removes that list and those reforms, so this PR leaves it alone and the two do not conflict. If #10071 does not land, that list needs the same four changes.

Baseline ms_refundable_credits has no formula, so ms_income_tax is unchanged in baseline. Under the contributed Mississippi EITC reform it now subtracts the credit, as state_income_tax already did.

Sources for expected values

Some cases are structural rather than sourced: the Colorado case (tax 4,000, AMT 500, refundable credits 100), the Vermont case (tax 1,000, refundable credits 200), the Mississippi credit of 159, and the two Wisconsin election cases in household_refundable_state_tax_credits.yaml set chosen inputs. For those, only the line structure (what adds before refundable credits) comes from the cited form. The Wisconsin household cases use whole-return arithmetic from those tables. For example, a single filer aged 68 with a $40,250 pension, $24,000 of Social Security and a $5,000 long-term gain: with credits, line 7 = $43,750, standard deduction $10,656, taxable income $32,144, tax $1,282; with the $24,000 exclusion, line 7 = $19,750, standard deduction $13,536, taxable income $5,264, tax $184. On main the household total carried $1,282 while state_income_tax was $184.

Tests

New and changed YAML:

  • household_state_tax_before_refundable_credits.yaml: six Wisconsin households (single, joint, renter, age 66), plus Vermont, Mississippi and Colorado cases.
  • household_refundable_state_tax_credits.yaml: a Wisconsin renter who keeps the $588 homestead credit, and the 2025 and 2026 credit treatment under the election.
  • household_net_income.yaml: a Wisconsin retiree, end to end ($41,250 − $96 = $41,154).
  • state_income_tax.yaml, ms_income_tax.yaml, ms_income_tax_before_refundable_credits.yaml, contrib/states/mi/mi_surtax.yaml.

Run against upstream main's Wisconsin variables (00968de, before #10026), the table-sourced Wisconsin household cases, written then with my own election variable's name, failed by $341 to $2,092 on the household total, and the end-to-end net income case by $1,063. After porting them to #10026's names and leaf inputs, they pass on this branch.

New Python test tests/core/test_state_income_tax_accounting_invariants.py. YAML cannot express these properties, because each compares two computed totals on every row:

  1. Structure, with no simulation: where a state's xx_income_tax is a sum, it adds exactly that state's members of the before-refundable list and subtracts exactly its members of the refundable list. List members must be defined variables, and the household refundable list must equal the state one.
  2. state_income_tax == xx_income_tax for every state, on a seeded population of 18 households per state and DC (12 archetypes, 6 random), plus Hypothesis draws in the four states whose xx_income_tax is a formula (MT, NC, NJ, WI) for 2023 and 2025.
  3. Per household: state tax before refundable credits − refundable state credits == Σ state_income_tax + state_use_tax. Use tax is the one labelled exception: it is paid to the state but is not income tax (state_income_tax_excludes_use_tax.yaml).

Mutation check, run before the Hypothesis layer was added: reverting each fix in turn (the MS list entry, VT, CO, MI, and the WI variables as on main) fails the test every time: MS and VT fail properties 1 and 2, CO and MI fail property 1, WI fails property 2.

Local runs (macOS, shared host):

  • YAML: household/income/household 53 passed; gov/states/tax/income 77; gov/states/ms/tax/income 149; gov/states/vt/tax/income 181; gov/states/wi/tax/income 165; gov/states/co/tax/income 335; household/household_state_income_tax.yaml 1.
  • Invariant test before the Hypothesis layer: 97 passed, 10.7 s in pytest and 44.4 s wall including import, peak RSS 1.78 GB.
  • At head b54018f, under the shared heavy-job lock (macOS, shared host):
    • invariant file, including the Hypothesis layer and the seeded 2023 population: 99 passed, 66 s in pytest, 104 s wall, 1.91 GB peak RSS;
    • the changed YAML files plus nj_income_tax.yaml (10 files, one process): 49 passed;
    • contrib/states/mi/mi_surtax.yaml: 10 passed;
    • contrib/states/ms: ms_eitc.yaml 5 passed, dependent_exemption_reform_test.yaml 5 passed;
    • the Mississippi tax folder (gov/states/ms/tax/income, five batches through test_batched.py): 151 passed, none failed.
  • The abolish-tax contrib files are not affected: this PR no longer edits the list those reforms sum.

CI: the new file runs in the existing core group, make test-other-python-rest-core (REST_PYTHON_GROUPS; the Makefile records 142 s for that group in run 37198184683). No Linux measurement yet. No runners or matrix entries are added.

Partner contract tests: none can move. A static scan found no partner case in WI, MS, VT, MI or NJ. None of the 50 Colorado cases asserts a household total, state_income_tax or co_income_tax. No partner file is edited.

Review

GPT-6.1 Sol, round 1 at 84edf5b: APPROVE, no blocking findings (static review; ~/reviews/us-hub/fixes/10039/review-r1-out.md on the hub host). Round 2, a delta review at b54018f: APPROVE, no blocking findings (review-r2-out.md); it confirmed the 2023 Montana population, the Hypothesis settings and the NJ decision, and flagged that an earlier version of this body claimed test results before they existed (fixed). git diff --stat 23285ce622 main over this PR's files is empty: the merge of main brings no change to them. Round 1's non-blocking findings and what changed at b54018f:

  1. Hypothesis layer unrun: now run locally at b54018f (99 passed, above); CI runs it in the core group. Shrinking is off (phases=[explicit, generate]), since each example builds a full simulation.
  2. Property 3 is close to circular: the docstring now says properties 1 and 2 find a new state's gap, and property 3 catches an override returning to state_income_tax.
  3. Thin Hypothesis sample for Montana: added a seeded 2023 population for the formula states that asserts at least one Montana couple files separately.
  4. Make nj_income_tax a plain sum: not taken. Two existing NJ cases set nj_income_tax_before_refundable_credits as an input with income under the filing threshold and expect the zero-out, so the second copy still does work; the test comment now says so.
  5. household_state_income_tax comment: updated (same sum as state_income_tax, kept for callers).
  6. Baseline list depends on a contrib reform variable: comment added in reforms/states/mi/surtax.py that the reform must stay unconditional; test_list_members_are_defined_variables also catches it.
  7. NC note: corrected (the contributed reforms net their refundable credits inside nc_income_tax).
  8. Structural cases: labelled above.
  9. Wider effects: listed under Impact.
  10. Abolish-tax list: noted above; Remove the abolish-tax structural reforms so household totals keep every other tax #10071 owns it.

Invariants

  • For every state with a modelled income tax: state_income_tax == xx_income_tax. It is zero in AK, FL, NV, SD, TN, TX and WY.
  • For every household: household_state_tax_before_refundable_credits − household_refundable_state_tax_credits == Σ state_income_tax + state_use_tax.
  • Structural: each summed xx_income_tax is built from exactly its state's list members.
  • ms_income_tax_before_refundable_credits ≥ 0, and ms_income_tax == ms_income_tax_before_refundable_credits − ms_refundable_credits.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base 5ec60b209a against this PR's head b54018fa60, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
household_state_tax_before_refundable_credits -632,120 -796,613 157 (87,144) 157 (87,766)
household_refundable_state_tax_credits +0 +0 0 (0) 0 (0)
spm_unit_state_tax -632,119 -796,613 158 (88,943) 158 (89,578)
vt_child_care_contributions +0 +0 0 (0) 0 (0)
co_amt +0 +0 0 (0) 0 (0)
ms_non_refundable_credits +0 +0 0 (0) 0 (0)
state_income_tax -632,118 -796,613 165 (88,943) 165 (89,579)
income_tax +0 +0 0 (0) 0 (0)
household_net_income +632,143 +796,611 157 (87,144) 157 (87,766)
household_benefits +0 +0 0 (0) 0 (0)
By state: weighted change, summed to households, states with any change
State household_net_income 2025 household_net_income 2026 state_income_tax 2025 state_income_tax 2026
MS +2.85M +3.16M -2.85M -3.16M
VT -2.22M -2.36M +2.22M +2.36M
ALL +632,143 +796,611 -632,118 -796,613

Comparison files are in fixes/10039-impact/ on the hub host.

Impact

The hub table above is a real microsimulation of the default dataset at this head (b54018f) against its merge base with main (5ec60b2), for 2025 and 2026. Only Mississippi and Vermont move, each by exactly the component the household totals used to miss:

State 2025 2026 Why
Mississippi +$2.85M +$3.16M household net income rises by the dataset's total ms_non_refundable_credits ($2.85M in 2025, $3.16M in 2026)
Vermont −$2.22M −$2.36M household net income falls by the dataset's total vt_child_care_contributions ($2.22M in 2025, $2.36M in 2026)
All +$0.63M +$0.80M 157 household records change
  • Colorado: no change. co_amt is $0 for every record in the dataset in both years, so the fix matters only for households with a Colorado AMT, such as household calculations.
  • Michigan: no change. The surtax is a contributed reform, off by default.
  • Wisconsin and New Jersey: no change, as expected: Fix Wisconsin retirement election reporting and capital gain distributions #10026 already fixed Wisconsin, and the New Jersey override was redundant.
  • Federal income_tax and household_benefits: no change.

state_income_tax and spm_unit_state_tax move by the same amounts with the opposite sign, and now match ms_income_tax and vt_income_tax. By code: federal_state_income_tax (defined in total_income_tax.py), taxsim_siitax and state_marginal_tax_rate read state_income_tax, and spm_unit_net_income subtracts spm_unit_taxes, which adds spm_unit_state_tax. No state's own xx_income_tax changes.

axiom: n/a: household aggregation only. No state's own income tax changes in baseline; the Wisconsin provision is queued in TheAxiomFoundation/rulespec-us#1610 through #10026.

Changelog: changelog.d/fix-wi-retirement-exclusion-household-accounting.fixed.md.

🤖 Generated with Claude Code

MaxGhenis and others added 5 commits October 9, 2026 08:47
Household net income subtracts household_state_tax_before_refundable_credits
and adds household_refundable_state_tax_credits, which sum the cross-state
lists of state tax before refundable credits and refundable credits. Five
state taxes applied rules outside those components, so household net income
disagreed with the state's own income tax:

- Wisconsin: the retirement income exclusion election (fixed in #10026's
  components); drop the WI override in state_income_tax and
  household_state_income_tax, which now follow the decomposition.
- New Jersey: the override was redundant (the filing-threshold zero-out is
  already in nj_income_tax_before_refundable_credits); drop it.
- Vermont: add vt_child_care_contributions (Form IN-111 line 21).
- Colorado: add co_amt (DR 0104 line 14).
- Mississippi: add ms_income_tax_before_refundable_credits, which applies
  nonrefundable credits (Form 80-105 lines 18-19), and build ms_income_tax
  from it less ms_refundable_credits.
- Michigan (contributed surtax): add mi_surtax.

Add table-sourced YAML cases for each state and an every-state invariant
test: structural list membership, state_income_tax == xx_income_tax, and
household before-refundable less refundable == state_income_tax + use tax.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 11, 2026 13:02
Takes main's version of every file #10026 changed. The branch's own change
is the one commit "Make household state tax totals equal each state's own
income tax", rebased onto main. It no longer edits
gov/household/household_tax_before_refundable_credits.yaml, which #10071
removes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Say which properties find a new state's gap and what property 3 guards.
- Add a seeded 2023 population for the formula states, so Montana's
  separate filing on one form is always exercised.
- Skip Hypothesis shrinking: each example builds a full simulation.
- Correct the NC and NJ notes, the household_state_income_tax comment, and
  note that the Michigan surtax reform must stay unconditional because the
  baseline list adds mi_surtax.

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

Copy link
Copy Markdown
Contributor Author

Hand-off to the US + core hub.

  • Head: b54018fa6014ae77703494dbb5a0d59fa15ec48c, not a draft, MERGEABLE. It is the change rebased on main plus the round 1 fixes, then a merge of main 5ec60b2. git diff --stat 23285ce622 main over this PR's 15 files is empty.
  • Review: GPT-6.1 Sol.
    • Round 1 at 84edf5b: APPROVE, ten non-blocking findings (~/reviews/us-hub/fixes/10039/review-r1-out.md).
    • Round 2, a delta review at this head: APPROVE (review-r2-out.md). Its one substantive note, a body line that claimed results before they existed, is fixed.
    • The body's "Review" section lists each finding and its outcome.
  • Local tests at this head, all through heavy_run.sh in one ticket (logs in ~/reviews/us-hub/fixes/10039/logs/):
    • invariant file 99 passed, including the Hypothesis layer and the seeded 2023 population;
    • changed YAML files plus nj_income_tax.yaml: 49 passed;
    • mi_surtax.yaml: 10 passed;
    • the two contrib/states/ms files: 5 + 5 passed;
    • the Mississippi tax folder: 151 passed.
  • CI: 26 passed, 1 pending, 0 failing when this was written.
  • Impact: a full-dataset run at this head against merge base 5ec60b2, 2025 and 2026, by state, is in the body. Household net income changes only in Mississippi (+$2.85M / +$3.16M) and Vermont (−$2.22M / −$2.36M), net +$0.63M / +$0.80M. Colorado AMT is $0 in the dataset, and the Michigan surtax is off by default. Files are in ~/reviews/us-hub/fixes/10039-impact/.
  • Depends on: nothing open; Fix Wisconsin retirement election reporting and capital gain distributions #10026 has merged. It doesn't conflict with Remove the abolish-tax structural reforms so household totals keep every other tax #10071: this PR no longer edits the list Remove the abolish-tax structural reforms so household totals keep every other tax #10071 deletes. If Remove the abolish-tax structural reforms so household totals keep every other tax #10071 does not land, that list needs co_amt, vt_child_care_contributions, mi_surtax, and ms_income_tax_before_refundable_credits in place of ms_income_tax_before_credits_unit.
  • Holds and decisions: none. Partner tests: no case can move, and none was edited.
  • Follow-ups owed (outside this PR):
    1. Round 2's nit: two NJ cases in nj_income_tax.yaml set nj_income_tax_before_refundable_credits as an input below the filing threshold. That is the only reason nj_income_tax keeps its own zero-out. Moving those checks to the before-refundable variable would let nj_income_tax become a plain sum.
    2. From Fix Wisconsin retirement election reporting and capital gain distributions #10026's design: when the Wisconsin exclusion is elected, wi_non_refundable_credits and the individual WI credits keep their standard-path amounts. So taxsim_state_eitc, taxsim_state_property_tax_credit and state_non_refundable_credits still count credits that electors give up. Household net income is unaffected.

State: ~/reviews/us-hub/fixes/10039/PROGRESS.md.

@MaxGhenis
MaxGhenis merged commit 033b92f into main Oct 18, 2026
34 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10039 at b54018fa6014ae77703494dbb5a0d59fa15ec48c (squash): Make household state tax totals equal each state's own income tax.

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