From cb504bd9b242f7c41595c74872f0fb1e7cc65787 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 09:43:49 -0400 Subject: [PATCH 1/9] Leave dependents' capital income out of state lines on the filers' return A tax unit dependent's interest, dividends and capital gains are on the dependent's own return, and federal adjusted gross income (irs_gross_income) leaves them out. State lines that adjust the filers' federal AGI or taxable income summed them over every member, so a child's dividends, gains or interest moved the parents' state tax. Count only the head and spouse (tax_unit_non_dep_add, or a dependent mask on per-person lines) in AZ, DC, DE, IA, IL, MA, MD, ME, MN, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV, and in us_govt_interest, which most states subtract. In formulas edited anyway: North Dakota's net long-term capital gain exclusion is floored at zero and includes capital gain distributions (Form ND-1 line 6 worksheet); Utah's retirement credit counts only the head and spouse as claimants; the West Virginia low-income family credit cites W. Va. Code 11-21-22a(d). Co-Authored-By: Claude Opus 5.5 --- ...endent-capital-income-state-lines.fixed.md | 1 + ...ent_capital_income_state_tax_invariants.py | 315 ++++++++++++++++++ ...z_long_term_capital_gains_subtraction.yaml | 34 ++ .../dc/tax/income/dc_income_subtractions.yaml | 18 + .../income/subtractions/de_subtractions.yaml | 18 + .../income/ia_reportable_social_security.yaml | 34 ++ .../income/il_base_income_additions.yaml | 16 + .../gross_income/ma_part_a_gross_income.yaml | 20 ++ .../gross_income/ma_part_c_gross_income.yaml | 36 ++ .../md_capital_gains_surtax.yaml | 17 + ...d_property_tax_fairness_credit_income.yaml | 34 ++ .../mn_social_security_subtraction.yaml | 34 ++ .../mt_capital_gains_tax_joint.yaml | 39 +++ .../credits/mt_capital_gain_credit.yaml | 17 + .../mt_interest_exemption_person.yaml | 18 + .../states/mt/tax/income/mt_agi_joint.yaml | 18 + .../mt_taxable_social_security_joint.yaml | 35 ++ .../nd/tax/income/nd_ltcg_subtraction.yaml | 65 ++++ .../nd/tax/income/nd_qdiv_subtraction.yaml | 17 + .../nh/tax/income/nh_taxable_income.yaml | 18 + .../tax/income/deductions/oh_deductions.yaml | 18 + .../forgiveness/pa_eligibility_income.yaml | 34 ++ .../sc_net_capital_gain_deduction.yaml | 17 + .../ut_retirement_credit_max.yaml | 17 + .../ut_ss_benefits_credit_max.yaml | 33 ++ .../tax/income/credits/ut_ctc_potential.yaml | 15 + .../states/va/tax/income/va_subtractions.yaml | 19 ++ .../vt/tax/income/agi/vt_additions.yaml | 16 + ...vt_percentage_capital_gains_exclusion.yaml | 40 +++ .../wa/tax/income/wa_capital_gains_tax.yaml | 17 + .../states/wi/tax/income/wi_additions.yaml | 16 + .../wi_capital_gain_loss_subtration.yaml | 17 + .../states/wi/tax/income/wi_capital_loss.yaml | 17 + .../wv_low_income_family_tax_credit_agi.yaml | 17 + .../income/tax_unit/us_govt_interest.yaml | 18 + .../az_long_term_capital_gains_subtraction.py | 13 +- .../subtractions/dc_income_subtractions.py | 9 + .../income/subtractions/de_subtractions.py | 9 + .../income/ia_reportable_social_security.py | 7 +- .../base_income/il_base_income_additions.py | 8 +- .../ma_long_term_loss_against_dividends.py | 11 +- .../ma_part_a_adjusted_dividends.py | 5 +- .../adjusted_gross_income/ma_part_a_agi.py | 11 +- .../adjusted_gross_income/ma_part_c_agi.py | 9 +- .../ma_short_term_loss_against_dividends.py | 9 +- .../gross_income/ma_part_a_gross_income.py | 7 +- .../gross_income/ma_part_c_gross_income.py | 7 +- .../ma_part_b_taxable_income_deductions.py | 6 +- .../capital_gains/md_capital_gains_surtax.py | 4 +- ...and_property_tax_fairness_credit_income.py | 11 +- .../mn_social_security_subtraction.py | 13 +- .../states/mt/tax/income/base/mt_agi_joint.py | 13 +- .../base/mt_taxable_social_security_joint.py | 13 +- ...al_gains_tax_applicable_threshold_joint.py | 6 +- .../mt_capital_gains_tax_joint.py | 6 +- .../capital_gain/mt_capital_gain_credit.py | 5 +- .../interest/mt_interest_exemption_person.py | 6 +- .../mt_regular_income_tax_joint.py | 6 +- .../subtractions/nd_ltcg_subtraction.py | 22 +- .../subtractions/nd_qdiv_subtraction.py | 8 +- .../states/nh/tax/income/nh_taxable_income.py | 13 +- .../oh/tax/income/deductions/oh_deductions.py | 15 +- .../forgiveness/pa_eligibility_income.py | 13 +- .../sc_net_capital_gain_deduction.py | 8 +- .../child_tax_credit/ut_ctc_potential.py | 4 +- .../ut_retirement_credit_max.py | 14 +- .../ut_ss_benefits_credit_max.py | 4 +- .../states/va/tax/income/va_subtractions.py | 5 +- .../va/tax/income/va_subtractions_person.py | 5 +- .../vt_percentage_capital_gains_exclusion.py | 6 +- .../adjusted_gross_income/vt_additions.py | 7 +- .../wa_millionaires_tax_base_income.py | 10 +- .../wa/tax/income/wa_capital_gains_tax.py | 5 +- .../wi/tax/income/additions/wi_additions.py | 9 +- .../tax/income/additions/wi_capital_loss.py | 4 +- .../wi_capital_gain_loss_subtraction.py | 7 +- .../wv_low_income_family_tax_credit_agi.py | 19 +- .../income/tax_unit/us_govt_interest.py | 17 +- 78 files changed, 1421 insertions(+), 83 deletions(-) create mode 100644 changelog.d/dependent-capital-income-state-lines.fixed.md create mode 100644 policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py create mode 100644 policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_income_subtractions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/de_subtractions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/il/tax/income/income/il_base_income_additions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_and_property_tax_fairness_credit_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/tax/income/nh_taxable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/oh_deductions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_ctc_potential.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_subtractions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_additions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_additions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/income/tax_unit/us_govt_interest.yaml diff --git a/changelog.d/dependent-capital-income-state-lines.fixed.md b/changelog.d/dependent-capital-income-state-lines.fixed.md new file mode 100644 index 00000000000..cf87b71e125 --- /dev/null +++ b/changelog.d/dependent-capital-income-state-lines.fixed.md @@ -0,0 +1 @@ +Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MN, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), and out of the U.S. government interest that most states subtract. diff --git a/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py new file mode 100644 index 00000000000..686cbefb5db --- /dev/null +++ b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py @@ -0,0 +1,315 @@ +"""Property tests: a dependent's capital income stays off the filers' North +Dakota and Vermont returns. + +Federal adjusted gross income leaves out a tax unit dependent's income +(irs_gross_income), which the dependent reports on their own return (2025 +Form 8814, line 4; the model does not implement the Form 8814 election). Both +states start from the filers' federal return: + +- N.D.C.C. 57-38-30.3(2) defines North Dakota taxable income as "the federal + taxable income of an individual", reduced by 40% of "the taxpayer's net + long-term capital gain" ((d)(1); Form ND-1 line 6, from the filer's + Schedule D lines 15 and 16, "If zero or less ... no exclusion is allowed") + and of qualified dividends ((d)(2); Form ND-1 line 13, from Form 1040 line + 3a), and by interest on U.S. obligations ((a)). +- 32 V.S.A. 5811(21) defines Vermont taxable income as the individual's + federal adjusted gross income, increased by interest on non-Vermont state + and local obligations ((A)(i)) and decreased, "to the extent such income is + included in federal adjusted gross income", by interest on U.S. government + obligations ((B)(i)) and by 40% of gains on "assets held by the taxpayer for + more than three years" ((B)(ii)). + +So for every tax unit in either state, and any capital income its dependents +have (qualified and other dividends, long- and short-term gains and losses, +capital gain distributions, taxable, tax-exempt and U.S. government interest, +and Vermont-eligible long-term gains): + +1. nd_income_tax and vt_income_tax are the same as when the dependents have + none. So are federal adjusted gross income and taxable income, the base + both states start from. +2. nd_qdiv_subtraction is 40% of the filers' qualified dividends, and + nd_ltcg_subtraction is 40% of max(0, min(line 15, line 16)) of the filers' + own Schedule D, with capital gain distributions on line 15. +3. vt_percentage_capital_gains_exclusion is 40% of the filers' eligible gains, + up to the cap, vt_additions is the filers' tax-exempt interest, and + us_govt_interest is the filers' U.S. government interest. + +Two restrictions keep effects that are outside this check out of the draws: + +- The filers earn at least $80,000, so no earned income tax credit is at + stake. Vermont's is a share of the federal credit, whose investment income + test (eitc_relevant_investment_income) is not changed here. +- Vermont filers have at least $50,000 of their own long-term gain and no + short-term loss, and Vermont dependents have gains, not losses. Until + PolicyEngine/policyengine-us#9855 is merged, the $5,000 flat part of + vt_capital_gains_exclusion reads the federal adjusted net capital gain, + which on main still counts a dependent's gains and losses. With these + amounts the flat part is at its cap either way. + +Amounts are whole dollars of at most $400,000, so every sum is exact in single +precision and twins are compared to the cent. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEAR = 2025 +TOLERANCE = 0.01 + +# Each person's capital income inputs. +CAPITAL_INPUTS = [ + "qualified_dividend_income", + "non_qualified_dividend_income", + "long_term_capital_gains", + "short_term_capital_gains", + "non_sch_d_capital_gains", + "taxable_interest_income", + "us_govt_interest_person", + "tax_exempt_interest_income", + "long_term_capital_gains_on_assets_eligible_for_vt_exclusion", +] +SAME_AS_WITHOUT_DEPENDENT_INCOME = [ + "adjusted_gross_income", + "taxable_income", + "nd_income_tax", + "vt_income_tax", +] +UNIT_OUTPUTS = SAME_AS_WITHOUT_DEPENDENT_INCOME + [ + "nd_qdiv_subtraction", + "nd_ltcg_subtraction", + "vt_percentage_capital_gains_exclusion", + "vt_additions", + "us_govt_interest", +] + + +def amount(low, high): + return st.integers(low, high).map(float) + + +@st.composite +def capital_income(draw, vermont, filer): + """One person's capital income. U.S. government interest is part of + taxable interest, and Vermont-eligible gains are part of long-term + gains.""" + if vermont and filer: + long_term = draw(amount(50_000, 300_000)) + short_term = draw(amount(0, 20_000)) + elif vermont: + long_term = draw(amount(0, 300_000)) + short_term = draw(amount(0, 50_000)) + else: + long_term = draw(amount(-30_000, 300_000)) + short_term = draw(amount(-20_000, 50_000)) + taxable_interest = draw(amount(0, 50_000)) + return { + "qualified_dividend_income": draw(amount(0, 50_000)), + "non_qualified_dividend_income": draw(amount(0, 20_000)), + "long_term_capital_gains": long_term, + "short_term_capital_gains": short_term, + "non_sch_d_capital_gains": draw(amount(0, 20_000)), + "taxable_interest_income": taxable_interest, + "us_govt_interest_person": draw(amount(0, int(taxable_interest))), + "tax_exempt_interest_income": draw(amount(0, 50_000)), + "long_term_capital_gains_on_assets_eligible_for_vt_exclusion": ( + draw(amount(0, int(long_term))) if long_term > 0 else 0.0 + ), + } + + +@st.composite +def tax_units(draw): + state = draw(st.sampled_from(["ND", "VT"])) + vermont = state == "VT" + n_filers = draw(st.integers(1, 2)) + filers = [ + { + "age": draw(st.integers(30, 64)), + "employment_income": draw(amount(80_000, 400_000)) if i == 0 else 0.0, + **draw(capital_income(vermont, filer=True)), + } + for i in range(n_filers) + ] + dependents = [ + { + "age": draw(st.integers(1, 17)), + **draw(capital_income(vermont, filer=False)), + } + for _ in range(draw(st.integers(1, 2))) + ] + return {"state": state, "filers": filers, "dependents": dependents} + + +def without_dependent_income(unit): + zero = {name: 0.0 for name in CAPITAL_INPUTS} + return {**unit, "dependents": [{**d, **zero} for d in unit["dependents"]]} + + +def build_situation(units): + """One tax unit per household: the head, an optional spouse and the + dependents, each dependent in a marital unit of their own.""" + people, tax_units_out, marital_units, households = {}, {}, {}, {} + for i, unit in enumerate(units): + members = [] + for j, person in enumerate(unit["filers"] + unit["dependents"]): + name = f"person_{i}_{j}" + filer = j < len(unit["filers"]) + people[name] = { + "is_tax_unit_head": {YEAR: j == 0}, + "is_tax_unit_spouse": {YEAR: j == 1 and filer}, + "is_tax_unit_dependent": {YEAR: not filer}, + **{key: {YEAR: value} for key, value in person.items()}, + } + members.append(name) + if not filer: + marital_units[f"dependent_{i}_{j}"] = {"members": [name]} + tax_units_out[f"tax_unit_{i}"] = {"members": members} + marital_units[f"filers_{i}"] = {"members": members[: len(unit["filers"])]} + households[f"household_{i}"] = { + "members": members, + "state_code": {YEAR: unit["state"]}, + } + return { + "people": people, + "tax_units": tax_units_out, + "marital_units": marital_units, + "households": households, + } + + +def calculate(units): + simulation = Simulation(situation=build_situation(units)) + results = { + variable: np.asarray(simulation.calculate(variable, YEAR)) + for variable in UNIT_OUTPUTS + } + p = simulation.tax_benefit_system.parameters(YEAR).gov.states + return results, p + + +def filer_total(unit, name): + return sum(person[name] for person in unit["filers"]) + + +def check(units): + twins = [without_dependent_income(unit) for unit in units] + results, p = calculate(units + twins) + n = len(units) + + # 1. The same taxes and federal base as without the dependents' income. + for variable in SAME_AS_WITHOUT_DEPENDENT_INCOME: + np.testing.assert_allclose( + results[variable][:n], + results[variable][n:], + atol=TOLERANCE, + err_msg=variable, + ) + + nd = p.nd.tax.income.taxable_income.subtractions + vt = p.vt.tax.income.agi.exclusions.capital_gain.percentage + is_nd = np.array([unit["state"] == "ND" for unit in units]) + is_vt = ~is_nd + long_term = np.array( + [ + filer_total(u, "long_term_capital_gains") + + filer_total(u, "non_sch_d_capital_gains") + for u in units + ] + ) + short_term = np.array([filer_total(u, "short_term_capital_gains") for u in units]) + reference = { + # 2. North Dakota, from the filers' own amounts. + "nd_qdiv_subtraction": nd.qdiv_fraction + * np.array([filer_total(u, "qualified_dividend_income") for u in units]), + "nd_ltcg_subtraction": nd.ltcg_fraction + * np.maximum(0, np.minimum(long_term, long_term + short_term)), + # 3. Vermont, from the filers' own amounts. + "vt_percentage_capital_gains_exclusion": np.minimum( + vt.rate + * np.array( + [ + filer_total( + u, "long_term_capital_gains_on_assets_eligible_for_vt_exclusion" + ) + for u in units + ] + ), + vt.cap, + ), + "vt_additions": np.array( + [filer_total(u, "tax_exempt_interest_income") for u in units] + ), + } + for variable, expected in reference.items(): + in_state = is_nd if variable.startswith("nd_") else is_vt + np.testing.assert_allclose( + results[variable][:n][in_state], + expected[in_state], + atol=TOLERANCE, + err_msg=variable, + ) + np.testing.assert_allclose( + results["us_govt_interest"][:n], + [filer_total(u, "us_govt_interest_person") for u in units], + atol=TOLERANCE, + ) + + +# A simulation's cost is mostly fixed, so each example is a batch of tax +# units and there are few examples. +@settings( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(tax_units(), min_size=10, max_size=30)) +def test_dependent_capital_income_stays_off_nd_and_vt_returns(units): + check(units) + + +def test_reported_examples(): + """The two households that showed the leak (TY2025): in North Dakota a + head with $50,000 of wages and $1,000 of qualified dividends, and in + Vermont a head with $150,000 of wages and a $10,000 long-term gain, each + with a dependent child who has dividends or a Vermont-eligible gain.""" + zero = {name: 0.0 for name in CAPITAL_INPUTS} + nd = { + "state": "ND", + "filers": [ + { + "age": 40, + "employment_income": 50_000.0, + **zero, + "qualified_dividend_income": 1_000.0, + } + ], + "dependents": [{"age": 10, **zero, "qualified_dividend_income": 5_000.0}], + } + vt = { + "state": "VT", + "filers": [ + { + "age": 40, + "employment_income": 150_000.0, + **zero, + "long_term_capital_gains": 10_000.0, + } + ], + "dependents": [ + { + "age": 10, + **zero, + "long_term_capital_gains": 100_000.0, + "long_term_capital_gains_on_assets_eligible_for_vt_exclusion": 100_000.0, + } + ], + } + check([nd, vt]) + results, _ = calculate([nd, vt]) + assert results["nd_qdiv_subtraction"][0] == 400 + assert results["vt_percentage_capital_gains_exclusion"][1] == 0 + np.testing.assert_allclose(results["vt_income_tax"][1], 6_623.20, atol=TOLERANCE) diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.yaml index e84729d129e..273d46e0802 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.yaml @@ -102,3 +102,37 @@ output: # Net capital gains = 40,000 (gain), so 25% of $50K LTCG = $12,500 az_long_term_capital_gains_subtraction: 12_500 + +- name: A dependent's long-term gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 10_000 + child: + age: 10 + long_term_capital_gains: 100_000 + household: + state_code: AZ + output: + az_long_term_capital_gains_subtraction: 2_500 + +- name: A dependent's short-term loss does not take away the filer's subtraction + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 10_000 + child: + age: 10 + short_term_capital_gains: -50_000 + household: + state_code: AZ + output: + az_long_term_capital_gains_subtraction: 2_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_income_subtractions.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_income_subtractions.yaml new file mode 100644 index 00000000000..2da5757b53a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_income_subtractions.yaml @@ -0,0 +1,18 @@ +- name: A dependent's U.S. government interest is not subtracted + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + us_govt_interest_person: 200 + taxable_interest_income: 200 + child: + age: 10 + us_govt_interest_person: 50_000 + taxable_interest_income: 50_000 + household: + state_code: DC + output: + dc_income_subtractions: [200, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/de_subtractions.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/de_subtractions.yaml new file mode 100644 index 00000000000..4ec19202c05 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/de_subtractions.yaml @@ -0,0 +1,18 @@ +- name: A dependent's U.S. government interest is not subtracted + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + us_govt_interest_person: 200 + taxable_interest_income: 200 + child: + age: 10 + us_govt_interest_person: 50_000 + taxable_interest_income: 50_000 + household: + state_code: DE + output: + de_subtractions: [200, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml index d0b89d39608..520300edea5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml @@ -49,3 +49,37 @@ state_code: IA output: ia_reportable_social_security: 1_500 # one-half of gross social security + +- name: A dependent's tax-exempt interest and benefits are on the dependent's own return + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + tax_exempt_interest_income: 50_000 + social_security_survivors: 10_000 + household: + state_code: IA + output: + ia_reportable_social_security: 0 + +- name: Same household without the dependent's income + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + household: + state_code: IA + output: + ia_reportable_social_security: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/income/il_base_income_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/income/il_base_income_additions.yaml new file mode 100644 index 00000000000..7fd38eec3db --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/income/il_base_income_additions.yaml @@ -0,0 +1,16 @@ +- name: A dependent's tax-exempt interest is not added + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + tax_exempt_interest_income: 300 + child: + age: 10 + tax_exempt_interest_income: 5_000 + household: + state_code: IL + output: + il_base_income_additions: 300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.yaml new file mode 100644 index 00000000000..a5b5a73c38f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.yaml @@ -0,0 +1,20 @@ +- name: A dependent's dividends and short-term gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + qualified_dividend_income: 1_000 + child: + age: 10 + qualified_dividend_income: 5_000 + short_term_capital_gains: 50_000 + household: + state_code: MA + output: + ma_part_a_gross_income: 1_000 + ma_part_a_agi: 1_000 + ma_part_b_gross_income: 60_000 + ma_part_a_taxable_dividend_income: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.yaml new file mode 100644 index 00000000000..c520cb07b13 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.yaml @@ -0,0 +1,36 @@ +- name: A dependent's long-term gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 2_000 + child: + age: 10 + long_term_capital_gains: 300_000 + household: + state_code: MA + output: + ma_part_c_gross_income: 2_000 + ma_part_b_gross_income: 60_000 + ma_income_tax: 2_170 + +- name: Same household without the dependent's gains + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 2_000 + child: + age: 10 + household: + state_code: MA + output: + ma_part_c_gross_income: 2_000 + ma_part_b_gross_income: 60_000 + ma_income_tax: 2_170 diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml index 1e12d9fed73..68a00a71e8f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml @@ -244,3 +244,20 @@ # Net capital gains: $300,000 (should potentially be less with exclusions) # Surtax: $300,000 * 2% = $6,000 (may overstate actual liability) md_capital_gains_surtax: 6_000 + +- name: A dependent's gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 400_000 + long_term_capital_gains: 10_000 + child: + age: 10 + long_term_capital_gains: 300_000 + household: + state_code: MD + output: + md_capital_gains_surtax: 200 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_and_property_tax_fairness_credit_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_and_property_tax_fairness_credit_income.yaml new file mode 100644 index 00000000000..21c8b920218 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_and_property_tax_fairness_credit_income.yaml @@ -0,0 +1,34 @@ +- name: A dependent's tax-exempt interest is on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 82 + social_security_retirement: 18_000 + taxable_pension_income: 8_000 + tax_exempt_interest_income: 500 + child: + age: 10 + tax_exempt_interest_income: 50_000 + household: + state_code: ME + output: + me_sales_and_property_tax_fairness_credit_income: 26_500 + +- name: Same household without the dependent's interest + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 82 + social_security_retirement: 18_000 + taxable_pension_income: 8_000 + tax_exempt_interest_income: 500 + child: + age: 10 + household: + state_code: ME + output: + me_sales_and_property_tax_fairness_credit_income: 26_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml index 17cb6090594..d9894ae452f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml @@ -153,3 +153,37 @@ # AGI equals the $86,410 threshold, so the simplified subtraction is not # reduced and the full $20,000 of taxable benefits is subtracted. mn_social_security_subtraction: 20_000 + +- name: A dependent's tax-exempt interest and benefits are on the dependent's own return + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + tax_exempt_interest_income: 50_000 + social_security_survivors: 10_000 + household: + state_code: MN + output: + mn_social_security_subtraction: 4_260 + +- name: Same household without the dependent's income + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + household: + state_code: MN + output: + mn_social_security_subtraction: 4_260 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml index 72ea9ba6c38..b18389de370 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml @@ -123,3 +123,42 @@ state_code: MT output: mt_capital_gains_tax_joint: 409 + +- name: A dependent's gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 2_000 + child: + age: 10 + long_term_capital_gains: 300_000 + household: + state_code: MT + output: + mt_capital_gains_tax_joint: 82 + mt_regular_income_tax_joint: 1_765.72 + mt_capital_gains_tax_applicable_threshold_joint: 0 + mt_income_tax: 1_847.72 + +- name: Same household without the dependent's gains + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 2_000 + child: + age: 10 + household: + state_code: MT + output: + mt_capital_gains_tax_joint: 82 + mt_regular_income_tax_joint: 1_765.72 + mt_capital_gains_tax_applicable_threshold_joint: 0 + mt_income_tax: 1_847.72 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml index 370c349f925..2f572d9e4cd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/mt_capital_gain_credit.yaml @@ -13,3 +13,20 @@ state_code: MT output: mt_capital_gain_credit: 60 + +- name: A dependent's gains give no credit on this return + period: 2023 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 2_000 + child: + age: 10 + long_term_capital_gains: 50_000 + household: + state_code: MT + output: + mt_capital_gain_credit: [40, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_interest_exemption_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_interest_exemption_person.yaml index c1ec3ef42bb..8578bfaae21 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_interest_exemption_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_interest_exemption_person.yaml @@ -58,3 +58,21 @@ output: mt_interest_exemption_person: [0, 0] + +- name: A dependent's interest does not dilute the filer's share + period: 2023 + input: + people: + head: + is_tax_unit_head: true + age: 70 + taxable_interest_income: 2_000 + social_security_retirement: 20_000 + child: + age: 10 + taxable_interest_income: 50_000 + household: + state_code: MT + output: + mt_interest_exemption_person: [800, 0] + mt_interest_exemption: 800 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml new file mode 100644 index 00000000000..b23971c2bcd --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_joint.yaml @@ -0,0 +1,18 @@ +- name: A dependent's U.S. government interest is not subtracted + period: 2023 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + us_govt_interest_person: 200 + taxable_interest_income: 200 + child: + age: 10 + us_govt_interest_person: 50_000 + taxable_interest_income: 50_000 + household: + state_code: MT + output: + mt_agi_joint: 60_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml new file mode 100644 index 00000000000..3d457ec24e8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml @@ -0,0 +1,35 @@ +- name: A dependent's tax-exempt interest and benefits are on the dependent's own return + period: 2023 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + tax_exempt_interest_income: 50_000 + social_security_survivors: 10_000 + household: + state_code: MT + output: + mt_taxable_social_security_joint: 5_350 + taxable_social_security: [5_350, 0] + +- name: Same household without the dependent's income + period: 2023 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + household: + state_code: MT + output: + mt_taxable_social_security_joint: 5_350 + taxable_social_security: [5_350, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_ltcg_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_ltcg_subtraction.yaml index d3a64990c9d..9daffc996be 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_ltcg_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_ltcg_subtraction.yaml @@ -31,3 +31,68 @@ state_code: ND output: nd_ltcg_subtraction: (7_000 - 2_000) * 0.40 + +- name: A dependent's long-term capital gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 50_000 + long_term_capital_gains: 2_000 + child: + age: 10 + long_term_capital_gains: 10_000 + household: + state_code: ND + output: + nd_ltcg_subtraction: 800 + +- name: A dependent's short-term capital loss does not reduce the filer's subtraction + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 50_000 + long_term_capital_gains: 2_000 + child: + age: 10 + short_term_capital_gains: -1_500 + household: + state_code: ND + output: + nd_ltcg_subtraction: 800 + +- name: No exclusion when the filers have a net long-term capital loss + # Form ND-1 line 6 worksheet, line 1: if zero or less, no exclusion. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 50_000 + long_term_capital_gains: -2_000 + short_term_capital_gains: 5_000 + household: + state_code: ND + output: + nd_ltcg_subtraction: 0 + +- name: Capital gain distributions reported without Schedule D count + # Form ND-1 line 6 worksheet: enter the distributions on line 3. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 50_000 + non_sch_d_capital_gains: 1_000 + household: + state_code: ND + output: + nd_ltcg_subtraction: 400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_qdiv_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_qdiv_subtraction.yaml index 0680681e7d2..7d7150b1ea1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_qdiv_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/nd_qdiv_subtraction.yaml @@ -12,3 +12,20 @@ state_code: ND output: nd_qdiv_subtraction: 800 * 0.40 + +- name: A dependent's qualified dividends are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 50_000 + qualified_dividend_income: 1_000 + child: + age: 10 + qualified_dividend_income: 5_000 + household: + state_code: ND + output: + nd_qdiv_subtraction: 400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/tax/income/nh_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/tax/income/nh_taxable_income.yaml new file mode 100644 index 00000000000..285c751a8cb --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/tax/income/nh_taxable_income.yaml @@ -0,0 +1,18 @@ +- name: A dependent's interest and dividends are on the dependent's own return + period: 2024 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + taxable_interest_income: 3_000 + child: + age: 10 + taxable_interest_income: 50_000 + qualified_dividend_income: 5_000 + household: + state_code: NH + output: + nh_taxable_income: 600 + nh_total_exemptions: 2_400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/oh_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/oh_deductions.yaml new file mode 100644 index 00000000000..d0ea8b2f9c3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/oh_deductions.yaml @@ -0,0 +1,18 @@ +- name: A dependent's U.S. government interest is not deducted + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + us_govt_interest_person: 200 + taxable_interest_income: 200 + child: + age: 10 + us_govt_interest_person: 50_000 + taxable_interest_income: 50_000 + household: + state_code: OH + output: + oh_deductions: [200, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.yaml new file mode 100644 index 00000000000..650bbb038ff --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.yaml @@ -0,0 +1,34 @@ +- name: A dependent's tax-exempt interest is not the claimant's eligibility income + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 82 + social_security_retirement: 18_000 + taxable_pension_income: 8_000 + tax_exempt_interest_income: 500 + child: + age: 10 + tax_exempt_interest_income: 50_000 + household: + state_code: PA + output: + pa_eligibility_income: 500 + +- name: Same household without the dependent's interest + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 82 + social_security_retirement: 18_000 + taxable_pension_income: 8_000 + tax_exempt_interest_income: 500 + child: + age: 10 + household: + state_code: PA + output: + pa_eligibility_income: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/deductions/net_captial_gain/sc_net_capital_gain_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/deductions/net_captial_gain/sc_net_capital_gain_deduction.yaml index d2906f41389..0be4cc161c1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/deductions/net_captial_gain/sc_net_capital_gain_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/deductions/net_captial_gain/sc_net_capital_gain_deduction.yaml @@ -33,3 +33,20 @@ short_term_capital_gains: -5_000 output: sc_net_capital_gain_deduction: 880 + +- name: A dependent's gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 10_000 + child: + age: 10 + long_term_capital_gains: 100_000 + household: + state_code: SC + output: + sc_net_capital_gain_deduction: 4_400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml index 71275114c66..0812b78f10f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml @@ -138,3 +138,20 @@ state_code: UT output: ut_retirement_credit_max: 300 + +- name: An elderly dependent is not a claimant, and a dependent's interest is on their own return + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 75 + taxable_pension_income: 20_000 + parent: + age: 95 + is_tax_unit_dependent: true + tax_exempt_interest_income: 50_000 + household: + state_code: UT + output: + ut_retirement_credit_max: 450 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml index 9f8b8f14077..875e5d85c1c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml @@ -52,3 +52,36 @@ state_code: UT output: ut_ss_benefits_credit_max: 4_700 + +- name: A dependent's tax-exempt interest is on the dependent's own return + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + tax_exempt_interest_income: 50_000 + household: + state_code: UT + output: + ut_ss_benefits_credit_max: 259.48 + +- name: Same household without the dependent's interest + period: 2022 + input: + people: + head: + is_tax_unit_head: true + age: 70 + social_security_retirement: 30_000 + taxable_pension_income: 20_000 + child: + age: 10 + household: + state_code: UT + output: + ut_ss_benefits_credit_max: 259.48 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_ctc_potential.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_ctc_potential.yaml new file mode 100644 index 00000000000..64cf55c3429 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_ctc_potential.yaml @@ -0,0 +1,15 @@ +- name: A dependent's tax-exempt interest is on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 40_000 + child: + age: 3 + tax_exempt_interest_income: 50_000 + household: + state_code: UT + output: + ut_ctc_potential: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_subtractions.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_subtractions.yaml new file mode 100644 index 00000000000..3adb2787ed9 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_subtractions.yaml @@ -0,0 +1,19 @@ +- name: A dependent's U.S. government interest is not subtracted + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + us_govt_interest_person: 1_000 + taxable_interest_income: 1_000 + child: + age: 10 + us_govt_interest_person: 5_000 + taxable_interest_income: 5_000 + household: + state_code: VA + output: + va_subtractions: 1_000 + va_subtractions_person: [1_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_additions.yaml new file mode 100644 index 00000000000..6ed0e14411f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_additions.yaml @@ -0,0 +1,16 @@ +- name: A dependent's tax-exempt interest is not added + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + tax_exempt_interest_income: 300 + child: + age: 10 + tax_exempt_interest_income: 5_000 + household: + state_code: VT + output: + vt_additions: 300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml index bd5b9f62e60..c2677e7fcb0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml @@ -43,3 +43,43 @@ long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 10_000 output: vt_percentage_capital_gains_exclusion: 4_000 # 40% of 10k eligible only + +- name: A dependent's eligible gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 150_000 + long_term_capital_gains: 10_000 + child: + age: 10 + long_term_capital_gains: 100_000 + long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 100_000 + household: + state_code: VT + output: + vt_percentage_capital_gains_exclusion: 0 + vt_capital_gains_exclusion: 5_000 + adjusted_gross_income: 160_000 + vt_income_tax: 6_623.2 + +- name: Same household without the dependent's gains + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 150_000 + long_term_capital_gains: 10_000 + child: + age: 10 + household: + state_code: VT + output: + vt_percentage_capital_gains_exclusion: 0 + vt_capital_gains_exclusion: 5_000 + adjusted_gross_income: 160_000 + vt_income_tax: 6_623.2 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/wa_capital_gains_tax.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/wa_capital_gains_tax.yaml index f9488394320..d351e6532df 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/wa_capital_gains_tax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/wa_capital_gains_tax.yaml @@ -93,3 +93,20 @@ # Taxable LTCG: $2,000,000 # 7% x $2,000,000 + 2.9% x $1,000,000 = $169,000 wa_capital_gains_tax: 169_000 + +- name: A dependent's gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 100_000 + child: + age: 10 + long_term_capital_gains: 300_000 + household: + state_code: WA + output: + wa_capital_gains_tax: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_additions.yaml new file mode 100644 index 00000000000..10e3aca4f23 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_additions.yaml @@ -0,0 +1,16 @@ +- name: A dependent's tax-exempt interest is not added + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + tax_exempt_interest_income: 300 + child: + age: 10 + tax_exempt_interest_income: 5_000 + household: + state_code: WI + output: + wi_additions: 300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_gain_loss_subtration.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_gain_loss_subtration.yaml index 13e882d98cc..7017e6dd5fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_gain_loss_subtration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_gain_loss_subtration.yaml @@ -65,3 +65,20 @@ state_code: WI output: wi_capital_gain_loss_subtraction: 300 + +- name: A dependent's gains are on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 10_000 + child: + age: 10 + long_term_capital_gains: 100_000 + household: + state_code: WI + output: + wi_capital_gain_loss_subtraction: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_loss.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_loss.yaml index 08d9dd4d723..4b2cb6bc686 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_loss.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_capital_loss.yaml @@ -39,3 +39,20 @@ filing_status: JOINT output: wi_capital_loss: 2_000 + +- name: A dependent's capital loss is on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + long_term_capital_gains: 1_000 + child: + age: 10 + short_term_capital_gains: -20_000 + household: + state_code: WI + output: + wi_capital_loss: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.yaml new file mode 100644 index 00000000000..67f5c43b829 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.yaml @@ -0,0 +1,17 @@ +- name: A dependent's tax-exempt interest is on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 82 + social_security_retirement: 18_000 + taxable_pension_income: 8_000 + tax_exempt_interest_income: 500 + child: + age: 10 + tax_exempt_interest_income: 50_000 + household: + state_code: WV + output: + wv_low_income_family_tax_credit_agi: 8_500 diff --git a/policyengine_us/tests/policy/baseline/household/income/tax_unit/us_govt_interest.yaml b/policyengine_us/tests/policy/baseline/household/income/tax_unit/us_govt_interest.yaml new file mode 100644 index 00000000000..d74085f3597 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/income/tax_unit/us_govt_interest.yaml @@ -0,0 +1,18 @@ +- name: A dependent's U.S. government interest is on the dependent's own return + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 60_000 + us_govt_interest_person: 1_000 + taxable_interest_income: 1_000 + child: + age: 10 + us_govt_interest_person: 5_000 + taxable_interest_income: 5_000 + household: + state_code: CA + output: + us_govt_interest: 1_000 diff --git a/policyengine_us/variables/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.py b/policyengine_us/variables/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.py index 4817bc865b6..d8f51eb3b53 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.py +++ b/policyengine_us/variables/gov/states/az/tax/income/subtractions/capital_gains/az_long_term_capital_gains_subtraction.py @@ -19,8 +19,17 @@ def formula(tax_unit, period, parameters): # Per ARS 43-1022(22): subtraction is for "net long-term capital gain # included in federal adjusted gross income". When there's an overall # capital loss, no LTCG is included in federal AGI. - long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) - net_capital_gains = tax_unit("net_capital_gains", period) + # Federal adjusted gross income leaves out a tax unit dependent's gains, + # which are on the dependent's own return, so only the head's and + # spouse's gains count. + long_term_capital_gains = tax_unit_non_dep_add( + tax_unit, period, ["long_term_capital_gains"] + ) + net_capital_gains = tax_unit_non_dep_add( + tax_unit, + period, + ["long_term_capital_gains", "short_term_capital_gains"], + ) # Only include LTCG when there's a net capital gain overall ltcg_in_agi = where( diff --git a/policyengine_us/variables/gov/states/dc/tax/income/subtractions/dc_income_subtractions.py b/policyengine_us/variables/gov/states/dc/tax/income/subtractions/dc_income_subtractions.py index a8bccb24d81..cc41f847bf5 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/subtractions/dc_income_subtractions.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/subtractions/dc_income_subtractions.py @@ -16,5 +16,14 @@ class dc_income_subtractions(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.dc.tax.income.subtractions total_subtractions = add(person, period, p.sources) + # A tax unit dependent's U.S. government interest is on the dependent's + # own return and never in the filer's federal AGI, so it is not + # subtracted here, where each person's amounts are summed into the + # filer's. + if "us_govt_interest_person" in p.sources: + dependent = person("is_tax_unit_dependent", period) + total_subtractions = total_subtractions - dependent * person( + "us_govt_interest_person", period + ) # Prevent negative subtractions from acting as additions return max_(0, total_subtractions) diff --git a/policyengine_us/variables/gov/states/de/tax/income/subtractions/de_subtractions.py b/policyengine_us/variables/gov/states/de/tax/income/subtractions/de_subtractions.py index 708fdab8acb..a4a8b2cf107 100644 --- a/policyengine_us/variables/gov/states/de/tax/income/subtractions/de_subtractions.py +++ b/policyengine_us/variables/gov/states/de/tax/income/subtractions/de_subtractions.py @@ -12,5 +12,14 @@ class de_subtractions(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.de.tax.income.subtractions total_subtractions = add(person, period, p.subtractions) + # A tax unit dependent's U.S. government interest is on the dependent's + # own return and never in the filer's federal AGI, so it is not + # subtracted here, where each person's amounts are summed into the + # filer's. + if "us_govt_interest_person" in p.subtractions: + dependent = person("is_tax_unit_dependent", period) + total_subtractions = total_subtractions - dependent * person( + "us_govt_interest_person", period + ) # Prevent negative subtractions from acting as additions return max_(0, total_subtractions) diff --git a/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py b/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py index 8964b2b87f6..af7410b3698 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py @@ -14,11 +14,14 @@ class ia_reportable_social_security(Variable): defined_for = StateCode.IA def formula(tax_unit, period, parameters): - benefits = add(tax_unit, period, ["social_security"]) + # The worksheet uses the head's and spouse's amounts from their federal + # return; a tax unit dependent's benefits and interest are on the + # dependent's own return. + benefits = tax_unit_non_dep_add(tax_unit, period, ["social_security"]) income = ( tax_unit("adjusted_gross_income", period) - add(tax_unit, period, ["taxable_social_security"]) - + add(tax_unit, period, ["tax_exempt_interest_income"]) + + tax_unit_non_dep_add(tax_unit, period, ["tax_exempt_interest_income"]) ) p = parameters(period).gov.states.ia.tax.income filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/il/tax/income/base_income/il_base_income_additions.py b/policyengine_us/variables/gov/states/il/tax/income/base_income/il_base_income_additions.py index b2e461a9c0e..2f2e47ebb02 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/base_income/il_base_income_additions.py +++ b/policyengine_us/variables/gov/states/il/tax/income/base_income/il_base_income_additions.py @@ -9,4 +9,10 @@ class il_base_income_additions(Variable): definition_period = YEAR defined_for = StateCode.IL - adds = "gov.states.il.tax.income.base.additions" + def formula(tax_unit, period, parameters): + # Illinois adds these items to federal adjusted gross income, which + # leaves out a tax unit dependent's income (the dependent files their + # own return), so only the head's and spouse's amounts count, such as + # their federally tax-exempt interest (IL-1040 line 2). + p = parameters(period).gov.states.il.tax.income.base + return tax_unit_non_dep_add(tax_unit, period, p.additions) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py index 73a8e2a1ab9..a02cefe9585 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_long_term_loss_against_dividends.py @@ -21,11 +21,16 @@ class ma_long_term_loss_against_dividends(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): - short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. + short_term_capital_gains = tax_unit_non_dep_add( + tax_unit, period, ["short_term_capital_gains"] + ) nonnegative_short_term_capital_gains = max_(0, short_term_capital_gains) # Massachusetts Schedule D, line 13 includes capital gain # distributions reported without a federal Schedule D (line 6). - long_term_capital_gains = add( + long_term_capital_gains = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] ) long_term_capital_loss = max_(0, -long_term_capital_gains) @@ -33,7 +38,7 @@ def formula(tax_unit, period, parameters): remaining_long_term_loss = max_( 0, long_term_capital_loss - nonnegative_short_term_capital_gains ) - dividends = add(tax_unit, period, ["dividend_income"]) + dividends = tax_unit_non_dep_add(tax_unit, period, ["dividend_income"]) short_term_loss_applied = tax_unit( "ma_short_term_loss_against_dividends", period ) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py index 5e28ac28c6f..96503dfce6b 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_adjusted_dividends.py @@ -15,7 +15,10 @@ class ma_part_a_adjusted_dividends(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): - dividends = add(tax_unit, period, ["dividend_income"]) + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. + dividends = tax_unit_non_dep_add(tax_unit, period, ["dividend_income"]) losses_applied = add( tax_unit, period, diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py index 5cdeac3d392..1fd6af15b33 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_a_agi.py @@ -11,12 +11,17 @@ class ma_part_a_agi(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. part_a_gross_income = tax_unit("ma_part_a_gross_income", period) - short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + short_term_capital_gains = tax_unit_non_dep_add( + tax_unit, period, ["short_term_capital_gains"] + ) nonnegative_short_term_capital_gains = max_(0, short_term_capital_gains) # Massachusetts Schedule D, line 13 includes capital gain # distributions reported without a federal Schedule D (line 6). - long_term_capital_gains = add( + long_term_capital_gains = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] ) long_term_capital_loss = max_(0, -long_term_capital_gains) @@ -36,7 +41,7 @@ def formula(tax_unit, period, parameters): ) tax = parameters(period).gov.states.ma.tax.income - long_term_capital_gains_on_collectibles = add( + long_term_capital_gains_on_collectibles = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains_on_collectibles"] ) nonnegative_long_term_capital_gains_on_collectibles = max_( diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py index a214438fb47..8b845c74870 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_part_c_agi.py @@ -15,10 +15,15 @@ class ma_part_c_agi(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. # Schedule B applies a short-term loss first against up to $2,000 of # dividends (line 20). Only the rest (line 21) offsets long-term # gains (line 22 and Schedule D, lines 14 and 15). - short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + short_term_capital_gains = tax_unit_non_dep_add( + tax_unit, period, ["short_term_capital_gains"] + ) short_term_capital_loss = max_(0, -short_term_capital_gains) loss_against_dividends = tax_unit( "ma_short_term_loss_against_dividends", period @@ -26,7 +31,7 @@ def formula(tax_unit, period, parameters): remaining_short_term_loss = short_term_capital_loss - loss_against_dividends # Massachusetts Schedule D, line 13 includes capital gain # distributions reported without a federal Schedule D (line 6). - long_term_capital_gains = add( + long_term_capital_gains = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] ) return max_(0, long_term_capital_gains - remaining_short_term_loss) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py index b480d00f788..35880683720 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/adjusted_gross_income/ma_short_term_loss_against_dividends.py @@ -20,9 +20,14 @@ class ma_short_term_loss_against_dividends(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): - short_term_capital_gains = add(tax_unit, period, ["short_term_capital_gains"]) + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. + short_term_capital_gains = tax_unit_non_dep_add( + tax_unit, period, ["short_term_capital_gains"] + ) short_term_capital_loss = max_(0, -short_term_capital_gains) - dividends = add(tax_unit, period, ["dividend_income"]) + dividends = tax_unit_non_dep_add(tax_unit, period, ["dividend_income"]) cap = parameters( period ).gov.states.ma.tax.income.capital_gains.deductible_against_interest_dividends diff --git a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.py b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.py index 9bfa54ab311..4d873c8afcf 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_a_gross_income.py @@ -11,8 +11,11 @@ class ma_part_a_gross_income(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): - dividends = add(tax_unit, period, ["dividend_income"]) + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. + dividends = tax_unit_non_dep_add(tax_unit, period, ["dividend_income"]) nonnegative_short_term_capital_gains = max_( - 0, add(tax_unit, period, ["short_term_capital_gains"]) + 0, tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) ) return dividends + nonnegative_short_term_capital_gains diff --git a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.py b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.py index cb800ca68d1..85d2c80122a 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_part_c_gross_income.py @@ -11,13 +11,16 @@ class ma_part_c_gross_income(Variable): defined_for = StateCode.MA def formula(tax_unit, period, parameters): + # Part B gross income starts from irs_gross_income, which leaves out + # tax unit dependents, whose interest, dividends and gains are on their + # own returns, so Parts A and C count only the head's and spouse's. # Long-term capital gains, with capital gain distributions reported # without a federal Schedule D (Massachusetts Schedule D, line 6) - ltcg = add( + ltcg = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] ) # Short-term capital gains (can be negative) - stcg = add(tax_unit, period, ["short_term_capital_gains"]) + stcg = tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) # Per MA Schedule B line 22, short-term losses offset long-term gains stcg_losses = min_(0, stcg) return max_(0, ltcg + stcg_losses) diff --git a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py index 2a338d9cfcc..07decd4f399 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/taxable_income/ma_part_b_taxable_income_deductions.py @@ -35,7 +35,11 @@ def formula(tax_unit, period, parameters): # Bank interest deduction. filing_status = tax_unit("ma_filing_status", period) if tax.exemptions.interest.in_effect: - bank_interest = add(tax_unit, period, ["taxable_interest_income"]) + # The head's and spouse's interest: a tax unit dependent's is on + # the dependent's own return. + bank_interest = tax_unit_non_dep_add( + tax_unit, period, ["taxable_interest_income"] + ) bank_interest_deduction = min_( tax.exemptions.interest.amount[filing_status], bank_interest, diff --git a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py index 13027bbfbbd..dc5cbc72cad 100644 --- a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py +++ b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py @@ -17,7 +17,9 @@ def formula(tax_unit, period, parameters): # Get net capital gains (sum of long-term and short-term) # NOTE: This is the basic implementation using readily available variables - total_capital_gains = add( + # A tax unit dependent's gains are on the dependent's own return, so + # only the head's and spouse's gains count. + total_capital_gains = tax_unit_non_dep_add( tax_unit, period, ["short_term_capital_gains", "long_term_capital_gains"], diff --git a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py index 7511a063e49..d1a62fd9e23 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py +++ b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/me_sales_and_property_tax_fairness_credit_income.py @@ -8,8 +8,15 @@ class me_sales_and_property_tax_fairness_credit_income(Variable): label = "Maine sales and property tax fairness credit total income" definition_period = YEAR reference = ( - "https://legislature.maine.gov/statutes/36/title36sec5219-KK.html" # 1. D. + "https://legislature.maine.gov/statutes/36/title36sec5219-KK.html", # 1. D. + "https://legislature.maine.gov/statutes/36/title36sec5213-A.html", # 1. B. ) defined_for = StateCode.ME - adds = "gov.states.me.tax.income.credits.fairness.income_sources" + def formula(tax_unit, period, parameters): + # 36 M.R.S. 5213-A(1)(B) and 5219-KK(1)(D): federal adjusted gross + # income increased by interest received to the extent not included in + # it. Federal AGI is the head's and spouse's, so the interest is too: a + # tax unit dependent's is on the dependent's own return. + p = parameters(period).gov.states.me.tax.income.credits.fairness + return tax_unit_non_dep_add(tax_unit, period, p.income_sources) diff --git a/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py b/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py index 74d3c37ea4f..ca3158b101d 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py @@ -41,12 +41,19 @@ def formula(tax_unit, period, parameters): # ... alternative benefit subtraction amount us_gross_income = add(tax_unit, period, ["irs_gross_income"]) adj_income = us_gross_income - us_taxable_oasdi - total_oasdi = add(tax_unit, period, ["social_security"]) + # The worksheet uses the head's and spouse's amounts from their federal + # return; a tax unit dependent's benefits, interest and student loan + # interest are on the dependent's own return. + total_oasdi = tax_unit_non_dep_add(tax_unit, period, ["social_security"]) oasdi_amount = total_oasdi * total_benefit_fraction - tax_exempt_int = add(tax_unit, period, ["tax_exempt_interest_income"]) + tax_exempt_int = tax_unit_non_dep_add( + tax_unit, period, ["tax_exempt_interest_income"] + ) sum_income = adj_income + oasdi_amount + tax_exempt_int us_ald = tax_unit("above_the_line_deductions", period) - student_loan_int = add(tax_unit, period, ["student_loan_interest"]) + student_loan_int = tax_unit_non_dep_add( + tax_unit, period, ["student_loan_interest"] + ) mn_ald = max_(0, us_ald - student_loan_int) income = max_(0, sum_income - mn_ald) net_income = max_(0, income - income_amount) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py index 56cdef43d71..8e4356e2cb7 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py @@ -14,12 +14,13 @@ def formula(tax_unit, period, parameters): # Pool federal AGI at tax unit level agi = add(tax_unit, period, ["adjusted_gross_income_person"]) - # Pool Montana additions at tax unit level - additions = add(tax_unit, period, ["mt_additions"]) - - # Pool Montana subtractions at tax unit level - # This allows spouse's subtractions to offset head's income - subtractions = add(tax_unit, period, ["mt_subtractions"]) + # Pool the head's and spouse's Montana additions and subtractions. + # This allows spouse's subtractions to offset head's income. Federal + # AGI leaves out a tax unit dependent's income, which is on the + # dependent's own return, so the dependent's adjustments (such as + # their U.S. government interest) are left out too. + additions = tax_unit_non_dep_add(tax_unit, period, ["mt_additions"]) + subtractions = tax_unit_non_dep_add(tax_unit, period, ["mt_subtractions"]) # Apply pooled subtractions to pooled income reduced_agi = max_(agi + additions - subtractions, 0) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py index cebfa573b8f..f80126102eb 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py @@ -15,8 +15,11 @@ def formula(tax_unit, period, parameters): # Joint filers run the Taxable Social Security Benefits Schedule once # in a single column, combining both spouses' lines 1-9 and applying # the joint base amounts once (Form 2, page 6). + # The worksheet uses the head's and spouse's amounts from their federal + # return; a tax unit dependent's benefits, interest and adjustments are + # on the dependent's own return. # line 1 total net SS amount - social_security = add(tax_unit, period, ["social_security"]) + social_security = tax_unit_non_dep_add(tax_unit, period, ["social_security"]) # line 2 SS multiplied by the base rate social_security_benefits_fraction = social_security * p_irs.base.benefit_cap # line 3: irs_gross_income - taxable_social_security @@ -24,7 +27,7 @@ def formula(tax_unit, period, parameters): taxable_ss = add(tax_unit, period, ["taxable_social_security"]) reduced_gross_income = max_(gross_income - taxable_ss, 0) # line 5: tax exempt interest income - tax_exempt_interest_income = add( + tax_exempt_interest_income = tax_unit_non_dep_add( tax_unit, period, ["tax_exempt_interest_income"] ) # line 6: Sum of line 2, 3, 4, 5 @@ -34,11 +37,11 @@ def formula(tax_unit, period, parameters): + tax_exempt_interest_income ) # line 7: Remove the student loans from the above the line deductions - ald_less_student_loan = add( + ald_less_student_loan = tax_unit_non_dep_add( tax_unit, period, ["mt_applicable_ald_deductions"] - ) - add(tax_unit, period, ["student_loan_interest_ald"]) + ) - tax_unit_non_dep_add(tax_unit, period, ["student_loan_interest_ald"]) # line 8: Montana subtractions + ald - subtractions = add(tax_unit, period, ["mt_subtractions"]) + subtractions = tax_unit_non_dep_add(tax_unit, period, ["mt_subtractions"]) increased_subtractions = subtractions + ald_less_student_loan # line 9: line 6 - line 8, if line 8 >= line 6, return 0 income_reduced_by_subtractions = max_( diff --git a/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_applicable_threshold_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_applicable_threshold_joint.py index d25a9981bc5..0a147c1233f 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_applicable_threshold_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_applicable_threshold_joint.py @@ -12,8 +12,10 @@ class mt_capital_gains_tax_applicable_threshold_joint(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.mt.tax.income.main.capital_gains - ltcg = add(tax_unit, period, ["long_term_capital_gains"]) - stcg = add(tax_unit, period, ["short_term_capital_gains"]) + # The head's and spouse's gains: a tax unit dependent's are on the + # dependent's own return, as federal taxable income leaves them out. + ltcg = tax_unit_non_dep_add(tax_unit, period, ["long_term_capital_gains"]) + stcg = tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) capital_gains = max_(min_(ltcg, ltcg + stcg), 0) taxable_income = add(tax_unit, period, ["mt_taxable_income_joint"]) filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.py index ac9960a67f9..86c865d14c2 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.py @@ -19,8 +19,10 @@ def formula(tax_unit, period, parameters): # Line 1 — aggregate across persons for joint filing taxable_income = add(tax_unit, period, ["mt_taxable_income_joint"]) # Line 2 — lesser of net LTCG and total net capital gain - ltcg = add(tax_unit, period, ["long_term_capital_gains"]) - stcg = add(tax_unit, period, ["short_term_capital_gains"]) + # The head's and spouse's gains: a tax unit dependent's are on the + # dependent's own return, as federal taxable income leaves them out. + ltcg = tax_unit_non_dep_add(tax_unit, period, ["long_term_capital_gains"]) + stcg = tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) net_cg = ltcg + stcg capital_gains = max_(min_(ltcg, net_cg), 0) # Line 3 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py index 8226eaf09cb..ffa4892ef25 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/capital_gain/mt_capital_gain_credit.py @@ -14,5 +14,8 @@ def formula(person, period, parameters): p = parameters(period).gov.states.mt.tax.income.credits.capital_gain net_capital_gain = person("capital_gains", period) + # A tax unit dependent's gains are on the dependent's own return, so + # the dependent has no credit on this one. + head_or_spouse = ~person("is_tax_unit_dependent", period) # The net capital gain variable is capped at 0 - return p.percentage * net_capital_gain + return p.percentage * net_capital_gain * head_or_spouse diff --git a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/interest/mt_interest_exemption_person.py b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/interest/mt_interest_exemption_person.py index 3bce8d6a88a..2c3d63c1bd7 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/exemptions/interest/mt_interest_exemption_person.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/exemptions/interest/mt_interest_exemption_person.py @@ -12,7 +12,11 @@ class mt_interest_exemption_person(Variable): def formula(person, period, parameters): # Allocate the interest exemption to head/spouse based on share of interest income. - interest_income = person("taxable_interest_income", period) + # The tax unit exemption counts only the head's and spouse's interest + # (mt_interest_exemption), so the shares do too: a tax unit dependent's + # interest is on the dependent's own return. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + interest_income = person("taxable_interest_income", period) * head_or_spouse total_interest_income = person.tax_unit.sum(interest_income) total_deduction = person.tax_unit("mt_interest_exemption", period) deduction_rate = np.zeros_like(total_interest_income) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/tax_calculation/mt_regular_income_tax_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/tax_calculation/mt_regular_income_tax_joint.py index 5dfab7c3707..036077b8d55 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/tax_calculation/mt_regular_income_tax_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/tax_calculation/mt_regular_income_tax_joint.py @@ -16,8 +16,10 @@ def formula(tax_unit, period, parameters): status = filing_status.possible_values if p.capital_gains.in_effect: - ltcg = add(tax_unit, period, ["long_term_capital_gains"]) - stcg = add(tax_unit, period, ["short_term_capital_gains"]) + # The head's and spouse's gains: a tax unit dependent's are on the + # dependent's own return, as federal taxable income leaves them out. + ltcg = tax_unit_non_dep_add(tax_unit, period, ["long_term_capital_gains"]) + stcg = tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) net_cg = ltcg + stcg # Montana Form 2 line 2 uses the federal net long-term capital gain # amount, which is limited by any short-term capital losses. diff --git a/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_ltcg_subtraction.py b/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_ltcg_subtraction.py index cadce9d85f4..940cb4f0963 100644 --- a/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_ltcg_subtraction.py +++ b/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_ltcg_subtraction.py @@ -14,12 +14,28 @@ class nd_ltcg_subtraction(Variable): "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2021-iit/individual-income-tax-booklet-2021.pdf#page=14", "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2022-iit/form-nd-1-2022.pdf#page=1", # line 7 "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2022-iit/2022-individual-income-tax-booklet.pdf#page=14", + "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2025-iit/2025-individual-income-tax-booklet.pdf#page=14", # line 6 + "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2025-iit/2025-individual-income-tax-booklet.pdf#page=15", # line 6 worksheet + "https://ndlegis.gov/cencode/t57c38.pdf#nameddest=57-38-30p3", # 2(d)(1) ) defined_for = StateCode.ND def formula(tax_unit, period, parameters): - ltcg = add(tax_unit, period, ["long_term_capital_gains"]) - stcg = add(tax_unit, period, ["short_term_capital_gains"]) - net_ltcg = min_(ltcg, ltcg + stcg) + # N.D.C.C. 57-38-30.3(2)(d)(1) reduces the individual's federal taxable + # income by 40% of "the taxpayer's net long-term capital gain"; the + # Form ND-1 line 6 worksheet takes it from the filer's Schedule D. A tax + # unit dependent's gains are on the dependent's own return. + # Worksheet line 1 is Schedule D line 15, the net long-term gain with + # capital gain distributions, which are long-term (26 U.S.C. + # 852(b)(3)(B)) and go on line 3 when there is no Schedule D. Line 2 is + # Schedule D line 16, which adds the net short-term gain or loss. If + # either is zero or less, no exclusion is allowed. + long_term = tax_unit_non_dep_add( + tax_unit, period, ["long_term_capital_gains"] + ) + tax_unit_non_dep_add(tax_unit, period, ["non_sch_d_capital_gains"]) + short_term = tax_unit_non_dep_add( + tax_unit, period, ["short_term_capital_gains"] + ) + net_ltcg = max_(0, min_(long_term, long_term + short_term)) p = parameters(period).gov.states.nd.tax.income return net_ltcg * p.taxable_income.subtractions.ltcg_fraction diff --git a/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_qdiv_subtraction.py b/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_qdiv_subtraction.py index e8c8364f978..0b8652be9dd 100644 --- a/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_qdiv_subtraction.py +++ b/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_qdiv_subtraction.py @@ -12,10 +12,16 @@ class nd_qdiv_subtraction(Variable): "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2021-iit/individual-income-tax-booklet-2021.pdf#page=15", "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2022-iit/form-nd-1-2022.pdf#page=1", # line 13 "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2022-iit/2022-individual-income-tax-booklet.pdf#page=15", + "https://www.tax.nd.gov/sites/www/files/documents/forms/individual/2025-iit/2025-individual-income-tax-booklet.pdf#page=15", # line 13 + "https://ndlegis.gov/cencode/t57c38.pdf#nameddest=57-38-30p3", # 2(d)(2) ) defined_for = StateCode.ND def formula(tax_unit, period, parameters): - qdiv = add(tax_unit, period, ["qualified_dividend_income"]) + # N.D.C.C. 57-38-30.3(2)(d)(2) reduces the individual's federal taxable + # income by 40% of qualified dividends; Form ND-1 line 13 takes them + # from Form 1040 line 3a. A tax unit dependent's dividends are on the + # dependent's own return, so only the head's and spouse's count. + qdiv = tax_unit_non_dep_add(tax_unit, period, ["qualified_dividend_income"]) p = parameters(period).gov.states.nd.tax.income return qdiv * p.taxable_income.subtractions.qdiv_fraction diff --git a/policyengine_us/variables/gov/states/nh/tax/income/nh_taxable_income.py b/policyengine_us/variables/gov/states/nh/tax/income/nh_taxable_income.py index 7f1a9ef1c3d..255b15f477f 100644 --- a/policyengine_us/variables/gov/states/nh/tax/income/nh_taxable_income.py +++ b/policyengine_us/variables/gov/states/nh/tax/income/nh_taxable_income.py @@ -13,7 +13,12 @@ class nh_taxable_income(Variable): ) defined_for = StateCode.NH - # New Hampshire allows for negative taxable income. - # It limits tax to nonnegative values in the tax computation instead. - adds = ["dividend_income", "interest_income"] - subtracts = ["nh_total_exemptions"] + def formula(tax_unit, period, parameters): + # The head's and spouse's interest and dividends: a tax unit dependent + # is a separate individual who reports their own on their own return. + income = tax_unit_non_dep_add( + tax_unit, period, ["dividend_income", "interest_income"] + ) + # New Hampshire allows for negative taxable income. + # It limits tax to nonnegative values in the tax computation instead. + return income - tax_unit("nh_total_exemptions", period) diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_deductions.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_deductions.py index a1b7752db0e..72e43e18425 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_deductions.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_deductions.py @@ -13,4 +13,17 @@ class oh_deductions(Variable): "https://cms7files1.revize.com/starkcountyoh/Document_center/Offices/Auditor/Services/Homestead%20Exemption/Ohio_Adj_Gross_Income.pdf", ) defined_for = StateCode.OH - adds = "gov.states.oh.tax.income.deductions.deductions" + + def formula(person, period, parameters): + p = parameters(period).gov.states.oh.tax.income.deductions + total_subtractions = add(person, period, p.deductions) + # A tax unit dependent's U.S. government interest is on the dependent's + # own return and never in the filer's federal AGI, so it is not + # subtracted here, where each person's amounts are summed into the + # filer's. + if "us_govt_interest_person" in p.deductions: + dependent = person("is_tax_unit_dependent", period) + total_subtractions = total_subtractions - dependent * person( + "us_govt_interest_person", period + ) + return total_subtractions diff --git a/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.py b/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.py index 5e84425cb75..5bf88b18083 100644 --- a/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.py +++ b/policyengine_us/variables/gov/states/pa/tax/income/forgiveness/pa_eligibility_income.py @@ -9,7 +9,16 @@ class pa_eligibility_income(Variable): label = "PA eligibility income" unit = USD definition_period = YEAR - reference = "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2021/2021_pa-40sp.pdf" + reference = ( + "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2021/2021_pa-40sp.pdf", + "https://www.pa.gov/en/agencies/revenue/forms-and-publications/pa-personal-income-tax-guide/tax-forgiveness.html", + ) defined_for = StateCode.PA - adds = "gov.states.pa.tax.income.forgiveness.eligibility_income_sources" + def formula(tax_unit, period, parameters): + # Eligibility income is the claimant's, and the spouse's for a married + # claimant; a dependent child claims forgiveness on their own return + # (PA Personal Income Tax Guide, Tax Forgiveness). So only the head's + # and spouse's amounts count. + p = parameters(period).gov.states.pa.tax.income.forgiveness + return tax_unit_non_dep_add(tax_unit, period, p.eligibility_income_sources) diff --git a/policyengine_us/variables/gov/states/sc/tax/income/deductions/net_capital_gain/sc_net_capital_gain_deduction.py b/policyengine_us/variables/gov/states/sc/tax/income/deductions/net_capital_gain/sc_net_capital_gain_deduction.py index 307963dcb82..b457da287a7 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/deductions/net_capital_gain/sc_net_capital_gain_deduction.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/deductions/net_capital_gain/sc_net_capital_gain_deduction.py @@ -15,8 +15,12 @@ class sc_net_capital_gain_deduction(Variable): ) def formula(tax_unit, period, parameters): - ltcg = add(tax_unit, period, ["long_term_capital_gains"]) - stcg = add(tax_unit, period, ["short_term_capital_gains"]) + # S.C. Code 12-6-1150(A) deducts 44% of the net capital gain included + # in the individual's federal taxable income. A tax unit dependent's + # gains are on the dependent's own return, so only the head's and + # spouse's gains count. + ltcg = tax_unit_non_dep_add(tax_unit, period, ["long_term_capital_gains"]) + stcg = tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) capped_stcg = min_(0, stcg) base = max_(0, ltcg + capped_stcg) p = parameters(period).gov.states.sc.tax.income.deductions.net_capital_gain diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/child_tax_credit/ut_ctc_potential.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/child_tax_credit/ut_ctc_potential.py index dd0e11dbdbb..7880fc85c12 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/child_tax_credit/ut_ctc_potential.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/child_tax_credit/ut_ctc_potential.py @@ -21,7 +21,9 @@ def formula(tax_unit, period, parameters): base_amount = p.amount * eligible_children # Utah reduces the CTC based on the state income in addition to # tax exempt interest income - relevant_income = add( + # The head's and spouse's interest, as Utah taxable income leaves out + # a tax unit dependent's income. + relevant_income = tax_unit_non_dep_add( tax_unit, period, ["tax_exempt_interest_income", "ut_taxable_income"], diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.py index c47a3a17b5c..6470cd0e46f 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.py @@ -17,12 +17,20 @@ def formula(tax_unit, period: Period, parameters): """ p = parameters(period).gov.states.ut.tax.income p_credit = p.credits.retirement - age = tax_unit.members("age", period) + person = tax_unit.members + age = person("age", period) birth_year = -(age - period.start.year) - meets_age_conditions = birth_year <= p_credit.birth_year + # The credit is for the claimant and, on a joint return, the spouse; + # a tax unit dependent is not a claimant on this return. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + meets_age_conditions = (birth_year <= p_credit.birth_year) & head_or_spouse max_value = p_credit.max * tax_unit.sum(meets_age_conditions) total_income = tax_unit("ut_total_income", period) - tax_exempt_interest = add(tax_unit, period, ["tax_exempt_interest_income"]) + # The head's and spouse's municipal bond interest, as Utah total + # income leaves out a tax unit dependent's income. + tax_exempt_interest = tax_unit_non_dep_add( + tax_unit, period, ["tax_exempt_interest_income"] + ) modified_agi = total_income + tax_exempt_interest filing_status = tax_unit("filing_status", period) phase_out_income = max_( diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.py index 8cf7f52a458..fa8fcaa6b2a 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.py @@ -22,7 +22,9 @@ def formula(tax_unit, period, parameters): # Skip line 3 (muncipal bond interest) # Skip line 4 (subtract line 3 from line 2) - tax_exempt_interest = add( + # The head's and spouse's municipal bond interest, as Utah total + # income leaves out a tax unit dependent's income. + tax_exempt_interest = tax_unit_non_dep_add( tax_unit, period, ["tax_exempt_interest_income"] ) # Line 5 modified_agi = total_income + tax_exempt_interest # Line 6 diff --git a/policyengine_us/variables/gov/states/va/tax/income/va_subtractions.py b/policyengine_us/variables/gov/states/va/tax/income/va_subtractions.py index 884c338e747..b959254d017 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/va_subtractions.py +++ b/policyengine_us/variables/gov/states/va/tax/income/va_subtractions.py @@ -11,6 +11,9 @@ class va_subtractions(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.va.tax.income.subtractions - total_subtractions = add(tax_unit, period, p.subtractions) + # Virginia subtracts these items from federal adjusted gross income, + # which leaves out a tax unit dependent's income (the dependent files + # their own return), so only the head's and spouse's amounts count. + total_subtractions = tax_unit_non_dep_add(tax_unit, period, p.subtractions) # Prevent negative subtractions from acting as additions return max_(0, total_subtractions) diff --git a/policyengine_us/variables/gov/states/va/tax/income/va_subtractions_person.py b/policyengine_us/variables/gov/states/va/tax/income/va_subtractions_person.py index da551291e2c..fd364a3f52c 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/va_subtractions_person.py +++ b/policyengine_us/variables/gov/states/va/tax/income/va_subtractions_person.py @@ -18,5 +18,8 @@ def formula(person, period, parameters): # the per-person amounts sum to it exactly. p = parameters(period).gov.states.va.tax.income.subtractions total_subtractions = add(person, period, p.subtractions) + # A tax unit dependent's income is on the dependent's own return, so + # the dependent has no subtractions on this one, as in va_subtractions. + head_or_spouse = ~person("is_tax_unit_dependent", period) # Prevent negative subtractions from acting as additions. - return max_(0, total_subtractions) + return max_(0, total_subtractions) * head_or_spouse diff --git a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py index d3e8aef242a..c6f222df2ac 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py @@ -19,7 +19,11 @@ def formula(tax_unit, period, parameters): # depreciable personal property (except farm property/timber), and real estate. # Since standard capital gains inputs map to financial instruments (ineligible), # we only use explicitly designated VT-eligible capital gains. - eligible_gains = add( + # 32 V.S.A. 5811(21)(B)(ii) excludes 40% of gains on "assets held by + # the taxpayer for more than three years", to the extent included in + # federal adjusted gross income. A tax unit dependent's gains are on + # the dependent's own return, so only the head's and spouse's count. + eligible_gains = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains_on_assets_eligible_for_vt_exclusion"], diff --git a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/vt_additions.py b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/vt_additions.py index a434ad17bb6..a78a265429f 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/vt_additions.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/vt_additions.py @@ -14,4 +14,9 @@ class vt_additions(Variable): "https://legislature.vermont.gov/statutes/section/32/151/05811", ) - adds = ["tax_exempt_interest_income"] + def formula(tax_unit, period, parameters): + # 32 V.S.A. 5811(21)(A)(i) adds the taxpayer's interest income from + # non-Vermont state and local obligations excluded from federal + # adjusted gross income. A tax unit dependent's interest is on the + # dependent's own return, so only the head's and spouse's count. + return tax_unit_non_dep_add(tax_unit, period, ["tax_exempt_interest_income"]) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/millionaires_tax/wa_millionaires_tax_base_income.py b/policyengine_us/variables/gov/states/wa/tax/income/millionaires_tax/wa_millionaires_tax_base_income.py index 4989765b17e..ea7dbc9a28a 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/millionaires_tax/wa_millionaires_tax_base_income.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/millionaires_tax/wa_millionaires_tax_base_income.py @@ -15,8 +15,14 @@ class wa_millionaires_tax_base_income(Variable): def formula(tax_unit, period, parameters): agi = tax_unit("adjusted_gross_income", period) - long_term_capital_gains = add(tax_unit, period, ["long_term_capital_gains"]) - tax_exempt_interest = add(tax_unit, period, ["tax_exempt_interest_income"]) + # Federal adjusted gross income leaves out a tax unit dependent's + # income, so the gains and interest are the head's and spouse's too. + long_term_capital_gains = tax_unit_non_dep_add( + tax_unit, period, ["long_term_capital_gains"] + ) + tax_exempt_interest = tax_unit_non_dep_add( + tax_unit, period, ["tax_exempt_interest_income"] + ) p = parameters(period).gov.states.wa.tax.income.capital_gains charitable_contributions = add( diff --git a/policyengine_us/variables/gov/states/wa/tax/income/wa_capital_gains_tax.py b/policyengine_us/variables/gov/states/wa/tax/income/wa_capital_gains_tax.py index c12a32defdd..d21b2f54e20 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/wa_capital_gains_tax.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/wa_capital_gains_tax.py @@ -13,7 +13,10 @@ def formula(tax_unit, period, parameters): in_effect = parameters(period).gov.states.wa.tax.income.in_effect if in_effect: p = parameters(period).gov.states.wa.tax.income.capital_gains - ltcg = add(tax_unit, period, ["long_term_capital_gains"]) + # The tax is on each individual's (and spouses' combined) long-term + # capital gains from the federal return. A tax unit dependent's + # gains are on the dependent's own return. + ltcg = tax_unit_non_dep_add(tax_unit, period, ["long_term_capital_gains"]) # Deduct charitable contributions. charitable_contributions = add( tax_unit, diff --git a/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_additions.py b/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_additions.py index 4f18cea6869..2e03a046c20 100644 --- a/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_additions.py +++ b/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_additions.py @@ -14,4 +14,11 @@ class wi_additions(Variable): "https://www.revenue.wi.gov/TaxForms2022/2022-ScheduleAD-Inst.pdf", ) defined_for = StateCode.WI - adds = "gov.states.wi.tax.income.additions.sources" + + def formula(tax_unit, period, parameters): + # Wisconsin adds these items to federal adjusted gross income, which + # leaves out a tax unit dependent's income (the dependent files their + # own return), so only the head's and spouse's amounts count, such as + # their interest on other states' and municipalities' obligations. + p = parameters(period).gov.states.wi.tax.income.additions + return tax_unit_non_dep_add(tax_unit, period, p.sources) diff --git a/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_capital_loss.py b/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_capital_loss.py index da97129c235..8108e51be37 100644 --- a/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_capital_loss.py +++ b/policyengine_us/variables/gov/states/wi/tax/income/additions/wi_capital_loss.py @@ -16,7 +16,9 @@ class wi_capital_loss(Variable): def formula(tax_unit, period, parameters): # calculate Schedule WD, Line 18 GAIN_SOURCES = ["short_term_capital_gains", "long_term_capital_gains"] - netcg = add(tax_unit, period, GAIN_SOURCES) + # Only the head's and spouse's gains and losses are on this return; a + # tax unit dependent reports theirs on their own return. + netcg = tax_unit_non_dep_add(tax_unit, period, GAIN_SOURCES) # return Schedule WD, Line 28, as a positive amount as on form p = parameters(period).gov.states.wi.tax.income.additions filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/wi/tax/income/subtractions/wi_capital_gain_loss_subtraction.py b/policyengine_us/variables/gov/states/wi/tax/income/subtractions/wi_capital_gain_loss_subtraction.py index 6544108a00f..049bb6bf2cf 100644 --- a/policyengine_us/variables/gov/states/wi/tax/income/subtractions/wi_capital_gain_loss_subtraction.py +++ b/policyengine_us/variables/gov/states/wi/tax/income/subtractions/wi_capital_gain_loss_subtraction.py @@ -18,11 +18,14 @@ class wi_capital_gain_loss_subtraction(Variable): def formula(tax_unit, period, parameters): # calculate Schedule WD, Line 8 - stcg_net = add(tax_unit, period, ["short_term_capital_gains"]) + # Schedule WD starts from the filer's federal Schedule D. A tax unit + # dependent's gains are on the dependent's own return, so only the + # head's and spouse's gains count. + stcg_net = tax_unit_non_dep_add(tax_unit, period, ["short_term_capital_gains"]) # calculate Schedule WD, Line 17 # Capital gain distributions retain their long-term character when # reported directly on Form 1040, without a federal Schedule D. - ltcg_net = add( + ltcg_net = tax_unit_non_dep_add( tax_unit, period, ["long_term_capital_gains", "non_sch_d_capital_gains"] ) # calculate Schedule WD, Line 18 diff --git a/policyengine_us/variables/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.py b/policyengine_us/variables/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.py index 2ec93c1a18e..04fec2fe9c8 100644 --- a/policyengine_us/variables/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.py +++ b/policyengine_us/variables/gov/states/wv/tax/income/credits/liftc/wv_low_income_family_tax_credit_agi.py @@ -7,11 +7,18 @@ class wv_low_income_family_tax_credit_agi(Variable): label = "Adjusted gross income for the West Virginia low-income family tax credit" unit = USD definition_period = YEAR - reference = "https://code.wvlegislature.gov/11-21-12/" # §11-21-12 (b) + # W. Va. Code 11-21-22a(d): "the federal adjusted gross income plus any + # applicable increasing West Virginia modifications plus any tax exempt + # interest income reported on the federal tax return". + reference = "https://code.wvlegislature.gov/11-21-22a/" defined_for = "wv_low_income_family_tax_credit_eligible" - adds = [ - "adjusted_gross_income", - "tax_exempt_interest_income", - "wv_additions", - ] + def formula(tax_unit, period, parameters): + # The tax-exempt interest is the head's and spouse's, reported on + # their federal return; a tax unit dependent's is on the dependent's + # own return, as federal adjusted gross income leaves it out. + return tax_unit_non_dep_add( + tax_unit, + period, + ["adjusted_gross_income", "tax_exempt_interest_income", "wv_additions"], + ) diff --git a/policyengine_us/variables/household/income/tax_unit/us_govt_interest.py b/policyengine_us/variables/household/income/tax_unit/us_govt_interest.py index 7604476910d..aa9e504e3b7 100644 --- a/policyengine_us/variables/household/income/tax_unit/us_govt_interest.py +++ b/policyengine_us/variables/household/income/tax_unit/us_govt_interest.py @@ -7,6 +7,19 @@ class us_govt_interest(Variable): label = "Interest on U.S. government obligations" unit = USD definition_period = YEAR - documentation = "Interest on U.S. government obligations such as U.S. savings bonds, U.S. Treasury bills, and U.S. government certificates." + documentation = "Interest on U.S. government obligations such as U.S. savings bonds, U.S. Treasury bills, and U.S. government certificates, received by the head and spouse." + reference = ( + # 31 U.S.C. 3124(a) exempts the interest from state income tax; states + # subtract it from federal adjusted gross income, which includes only + # the filer's own interest. + "https://www.law.cornell.edu/uscode/text/31/3124", + "https://legislature.vermont.gov/statutes/section/32/151/05811", # (21)(B)(i) + "https://ndlegis.gov/cencode/t57c38.pdf#nameddest=57-38-30p3", # 2(a) + ) - adds = ["us_govt_interest_person"] + def formula(tax_unit, period, parameters): + # States subtract this interest from federal adjusted gross income or + # taxable income. A tax unit dependent's interest is on the dependent's + # own return and never in the filer's federal AGI, so only the head's + # and spouse's interest counts. + return tax_unit_non_dep_add(tax_unit, period, ["us_govt_interest_person"]) From 1ef3c7ae2a205808502baebdd79403c3f43c26f1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 10:05:10 -0400 Subject: [PATCH 2/9] Use exact test amounts and leave the dependent's interest out of two UT cases The Montana and Utah Social Security credit cases now carry the exact half-cent amounts (1,765.725, 1,847.725 and 259.475) instead of values rounded past the 0.001 margin. In two existing Utah retirement credit cases person2 (age 64) is a tax unit dependent, so their tax-exempt interest is on their own return and no longer enters the modified AGI: 750 instead of 737.5, and 675 instead of 662.5. Co-Authored-By: Claude Opus 5.5 --- .../income/capital_gains/mt_capital_gains_tax_joint.yaml | 8 ++++---- .../retirement_credit/ut_retirement_credit_max.yaml | 9 +++++++-- .../ss_benefits_credit/ut_ss_benefits_credit_max.yaml | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml index b18389de370..b6c22336318 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/capital_gains/mt_capital_gains_tax_joint.yaml @@ -140,9 +140,9 @@ state_code: MT output: mt_capital_gains_tax_joint: 82 - mt_regular_income_tax_joint: 1_765.72 + mt_regular_income_tax_joint: 1_765.725 mt_capital_gains_tax_applicable_threshold_joint: 0 - mt_income_tax: 1_847.72 + mt_income_tax: 1_847.725 - name: Same household without the dependent's gains period: 2025 @@ -159,6 +159,6 @@ state_code: MT output: mt_capital_gains_tax_joint: 82 - mt_regular_income_tax_joint: 1_765.72 + mt_regular_income_tax_joint: 1_765.725 mt_capital_gains_tax_applicable_threshold_joint: 0 - mt_income_tax: 1_847.72 + mt_income_tax: 1_847.725 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml index 0812b78f10f..02b73857bd3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml @@ -65,7 +65,10 @@ members: [person1, person2] state_code: UT output: - ut_retirement_credit_max: 737.5 # phase-out reduction partially eliminates credit: 900-(30000+3*500-25000)/0.025 = 737.5 + # person1 is the head, person3 the spouse and person2 a dependent, whose + # tax-exempt interest is on their own return. Phase-out reduction: + # 900 - (30_000 + 2 * 500 - 25_000) * 0.025 = 750. + ut_retirement_credit_max: 750 - name: UT retirement credit max unit test 4 period: 2022 @@ -115,7 +118,9 @@ members: [person1, person2] state_code: UT output: - ut_retirement_credit_max: 662.5 + # person2 is a dependent, so only the head's and spouse's tax-exempt + # interest counts: 900 - (40_000 + 2 * 500 - 32_000) * 0.025 = 675. + ut_retirement_credit_max: 675 - name: Edge case test, birth year period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml index 875e5d85c1c..9f521aaeaf0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ss_benefits_credit/ut_ss_benefits_credit_max.yaml @@ -68,7 +68,7 @@ household: state_code: UT output: - ut_ss_benefits_credit_max: 259.48 + ut_ss_benefits_credit_max: 259.475 - name: Same household without the dependent's interest period: 2022 @@ -84,4 +84,4 @@ household: state_code: UT output: - ut_ss_benefits_credit_max: 259.48 + ut_ss_benefits_credit_max: 259.475 From db64d9f1adf0daf9ded7b689c16d150236223ee0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 23:25:59 -0400 Subject: [PATCH 3/9] Keep Alabama's subtraction whole and fix Virginia Guard, Michigan and Missouri lines From the independent review of the first commit: - Alabama AGI sums every member's interest, so it now also subtracts dependents_us_govt_interest (new, next to us_govt_interest, for state income definitions that count dependents' income). Without it the filer-only us_govt_interest left a dependent's U.S. government interest taxed (parent wages 100,000, child 5,000 of Treasury interest: AL AGI 105,000 instead of 100,000). - Virginia's National Guard subtraction leaves a dependent's pay out of both the cap and the per-person shares, so the filer keeps the full amount. - Michigan's senior interest, dividends and capital gains deduction counts only the head's and spouse's income (MCL 206.30(1)(p): "included in adjusted gross income"). - Missouri's capital gains subtraction takes a dependent's gains out of net_capital_gains, and its per-person split allocates among the head and spouse only. The Utah retirement credit case with SINGLE status now names its roles (one head, two dependents), and the property test drops the Vermont restriction now that #9855 is on main. Co-Authored-By: Claude Opus 5.5 --- ...ent_capital_income_state_tax_invariants.py | 33 +++++-------------- .../tax/income/al_us_govt_bond_interest.yaml | 19 +++++++++++ ...est_dividends_capital_gains_deduction.yaml | 18 ++++++++++ .../ut_retirement_credit_max.yaml | 19 ++++++----- .../va_national_guard_subtraction.yaml | 20 +++++++++++ ...erest_dividends_capital_gains_deduction.py | 4 ++- .../va_national_guard_subtraction.py | 7 +++- .../va_national_guard_subtraction_person.py | 7 +++- 8 files changed, 91 insertions(+), 36 deletions(-) diff --git a/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py index 686cbefb5db..b97457d4eb9 100644 --- a/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py +++ b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py @@ -34,17 +34,9 @@ up to the cap, vt_additions is the filers' tax-exempt interest, and us_govt_interest is the filers' U.S. government interest. -Two restrictions keep effects that are outside this check out of the draws: - -- The filers earn at least $80,000, so no earned income tax credit is at - stake. Vermont's is a share of the federal credit, whose investment income - test (eitc_relevant_investment_income) is not changed here. -- Vermont filers have at least $50,000 of their own long-term gain and no - short-term loss, and Vermont dependents have gains, not losses. Until - PolicyEngine/policyengine-us#9855 is merged, the $5,000 flat part of - vt_capital_gains_exclusion reads the federal adjusted net capital gain, - which on main still counts a dependent's gains and losses. With these - amounts the flat part is at its cap either way. +The filers earn at least $80,000, so no earned income tax credit is at +stake: Vermont's is a share of the federal credit, whose investment income +test (eitc_relevant_investment_income) is not changed here. Amounts are whole dollars of at most $400,000, so every sum is exact in single precision and twins are compared to the cent. @@ -91,25 +83,17 @@ def amount(low, high): @st.composite -def capital_income(draw, vermont, filer): +def capital_income(draw): """One person's capital income. U.S. government interest is part of taxable interest, and Vermont-eligible gains are part of long-term gains.""" - if vermont and filer: - long_term = draw(amount(50_000, 300_000)) - short_term = draw(amount(0, 20_000)) - elif vermont: - long_term = draw(amount(0, 300_000)) - short_term = draw(amount(0, 50_000)) - else: - long_term = draw(amount(-30_000, 300_000)) - short_term = draw(amount(-20_000, 50_000)) + long_term = draw(amount(-30_000, 300_000)) taxable_interest = draw(amount(0, 50_000)) return { "qualified_dividend_income": draw(amount(0, 50_000)), "non_qualified_dividend_income": draw(amount(0, 20_000)), "long_term_capital_gains": long_term, - "short_term_capital_gains": short_term, + "short_term_capital_gains": draw(amount(-20_000, 50_000)), "non_sch_d_capital_gains": draw(amount(0, 20_000)), "taxable_interest_income": taxable_interest, "us_govt_interest_person": draw(amount(0, int(taxable_interest))), @@ -123,20 +107,19 @@ def capital_income(draw, vermont, filer): @st.composite def tax_units(draw): state = draw(st.sampled_from(["ND", "VT"])) - vermont = state == "VT" n_filers = draw(st.integers(1, 2)) filers = [ { "age": draw(st.integers(30, 64)), "employment_income": draw(amount(80_000, 400_000)) if i == 0 else 0.0, - **draw(capital_income(vermont, filer=True)), + **draw(capital_income()), } for i in range(n_filers) ] dependents = [ { "age": draw(st.integers(1, 17)), - **draw(capital_income(vermont, filer=False)), + **draw(capital_income()), } for _ in range(draw(st.integers(1, 2))) ] diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_us_govt_bond_interest.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_us_govt_bond_interest.yaml index 92ca542576c..e952faeb60d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_us_govt_bond_interest.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_us_govt_bond_interest.yaml @@ -6,3 +6,22 @@ us_govt_interest_person: 5_000 output: al_agi: 5_000 + +- name: A dependent's interest, U.S. government interest included, stays off the filer's AGI + # Neither the child's interest nor its exemption is on the filer's return; + # us_govt_interest counts only the head's and spouse's. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 100_000 + child: + age: 10 + taxable_interest_income: 5_000 + us_govt_interest_person: 5_000 + household: + state_code: AL + output: + al_agi: 100_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml index f2610ec056d..1c458fc18be 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml @@ -118,3 +118,21 @@ state_code: MI output: # 2025 MI-1040 booklet, Schedule 1 line 28: $14,688 single limit < 50,000 mi_interest_dividends_capital_gains_deduction: 14_688 + +- name: A dependent's interest is not in the senior filer's AGI + # MCL 206.30(1)(p): interest, dividends and capital gains "included in + # adjusted gross income"; the dependent reports theirs on their own return. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 85 + employment_income: 100_000 + child: + age: 10 + taxable_interest_income: 5_000 + household: + state_code: MI + output: + mi_interest_dividends_capital_gains_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml index 02b73857bd3..827777f2f3a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/retirement_credit/ut_retirement_credit_max.yaml @@ -48,27 +48,30 @@ people: person1: age: 80 + is_tax_unit_head: true tax_exempt_interest_income: 500 - person2: + person2: age: 64 + is_tax_unit_dependent: true tax_exempt_interest_income: 500 person3: age: 80 + is_tax_unit_dependent: true tax_exempt_interest_income: 500 tax_units: tax_unit: - members: [person1, person2,person3] + members: [person1, person2, person3] ut_total_income: 30_000 filing_status: SINGLE households: household: - members: [person1, person2] + members: [person1, person2, person3] state_code: UT - output: - # person1 is the head, person3 the spouse and person2 a dependent, whose - # tax-exempt interest is on their own return. Phase-out reduction: - # 900 - (30_000 + 2 * 500 - 25_000) * 0.025 = 750. - ut_retirement_credit_max: 750 + output: + # Only the head is a claimant, and only the head's tax-exempt interest + # counts; the dependents' is on their own returns (Utah Code 59-10-1019). + # 450 - (30_000 + 500 - 25_000) * 0.025 = 312.5. + ut_retirement_credit_max: 312.5 - name: UT retirement credit max unit test 4 period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml index e3041498aed..6a46ddf9898 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml @@ -29,3 +29,23 @@ state_code: VA output: va_national_guard_subtraction: 5_500 + +- name: A dependent's Guard pay neither uses up the cap nor shares in it + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 30 + employment_income: 50_000 + military_service_income: 5_500 + student: + age: 20 + is_tax_unit_dependent: true + military_service_income: 5_500 + household: + state_code: VA + output: + va_national_guard_subtraction: 5_500 + va_national_guard_subtraction_person: [5_500, 0] + va_subtractions: 5_500 diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py index f8b7ea7cabc..403b3bc8f29 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py @@ -26,7 +26,9 @@ def formula(tax_unit, period, parameters): person = tax_unit.members # Senior citizens may subtract interest, dividends, and capital gains included in AGI. - income = add(tax_unit, period, p.income_types) + # A tax unit dependent's are on the dependent's own return, not in the + # filer's AGI, so only the head's and spouse's count. + income = tax_unit_non_dep_add(tax_unit, period, p.income_types) # The maximum amount of the deduction must be reduced by any deduction for # Military (including Michigan National Guard) retirement benefits # Public and private retirement and pension benefits diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py index 01f65679ad2..84ffbfd4d28 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py @@ -12,5 +12,10 @@ class va_national_guard_subtraction(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.va.tax.income.subtractions.national_guard_pay - national_guard_pay = add(tax_unit, period, ["military_service_income"]) + # The head's and spouse's pay: a tax unit dependent's is on the + # dependent's own return, as federal adjusted gross income leaves it + # out, so it neither uses up the cap nor shares in the subtraction. + national_guard_pay = tax_unit_non_dep_add( + tax_unit, period, ["military_service_income"] + ) return min_(national_guard_pay, p.cap) diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py index 94b5274c607..938929fc5f9 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py @@ -14,7 +14,12 @@ def formula(person, period, parameters): # The subtraction caps the household's military service income, so # allocate the capped tax-unit amount by each person's share. total = person.tax_unit("va_national_guard_subtraction", period) - military_service_income = person("military_service_income", period) + # Shares of the head's and spouse's pay, as va_national_guard_subtraction + # leaves out a tax unit dependent's. + head_or_spouse = ~person("is_tax_unit_dependent", period) + military_service_income = ( + person("military_service_income", period) * head_or_spouse + ) total_military_service_income = person.tax_unit.sum(military_service_income) share = where( total_military_service_income > 0, From d86a91189bea2129d9bb53b3bf82bade7ea6a09b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 23:26:15 -0400 Subject: [PATCH 4/9] Add Michigan and Missouri to the changelog fragment Co-Authored-By: Claude Opus 5.5 --- changelog.d/dependent-capital-income-state-lines.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/dependent-capital-income-state-lines.fixed.md b/changelog.d/dependent-capital-income-state-lines.fixed.md index cf87b71e125..26fd7e34ba8 100644 --- a/changelog.d/dependent-capital-income-state-lines.fixed.md +++ b/changelog.d/dependent-capital-income-state-lines.fixed.md @@ -1 +1 @@ -Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MN, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), and out of the U.S. government interest that most states subtract. +Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MO, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), and out of the U.S. government interest that most states subtract. From fadda4d89e442200325840e5136c2c1587776140 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 12 Oct 2026 05:35:17 -0400 Subject: [PATCH 5/9] Net Vermont eligible gains, use Missouri's line 7a, and fix three more lines From the second independent review: - Iowa's reportable Social Security worksheet adds one-half of the benefits (line 2) before the base amount, as both the 2021 and 2022 IA 1040 worksheets do. The first test's 4,000 came from the formula; the worksheet gives 14,000. - Missouri subtracts the filers' positive Form 1040 line 7a (filer_loss_limited_net_capital_gains), so capital gain distributions count, a loss nets against them, and a supplied tax unit amount less the dependents' gains is kept and goes to the head. - Vermont's percentage exclusion limits eligible gains to the filers' net gain (Schedule IN-153 line 15 starts from Part I line 4). The eligible gains input is now documented as part of long_term_capital_gains, and two cases that set it alone now set both. - California's young child tax credit measures the filers' own net loss, so a child's capital loss no longer takes the parent's credit. - Virginia caps National Guard pay per person (Va. Code 58.1-322.02(8), "received by any person"), so two qualifying spouses subtract up to $5,500 each. Co-Authored-By: Claude Opus 5.5 --- ...ent_capital_income_state_tax_invariants.py | 16 +++++--- .../income/credits/young_child/ca_yctc.yaml | 17 +++++++++ .../income/ia_reportable_social_security.yaml | 12 ++++-- .../va_national_guard_subtraction.yaml | 20 ++++++++++ ...vt_percentage_capital_gains_exclusion.yaml | 37 +++++++++++++++++++ .../tax/income/credits/young_child/ca_yctc.py | 10 +++-- .../income/ia_reportable_social_security.py | 9 ++++- .../va_national_guard_subtraction.py | 11 +----- .../va_national_guard_subtraction_person.py | 23 ++++-------- .../vt_percentage_capital_gains_exclusion.py | 10 +++++ ...ins_on_assets_eligible_for_vt_exclusion.py | 7 ++++ 11 files changed, 134 insertions(+), 38 deletions(-) diff --git a/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py index b97457d4eb9..8e4f3a72f6d 100644 --- a/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py +++ b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py @@ -30,9 +30,11 @@ 2. nd_qdiv_subtraction is 40% of the filers' qualified dividends, and nd_ltcg_subtraction is 40% of max(0, min(line 15, line 16)) of the filers' own Schedule D, with capital gain distributions on line 15. -3. vt_percentage_capital_gains_exclusion is 40% of the filers' eligible gains, - up to the cap, vt_additions is the filers' tax-exempt interest, and - us_govt_interest is the filers' U.S. government interest. +3. vt_percentage_capital_gains_exclusion is 40% of the filers' eligible gains + netted against their own Schedule D (no more than max(0, min(line 15, + line 16)), Schedule IN-153 line 15), up to the cap. vt_additions is the + filers' tax-exempt interest, and us_govt_interest is the filers' U.S. + government interest. The filers earn at least $80,000, so no earned income tax credit is at stake: Vermont's is a share of the federal credit, whose investment income @@ -210,15 +212,19 @@ def check(units): "nd_ltcg_subtraction": nd.ltcg_fraction * np.maximum(0, np.minimum(long_term, long_term + short_term)), # 3. Vermont, from the filers' own amounts. + # Schedule IN-153 line 15: eligible gains after netting, no more than + # the filers' net gain (the draws have no collectibles or section 1250 + # gain, so Part I line 4 is max(0, min(line 15, line 16))). "vt_percentage_capital_gains_exclusion": np.minimum( vt.rate - * np.array( + * np.minimum( [ filer_total( u, "long_term_capital_gains_on_assets_eligible_for_vt_exclusion" ) for u in units - ] + ], + np.maximum(0, np.minimum(long_term, long_term + short_term)), ), vt.cap, ), diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/young_child/ca_yctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/young_child/ca_yctc.yaml index 11ae453eb42..6f4c1f759d7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/young_child/ca_yctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/credits/young_child/ca_yctc.yaml @@ -406,3 +406,20 @@ # Step 8: earned income of zero or less; the 5,000 net loss and the # 10,000 wages are within the 2022 $32,490 limits, so the full $1,083. ca_yctc: 1_083 + +- name: A dependent's capital loss does not count against the filers' loss limit + # FTB 3514 line 23b measures the filers' own net loss; the child's loss is on + # the child's own return, so the parent keeps the credit. + period: 2025 + input: + people: + parent: + is_tax_unit_head: true + age: 35 + child: + age: 3 + short_term_capital_gains: -40_000 + household: + state_code: CA + output: + ca_yctc: 1_189 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml index 520300edea5..4ab612abc04 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_reportable_social_security.yaml @@ -23,7 +23,9 @@ output: adjusted_gross_income: 57_600 taxable_social_security: [11_760, 7_840] - ia_reportable_social_security: 4_000 # one-half of (income - deduction) + # Worksheet: line 2 (half of 40,000) + line 3 (38,000) + line 6 (2,000) + # = 60,000; less 32,000 = 28,000; half = 14,000; smaller of 20,000. + ia_reportable_social_security: 14_000 - name: IA reportable social security unit test 2 absolute_error_margin: 0.01 @@ -66,7 +68,9 @@ household: state_code: IA output: - ia_reportable_social_security: 0 + # Line 2 (15,000) + line 3 (20,000) = 35,000; less 25,000 = 10,000; + # half = 5,000, smaller than 15,000. + ia_reportable_social_security: 5_000 - name: Same household without the dependent's income period: 2022 @@ -82,4 +86,6 @@ household: state_code: IA output: - ia_reportable_social_security: 0 + # Line 2 (15,000) + line 3 (20,000) = 35,000; less 25,000 = 10,000; + # half = 5,000, smaller than 15,000. + ia_reportable_social_security: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml index 6a46ddf9898..8df3632dbcb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml @@ -49,3 +49,23 @@ va_national_guard_subtraction: 5_500 va_national_guard_subtraction_person: [5_500, 0] va_subtractions: 5_500 + +- name: Each spouse subtracts their own Guard pay up to the cap + # Va. Code 58.1-322.02(8): wages "received by any person", up to $5,500. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 30 + employment_income: 40_000 + military_service_income: 5_500 + spouse: + is_tax_unit_spouse: true + age: 30 + military_service_income: 5_500 + household: + state_code: VA + output: + va_national_guard_subtraction: 11_000 + va_national_guard_subtraction_person: [5_500, 5_500] diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml index c2677e7fcb0..cee2b6339cf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml @@ -23,6 +23,7 @@ period: 2022 input: state_code: VT + long_term_capital_gains: 1_000 # The eligible gains are part of these. long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 1_000 output: vt_percentage_capital_gains_exclusion: 400 @@ -31,6 +32,7 @@ period: 2022 input: state_code: VT + long_term_capital_gains: 900_000 # The eligible gains are part of these. long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 900_000 output: vt_percentage_capital_gains_exclusion: 350_000 @@ -83,3 +85,38 @@ vt_capital_gains_exclusion: 5_000 adjusted_gross_income: 160_000 vt_income_tax: 6_623.2 + +- name: Eligible gains are netted against a short-term loss first + # Schedule IN-153 Part II starts from Part I line 4, the smaller of + # Schedule D lines 15 and 16: min(10,000, 10,000 - 20,000) is a loss. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 80_000 + long_term_capital_gains: 10_000 + long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 10_000 + short_term_capital_gains: -20_000 + household: + state_code: VT + output: + vt_percentage_capital_gains_exclusion: 0 + +- name: Eligible gains are limited to the net gain + # Line 15 is no more than the 6,000 net gain: 40% x 6,000. + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 80_000 + long_term_capital_gains: 10_000 + long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 10_000 + short_term_capital_gains: -4_000 + household: + state_code: VT + output: + vt_percentage_capital_gains_exclusion: 2_400 diff --git a/policyengine_us/variables/gov/states/ca/tax/income/credits/young_child/ca_yctc.py b/policyengine_us/variables/gov/states/ca/tax/income/credits/young_child/ca_yctc.py index b25632b5374..aecde7bdb55 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/credits/young_child/ca_yctc.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/credits/young_child/ca_yctc.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.tax.federal_income.capital_gains.filer_schedule_d_lines import ( + filer_schedule_d_lines, +) class ca_yctc(Variable): @@ -45,9 +48,10 @@ def formula(tax_unit, period, parameters): # loss_limited_net_capital_gains, which already includes capital gain # distributions (they net against losses on Schedule D line 13), so the # unlimited amount adds them back to net_capital_gains as well. - limited_capital_gains = tax_unit("loss_limited_net_capital_gains", period) - distributions = tax_unit.sum(max_(0, person("non_sch_d_capital_gains", period))) - unlimited_capital_gains = tax_unit("net_capital_gains", period) + distributions + # Both are the filers' own: a tax unit dependent's gains and losses are + # on the dependent's own return, and federal AGI leaves them out. + limited_capital_gains = tax_unit("filer_loss_limited_net_capital_gains", period) + unlimited_capital_gains = filer_schedule_d_lines(tax_unit, period).line_16 total_income = ( tax_unit("ca_agi", period) - limited_capital_gains + unlimited_capital_gains ) diff --git a/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py b/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py index af7410b3698..bf2b478e48e 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py @@ -18,12 +18,17 @@ def formula(tax_unit, period, parameters): # return; a tax unit dependent's benefits and interest are on the # dependent's own return. benefits = tax_unit_non_dep_add(tax_unit, period, ["social_security"]) + p = parameters(period).gov.states.ia.tax.income + fraction = p.reportable_social_security.fraction + # Worksheet line 7: one-half of the benefits (line 2), other income + # (line 3, here AGI without taxable benefits, so already net of the + # line 8 adjustments) and tax-exempt interest (line 6). income = ( - tax_unit("adjusted_gross_income", period) + fraction * benefits + + tax_unit("adjusted_gross_income", period) - add(tax_unit, period, ["taxable_social_security"]) + tax_unit_non_dep_add(tax_unit, period, ["tax_exempt_interest_income"]) ) - p = parameters(period).gov.states.ia.tax.income filing_status = tax_unit("filing_status", period) deduction = p.reportable_social_security.deduction[filing_status] return p.reportable_social_security.fraction * min_( diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py index 84ffbfd4d28..7a920972907 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py @@ -10,12 +10,5 @@ class va_national_guard_subtraction(Variable): definition_period = YEAR reference = "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/" - def formula(tax_unit, period, parameters): - p = parameters(period).gov.states.va.tax.income.subtractions.national_guard_pay - # The head's and spouse's pay: a tax unit dependent's is on the - # dependent's own return, as federal adjusted gross income leaves it - # out, so it neither uses up the cap nor shares in the subtraction. - national_guard_pay = tax_unit_non_dep_add( - tax_unit, period, ["military_service_income"] - ) - return min_(national_guard_pay, p.cap) + # The sum of each filer's own capped pay (va_national_guard_subtraction_person). + adds = ["va_national_guard_subtraction_person"] diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py index 938929fc5f9..32f6a99c3b7 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py @@ -4,26 +4,17 @@ class va_national_guard_subtraction_person(Variable): value_type = float entity = Person - label = "Virginia National Guard pay subtraction allocated to each person" + label = "Virginia National Guard pay subtraction for each person" defined_for = StateCode.VA unit = USD definition_period = YEAR reference = "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/" def formula(person, period, parameters): - # The subtraction caps the household's military service income, so - # allocate the capped tax-unit amount by each person's share. - total = person.tax_unit("va_national_guard_subtraction", period) - # Shares of the head's and spouse's pay, as va_national_guard_subtraction - # leaves out a tax unit dependent's. + p = parameters(period).gov.states.va.tax.income.subtractions.national_guard_pay + # Va. Code 58.1-322.02(8) subtracts "the wages or salaries received by + # any person" for Guard service, up to the cap for each person. A tax + # unit dependent's pay is on the dependent's own return. head_or_spouse = ~person("is_tax_unit_dependent", period) - military_service_income = ( - person("military_service_income", period) * head_or_spouse - ) - total_military_service_income = person.tax_unit.sum(military_service_income) - share = where( - total_military_service_income > 0, - military_service_income / total_military_service_income, - 0, - ) - return total * share + pay = person("military_service_income", period) * head_or_spouse + return min_(pay, p.cap) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py index c6f222df2ac..1b8ee7c8e83 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py @@ -28,6 +28,16 @@ def formula(tax_unit, period, parameters): period, ["long_term_capital_gains_on_assets_eligible_for_vt_exclusion"], ) + # Schedule IN-153 line 15: eligible gains after netting, so no more + # than the filers' adjusted net capital gain less qualified dividends + # (Part I line 4, from Schedule D lines 15, 16, 18 and 19), as in + # vt_capital_gains_exclusion. + net_gain = max_( + tax_unit("adjusted_net_capital_gain", period) + - tax_unit_non_dep_add(tax_unit, period, ["qualified_dividend_income"]), + 0, + ) + eligible_gains = max_(0, min_(eligible_gains, net_gain)) p = parameters(period).gov.states.vt.tax.income.agi.exclusions.capital_gain percentage_exclusion = eligible_gains * p.percentage.rate return min_(percentage_exclusion, p.percentage.cap) diff --git a/policyengine_us/variables/household/income/person/capital_gains/long_term_capital_gains_on_assets_eligible_for_vt_exclusion.py b/policyengine_us/variables/household/income/person/capital_gains/long_term_capital_gains_on_assets_eligible_for_vt_exclusion.py index 9b6ae285b7e..65a07b3d998 100644 --- a/policyengine_us/variables/household/income/person/capital_gains/long_term_capital_gains_on_assets_eligible_for_vt_exclusion.py +++ b/policyengine_us/variables/household/income/person/capital_gains/long_term_capital_gains_on_assets_eligible_for_vt_exclusion.py @@ -5,6 +5,13 @@ class long_term_capital_gains_on_assets_eligible_for_vt_exclusion(Variable): value_type = float entity = Person label = "Long-term capital gains on assets eligible for Vermont's 40% exclusion" + documentation = ( + "The part of long_term_capital_gains from assets held more than three " + "years that Vermont's percentage exclusion covers (Schedule IN-153 " + "Part II), which excludes primary or nonprimary homes, depreciable " + "personal property other than farm property and standing timber, and " + "publicly traded stocks, bonds and other financial instruments." + ) unit = USD definition_period = YEAR uprating = "calibration.gov.irs.soi.long_term_capital_gains" From 6207d77cfaa38a925f8bc40236ce33bf39201468 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 12 Oct 2026 05:35:33 -0400 Subject: [PATCH 6/9] Describe the second-review fixes in the changelog fragment Co-Authored-By: Claude Opus 5.5 --- changelog.d/dependent-capital-income-state-lines.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/dependent-capital-income-state-lines.fixed.md b/changelog.d/dependent-capital-income-state-lines.fixed.md index 26fd7e34ba8..14b4a5f3ad4 100644 --- a/changelog.d/dependent-capital-income-state-lines.fixed.md +++ b/changelog.d/dependent-capital-income-state-lines.fixed.md @@ -1 +1 @@ -Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MO, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), and out of the U.S. government interest that most states subtract. +Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MO, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), out of California's young child tax credit loss test, and out of the U.S. government interest that most states subtract. Also add the half of benefits to Iowa's reportable Social Security worksheet, net Vermont's eligible gains before its percentage exclusion, floor North Dakota's long-term gain exclusion at zero, and cap Virginia's National Guard subtraction per person. From cffbaf1dc2450d44af6787c95b2e58da3b71d722 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 12 Oct 2026 07:55:37 -0400 Subject: [PATCH 7/9] Leave Missouri to #9899 in the changelog fragment Co-Authored-By: Claude Opus 5.5 --- changelog.d/dependent-capital-income-state-lines.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/dependent-capital-income-state-lines.fixed.md b/changelog.d/dependent-capital-income-state-lines.fixed.md index 14b4a5f3ad4..c917fc37f72 100644 --- a/changelog.d/dependent-capital-income-state-lines.fixed.md +++ b/changelog.d/dependent-capital-income-state-lines.fixed.md @@ -1 +1 @@ -Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MO, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), out of California's young child tax credit loss test, and out of the U.S. government interest that most states subtract. Also add the half of benefits to Iowa's reportable Social Security worksheet, net Vermont's eligible gains before its percentage exclusion, floor North Dakota's long-term gain exclusion at zero, and cap Virginia's National Guard subtraction per person. +Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), out of California's young child tax credit loss test, and out of the U.S. government interest that most states subtract. Also add the half of benefits to Iowa's reportable Social Security worksheet, net Vermont's eligible gains before its percentage exclusion, floor North Dakota's long-term gain exclusion at zero, and cap Virginia's National Guard subtraction per person. From 223bd88a09404105fa52adfc9522ddd6b5008111 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 12 Oct 2026 08:56:47 -0400 Subject: [PATCH 8/9] Apply IN-153's election share, keep supplied Guard amounts, fix GA, DE and MT lines From the third independent review: - Vermont's percentage exclusion nets eligible gains against Schedule IN-153 Part I line 4 built from the filers' Schedule D lines 15, 16, 18 and 19, before the Form 4952 election, then takes off the election's share on gains (lines 5-7 and 16: line 4g x 4e / (4b + 4e)). A 100,000 eligible gain with 100,000 of qualified dividends and a 100,000 election now gives 20,000, as the form does. - Virginia's Guard subtraction defaults to each filer's capped pay summed, and the per-person split shares the unit amount by capped pay, so a supplied unit amount (such as the 39-day limit) is allocated again. - New filer_loss_limited_net_capital_gains_person allocates the filers' loss-limited gains among the head and spouse only. Georgia's retirement exclusion and Delaware's pension exclusion use it, so a dependent's loss no longer reduces an eligible filer's gains (IT-511: "Income or losses should be allocated to the person who owns the item"). - Montana's separate-return AGI no longer adds a dependent's Montana amounts to the head's, as the joint branch already leaves them out; the two cases that expected that allocation now expect the head's own amounts. - The Montana Social Security worksheet cases use wages instead of a pension, whose Montana exemption the worksheet does not apply. Co-Authored-By: Claude Opus 5.5 --- .../exclusions/pension/income_sources.yaml | 4 +- ...loss_limited_net_capital_gains_person.yaml | 34 +++++++++++++++ .../de_pension_exclusion.yaml | 17 ++++++++ ...ent_exclusion_countable_capital_gains.yaml | 18 ++++++++ .../states/mt/tax/income/mt_agi_indiv.yaml | 42 +++++++++++++++++-- .../mt_taxable_social_security_joint.yaml | 4 +- .../va_national_guard_subtraction.yaml | 19 +++++++++ ...vt_percentage_capital_gains_exclusion.yaml | 19 +++++++++ ...r_loss_limited_net_capital_gains_person.py | 38 +++++++++++++++++ ...ement_exclusion_countable_capital_gains.py | 5 ++- .../states/mt/tax/income/base/mt_agi_indiv.py | 9 ++-- .../va_national_guard_subtraction.py | 11 ++++- .../va_national_guard_subtraction_person.py | 25 ++++++++--- .../vt_percentage_capital_gains_exclusion.py | 40 ++++++++++++++---- 14 files changed, 259 insertions(+), 26 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.yaml create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/filer_loss_limited_net_capital_gains_person.py diff --git a/policyengine_us/parameters/gov/states/de/tax/income/subtractions/exclusions/pension/income_sources.yaml b/policyengine_us/parameters/gov/states/de/tax/income/subtractions/exclusions/pension/income_sources.yaml index 68d01d3af09..22070b8a509 100644 --- a/policyengine_us/parameters/gov/states/de/tax/income/subtractions/exclusions/pension/income_sources.yaml +++ b/policyengine_us/parameters/gov/states/de/tax/income/subtractions/exclusions/pension/income_sources.yaml @@ -2,7 +2,9 @@ description: Delaware counts these income sources for the pension exclusion. values: 2021-01-01: - dividend_income - - loss_limited_net_capital_gains_person + # The filer's own share; a tax unit dependent's losses are on the + # dependent's own return. + - filer_loss_limited_net_capital_gains_person - taxable_interest_income - rental_income - taxable_pension_income diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/loss_limited_net_capital_gains_person.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/loss_limited_net_capital_gains_person.yaml index 92738cf8ca9..7430f3970e7 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/loss_limited_net_capital_gains_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/loss_limited_net_capital_gains_person.yaml @@ -70,3 +70,37 @@ # Total raw: 12000 (positive, so no limit applied) # Return person's raw amounts unchanged loss_limited_net_capital_gains_person: [-3000, 15000] + +- name: The filer-only allocation leaves a dependent's loss out + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 45 + long_term_capital_gains: 5_000 + child: + age: 15 + short_term_capital_gains: -10_000 + output: + # The pooled allocation shares the 3,000 limited loss; the filer-only one + # keeps the head's 5,000 gain. + filer_loss_limited_net_capital_gains_person: [5_000, 0] + +- name: The filer-only allocation limits a filers' net loss + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 45 + long_term_capital_gains: -8_000 + spouse: + is_tax_unit_spouse: true + age: 45 + long_term_capital_gains: -2_000 + child: + age: 15 + long_term_capital_gains: 50_000 + output: + filer_loss_limited_net_capital_gains_person: [-2_400, -600, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/pension_exclusion/de_pension_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/pension_exclusion/de_pension_exclusion.yaml index 36f594749d8..55650ed50e0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/pension_exclusion/de_pension_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/subtractions/pension_exclusion/de_pension_exclusion.yaml @@ -133,3 +133,20 @@ output: de_pension_exclusion: 10_000 + +- name: A dependent's capital loss does not reduce the filer's eligible retirement income + period: 2024 + input: + people: + head: + is_tax_unit_head: true + age: 63 + taxable_pension_income: 10_000 + long_term_capital_gains: 5_000 + child: + age: 15 + short_term_capital_gains: -10_000 + household: + state_code: DE + output: + de_pension_exclusion_income: [15_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.yaml new file mode 100644 index 00000000000..7e139b378f7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.yaml @@ -0,0 +1,18 @@ +- name: A dependent's capital loss does not reduce an eligible filer's gains + # IT-511 instructions: "Income or losses should be allocated to the person + # who owns the item"; the child's loss is on the child's own return. + period: 2024 + input: + people: + head: + is_tax_unit_head: true + age: 63 + employment_income: 20_000 + long_term_capital_gains: 5_000 + child: + age: 15 + short_term_capital_gains: -10_000 + household: + state_code: GA + output: + ga_retirement_exclusion_countable_capital_gains: [5_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_indiv.yaml index 377b0d7d460..66cf40791a6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_indiv.yaml @@ -1,4 +1,6 @@ -- name: The dependent incoome is allocated to the head +- name: A dependent's Montana amounts stay off the head's separate AGI + # The dependent files their own return; only the head's own amounts + # count: 10,000 + 500 - 3,000. period: 2021 input: people: @@ -31,7 +33,7 @@ members: [person1, person2, person3] state_code: MT output: - mt_agi_indiv: [9_100, 1_600, 0] + mt_agi_indiv: [7_500, 1_600, 0] - name: Test if mt_taxable_social_security works in formula period: 2021 @@ -66,4 +68,38 @@ members: [person1, person2, person3] state_code: MT output: - mt_agi_indiv: [9_200, 1_600, 0] + # 7,500 plus the head's own 100 Montana-taxable benefit difference. + mt_agi_indiv: [7_600, 1_600, 0] + +- name: A dependent's benefits and interest stay off the filers' separate AGIs + # Form 2 runs the Social Security worksheet on the taxpayer's and spouse's + # income; the child's are on the child's own return. + period: 2021 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 50_000 + spouse: + is_tax_unit_spouse: true + age: 40 + employment_income: 50_000 + child: + age: 10 + social_security_survivors: 20_000 + tax_exempt_interest_income: 10_000 + marital_units: + parents: + members: [head, spouse] + child_unit: + members: [child] + tax_units: + tax_unit: + members: [head, spouse, child] + households: + household: + members: [head, spouse, child] + state_code: MT + output: + mt_agi_indiv: [50_000, 50_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml index 3d457ec24e8..6ec8f8c79c2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security_joint.yaml @@ -6,7 +6,7 @@ is_tax_unit_head: true age: 70 social_security_retirement: 30_000 - taxable_pension_income: 20_000 + employment_income: 20_000 child: age: 10 tax_exempt_interest_income: 50_000 @@ -25,7 +25,7 @@ is_tax_unit_head: true age: 70 social_security_retirement: 30_000 - taxable_pension_income: 20_000 + employment_income: 20_000 child: age: 10 household: diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml index 8df3632dbcb..70331566928 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.yaml @@ -69,3 +69,22 @@ output: va_national_guard_subtraction: 11_000 va_national_guard_subtraction_person: [5_500, 5_500] + +- name: A supplied tax unit amount is allocated by capped pay + period: 2025 + input: + people: + head: + is_tax_unit_head: true + age: 30 + military_service_income: 10_000 + tax_units: + tax_unit: + members: [head] + va_national_guard_subtraction: 3_000 + households: + household: + members: [head] + state_code: VA + output: + va_national_guard_subtraction_person: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml index cee2b6339cf..3f3c0f6f65a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_percentage_capital_gains_exclusion.yaml @@ -120,3 +120,22 @@ state_code: VT output: vt_percentage_capital_gains_exclusion: 2_400 + +- name: The Form 4952 election share on gains comes off the eligible gains + # Schedule IN-153 line 7 = 100,000 x 100,000 / (100,000 + 100,000) = 50,000; + # line 17 = 100,000 - 50,000; 40% = 20,000. + period: 2024 + input: + people: + head: + is_tax_unit_head: true + age: 50 + employment_income: 80_000 + long_term_capital_gains: 100_000 + long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 100_000 + qualified_dividend_income: 100_000 + investment_income_elected_form_4952: 100_000 + household: + state_code: VT + output: + vt_percentage_capital_gains_exclusion: 20_000 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/filer_loss_limited_net_capital_gains_person.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/filer_loss_limited_net_capital_gains_person.py new file mode 100644 index 00000000000..9f119b6f6f3 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/filer_loss_limited_net_capital_gains_person.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * + + +class filer_loss_limited_net_capital_gains_person(Variable): + value_type = float + entity = Person + label = "Filer's loss-limited capital gains (person)" + unit = USD + definition_period = YEAR + documentation = ( + "Each head's or spouse's share of filer_loss_limited_net_capital_gains: " + "the person's own capital gains and losses plus capital gain " + "distributions, with a net loss of the head and spouse limited and " + "allocated in proportion. A tax unit dependent's gains and losses are " + "on the dependent's own return, so the dependent has none here and " + "takes no part in the loss limit, unlike " + "loss_limited_net_capital_gains_person." + ) + reference = "https://www.irs.gov/pub/irs-prior/f1040sd--2025.pdf#page=2" + + def formula(person, period, parameters): + head_or_spouse = ~person("is_tax_unit_dependent", period) + own_gains = head_or_spouse * ( + person("capital_gains", period) + + max_(0, person("non_sch_d_capital_gains", period)) + ) + tax_unit = person.tax_unit + filer_gains = tax_unit.sum(own_gains) + filer_limited = tax_unit("filer_loss_limited_net_capital_gains", period) + proportion = np.divide( + own_gains, + filer_gains, + out=np.zeros_like(own_gains), + where=filer_gains != 0, + ) + # With a net gain each filer keeps their own amount; with a net loss + # the limited loss is shared in proportion to each filer's own. + return where(filer_gains < 0, filer_limited * proportion, own_gains) diff --git a/policyengine_us/variables/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.py b/policyengine_us/variables/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.py index ac17b2bbae7..f4c8115629d 100644 --- a/policyengine_us/variables/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.py +++ b/policyengine_us/variables/gov/states/ga/tax/income/subtractions/retirement/ga_retirement_exclusion_countable_capital_gains.py @@ -14,4 +14,7 @@ class ga_retirement_exclusion_countable_capital_gains(Variable): defined_for = StateCode.GA def formula(person, period, parameters): - return person("loss_limited_net_capital_gains_person", period) + # "Income or losses should be allocated to the person who owns the + # item" (IT-511 instructions); a tax unit dependent's losses are on the + # dependent's own return and do not reduce a filer's gains. + return person("filer_loss_limited_net_capital_gains_person", period) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_indiv.py index 943c22aca8c..5ed819b68c0 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_indiv.py @@ -28,9 +28,8 @@ def formula(person, period, parameters): # 2024 and after: no longer apply the social security adjustment tax_unit_mt_agi = reduced_agi - # in all years - # allocate any dependent net_income to tax unit head + # A tax unit dependent's income, and the Montana-taxable benefits the + # worksheet finds from it, are on the dependent's own return (Form 2 + # uses the taxpayer's and spouse's income), as in mt_agi_joint. is_dependent = person("is_tax_unit_dependent", period) - sum_dep_net_income = person.tax_unit.sum(is_dependent * tax_unit_mt_agi) - is_head = person("is_tax_unit_head", period) - return ~is_dependent * tax_unit_mt_agi + is_head * sum_dep_net_income + return ~is_dependent * tax_unit_mt_agi diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py index 7a920972907..331cc9d0455 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py @@ -10,5 +10,12 @@ class va_national_guard_subtraction(Variable): definition_period = YEAR reference = "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/" - # The sum of each filer's own capped pay (va_national_guard_subtraction_person). - adds = ["va_national_guard_subtraction_person"] + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.va.tax.income.subtractions.national_guard_pay + # Va. Code 58.1-322.02(8) subtracts "the wages or salaries received by + # any person" for Guard service, up to the cap for each person. A tax + # unit dependent's pay is on the dependent's own return. + person = tax_unit.members + head_or_spouse = ~person("is_tax_unit_dependent", period) + pay = person("military_service_income", period) * head_or_spouse + return tax_unit.sum(min_(pay, p.cap)) diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py index 32f6a99c3b7..3df6b4dd789 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction_person.py @@ -12,9 +12,24 @@ class va_national_guard_subtraction_person(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.va.tax.income.subtractions.national_guard_pay - # Va. Code 58.1-322.02(8) subtracts "the wages or salaries received by - # any person" for Guard service, up to the cap for each person. A tax - # unit dependent's pay is on the dependent's own return. + # Each filer's own pay up to the per-person cap, as in + # va_national_guard_subtraction, sets the shares of the unit amount, + # so a supplied unit amount is allocated too. A tax unit dependent's + # pay is on the dependent's own return; with no filer pay, the head + # takes a supplied amount. head_or_spouse = ~person("is_tax_unit_dependent", period) - pay = person("military_service_income", period) * head_or_spouse - return min_(pay, p.cap) + capped_pay = min_( + person("military_service_income", period) * head_or_spouse, p.cap + ) + total_capped_pay = person.tax_unit.sum(capped_pay) + share = where( + total_capped_pay > 0, + np.divide( + capped_pay, + total_capped_pay, + out=np.zeros_like(capped_pay), + where=total_capped_pay > 0, + ), + person("is_tax_unit_head", period), + ) + return person.tax_unit("va_national_guard_subtraction", period) * share diff --git a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py index 1b8ee7c8e83..7465034903e 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/adjusted_gross_income/subtractions/vt_capital_gain_exclusion/vt_percentage_capital_gains_exclusion.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.irs.tax.federal_income.capital_gains.filer_schedule_d_lines import ( + filer_schedule_d_lines, +) class vt_percentage_capital_gains_exclusion(Variable): @@ -29,15 +32,38 @@ def formula(tax_unit, period, parameters): ["long_term_capital_gains_on_assets_eligible_for_vt_exclusion"], ) # Schedule IN-153 line 15: eligible gains after netting, so no more - # than the filers' adjusted net capital gain less qualified dividends - # (Part I line 4, from Schedule D lines 15, 16, 18 and 19), as in - # vt_capital_gains_exclusion. - net_gain = max_( - tax_unit("adjusted_net_capital_gain", period) - - tax_unit_non_dep_add(tax_unit, period, ["qualified_dividend_income"]), - 0, + # than Part I line 4, the smaller of the filers' Schedule D lines 15 + # and 16 less lines 18 and 19. Line 4 comes before the Form 4952 + # election, which IN-153 allocates separately (lines 5-7 and 16). + lines = filer_schedule_d_lines(tax_unit, period) + net_gain = min_(lines.line_15, lines.line_16) - ( + tax_unit("capital_gains_28_percent_rate_gain", period) + + tax_unit("schedule_d_unrecaptured_section_1250_gain", period) ) eligible_gains = max_(0, min_(eligible_gains, net_gain)) + # Lines 5-7 and 16: the share of the filers' Form 4952 election that + # falls on net capital gain, line 4g x line 4e / (line 4b + line 4e), + # taking line 4e as the filers' net capital gain and line 4b as their + # qualified dividends. Line 16 uses this share when no investment + # interest is for ineligible assets. + election = max_( + 0, + tax_unit_non_dep_add( + tax_unit, period, ["investment_income_elected_form_4952"] + ), + ) + net_capital_gain = max_(0, min_(lines.line_15, lines.line_16)) + qualified_dividends = max_( + 0, tax_unit_non_dep_add(tax_unit, period, ["qualified_dividend_income"]) + ) + denominator = qualified_dividends + net_capital_gain + election_on_gain = np.divide( + election * net_capital_gain, + denominator, + out=np.zeros_like(denominator), + where=denominator > 0, + ) + eligible_gains = max_(0, eligible_gains - election_on_gain) p = parameters(period).gov.states.vt.tax.income.agi.exclusions.capital_gain percentage_exclusion = eligible_gains * p.percentage.rate return min_(percentage_exclusion, p.percentage.cap) From fa6457644cdf5be12b0cda4e5429e151656d4682 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 12 Oct 2026 08:57:19 -0400 Subject: [PATCH 9/9] Add Georgia and Delaware to the changelog fragment Co-Authored-By: Claude Opus 5.5 --- changelog.d/dependent-capital-income-state-lines.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/dependent-capital-income-state-lines.fixed.md b/changelog.d/dependent-capital-income-state-lines.fixed.md index c917fc37f72..8d923d3cb1b 100644 --- a/changelog.d/dependent-capital-income-state-lines.fixed.md +++ b/changelog.d/dependent-capital-income-state-lines.fixed.md @@ -1 +1 @@ -Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), out of California's young child tax credit loss test, and out of the U.S. government interest that most states subtract. Also add the half of benefits to Iowa's reportable Social Security worksheet, net Vermont's eligible gains before its percentage exclusion, floor North Dakota's long-term gain exclusion at zero, and cap Virginia's National Guard subtraction per person. +Leave tax unit dependents' interest, dividends and capital gains out of the state lines that adjust the filers' federal adjusted gross income or taxable income (AZ, DC, DE, IA, IL, MA, MD, ME, MI, MN, MT, ND, NH, OH, PA, SC, UT, VA, VT, WA, WI and WV), out of California's young child tax credit loss test and Georgia's and Delaware's retirement exclusions, and out of the U.S. government interest that most states subtract. Also add the half of benefits to Iowa's reportable Social Security worksheet, net Vermont's eligible gains before its percentage exclusion, floor North Dakota's long-term gain exclusion at zero, and cap Virginia's National Guard subtraction per person.