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..7388736b5ca --- /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, 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, and cap Virginia's National Guard subtraction per person. Net Vermont's eligible gains, less the Form 4952 election's share, before its percentage exclusion. Floor North Dakota's long-term gain exclusion at zero, and count capital gain distributions in it. 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 74344c13e5e..b7ac6094352 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/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..8e4f3a72f6d --- /dev/null +++ b/policyengine_us/tests/core/test_dependent_capital_income_state_tax_invariants.py @@ -0,0 +1,304 @@ +"""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 + 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 +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. +""" + +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): + """One person's capital income. U.S. government interest is part of + taxable interest, and Vermont-eligible gains are part of long-term + gains.""" + 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": 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))), + "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"])) + 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()), + } + for i in range(n_filers) + ] + dependents = [ + { + "age": draw(st.integers(1, 17)), + **draw(capital_income()), + } + 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. + # 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.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, + ), + "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/core/test_state_tax_head_spouse_swap_invariance.py b/policyengine_us/tests/core/test_state_tax_head_spouse_swap_invariance.py index 6a2c81e71e9..56b22da0cbd 100644 --- a/policyengine_us/tests/core/test_state_tax_head_spouse_swap_invariance.py +++ b/policyengine_us/tests/core/test_state_tax_head_spouse_swap_invariance.py @@ -9,10 +9,12 @@ other input kept, must leave each state's income tax unchanged. On main these depended on the label: -- Arkansas, Delaware, Iowa, Mississippi and Montana put the dependents' income - the model counts on the head's column before taxing each column separately. - It now goes on the column of the spouse with the greater income of their - own, and an exact tie splits it (move_dependent_amounts_to_filer). +- Arkansas, Delaware, Iowa and Mississippi tax each spouse's column + separately. The model used to put the dependents' income it counts on the + head's column. It now puts it on the column of the spouse with more income + of their own, and splits it on an exact tie + (move_dependent_amounts_to_filer). Montana leaves the dependents' amounts + off both columns, since the dependent reports them on their own return. - Delaware's combined separate return routed the EITC and the dependent care credit by label on equal incomes and split the dependent personal credits with a heuristic. They are now chosen together to minimise tax. 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/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/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/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/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/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 935d7bd10f1..684dd506df6 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,6 +133,24 @@ 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] + - name: DE under-60 survivor benefits treated as pension (capped at 2,000) period: 2024 input: 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/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..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 @@ -49,3 +51,41 @@ 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: + # 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 + 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: + # 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/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/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 b58c60f5e55..288e1a0a8e5 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 @@ -119,6 +119,24 @@ 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 + - name: 2022 single filer with interest and dividends deduction reduced by military retirement and survivor pay period: 2022 input: 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 e90fead9244..33ce5bbdc81 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 @@ -154,6 +154,40 @@ # 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 + - name: MN social security subtraction 2020 worksheet step 8 leaves out Form 1040 line 10b absolute_error_margin: 0.01 period: 2020 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..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 @@ -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.725 + mt_capital_gains_tax_applicable_threshold_joint: 0 + mt_income_tax: 1_847.725 + +- 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.725 + mt_capital_gains_tax_applicable_threshold_joint: 0 + mt_income_tax: 1_847.725 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 6f28a48eb44..e0ff668fac6 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 @@ -14,6 +14,23 @@ 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] + - name: A net capital loss produces no credit rather than a negative one period: 2022 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml index 9104def4ba4..66e6ad5ce33 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/exemptions/mt_dependent_exemptions_person.yaml @@ -521,8 +521,10 @@ # (the net gain is only 1,000), so not a Montana dependent. mt_dependent_exemptions_person: [0, 0] # With the exemption on the net 1,000 the tax was 1,711.65; - # 1,711.65 + 182.92 = 1,894.57. - mt_income_tax: 1_894.57 + # 1,711.65 + 182.92 = 1,894.57. The dependent's net 1,000 gain is on the + # dependent's own return, so it no longer gives the filers the 2% capital + # gain credit: 1,894.57 + 20 = 1,914.57. + mt_income_tax: 1_914.57 - name: Adult dependent whose short-term gain exceeds the exemption amount is not eligible despite a long-term loss period: 2022 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_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_agi_indiv.yaml index 157f8aa56a0..8538f597dd2 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 income goes to the spouse with the greater income +- 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,9 +68,43 @@ 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: The dependent income goes to the spouse with the greater income, here not the head +- 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] + +- name: A dependent's amounts stay off both spouses' separate AGIs when the higher earner is not the head period: 2021 input: people: @@ -107,10 +143,11 @@ members: [person1, person2, person3] state_code: MT output: - # The first test's couple with the head and spouse labels exchanged. - mt_agi_indiv: [9_100, 1_600, 0] + # The head and spouse labels exchanged: the spouse's own 10,000 + 500 - + # 3,000 and the head's own 2,000 + 300 - 700, whichever is labelled head. + mt_agi_indiv: [7_500, 1_600, 0] -- name: Spouses with equal income split the dependent income equally +- name: Spouses with equal income keep their own amounts, and the dependent's stay off period: 2021 input: people: @@ -149,4 +186,5 @@ members: [person1, person2, person3] state_code: MT output: - mt_agi_indiv: [5_500, 5_500, 0] + # The dependent's 1,000 of additions is on the dependent's own return. + mt_agi_indiv: [5_000, 5_000, 0] 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 index f1e3425ed94..aef84f3a807 100644 --- 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 @@ -1,3 +1,24 @@ +- 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: + # Federal AGI 60,200 includes only the head's interest, and Montana + # subtracts only the head's 200: 60,000. + mt_agi_joint: 60_000 + - name: A dependent's Montana subtraction stays off the filer's joint Montana AGI period: 2022 input: 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..6ec8f8c79c2 --- /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 + employment_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 + employment_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 index 42abd0b2256..a531a8af085 100644 --- 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 @@ -1,3 +1,22 @@ +- 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] + - name: Ohio deducts the disability benefits included in federal AGI. period: 2024 input: 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 index e1ab38e4072..00a2adeb70f 100644 --- 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 @@ -1,3 +1,38 @@ +- 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 + - name: Case 1, military pension benefits are excluded from PA tax forgiveness eligibility income per PA-40 SP. period: 2025 input: 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..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,24 +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: - ut_retirement_credit_max: 737.5 # phase-out reduction partially eliminates credit: 900-(30000+3*500-25000)/0.025 = 737.5 + 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 @@ -115,7 +121,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 @@ -138,3 +146,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..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 @@ -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.475 + +- 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.475 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/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..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 @@ -29,3 +29,62 @@ 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 + +- 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] + +- 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/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_capital_gains_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_capital_gains_exclusion.yaml index 403e00d94bc..034aa663a69 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_capital_gains_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/agi/vt_capital_gains_exclusion.yaml @@ -39,6 +39,9 @@ state_code: VT adjusted_net_capital_gain: 12_600 taxable_income: 12_700 + # The VT-eligible gains are part of the filer's long-term gains + # (Schedule IN-153 line 15 is no more than net capital gain). + long_term_capital_gains: 12_600 long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 12_600 output: # Flat: min(12600, 5000) = 5000 @@ -54,6 +57,9 @@ state_code: VT adjusted_net_capital_gain: 12_600 taxable_income: 12_500 + # The VT-eligible gains are part of the filer's long-term gains + # (Schedule IN-153 line 15 is no more than net capital gain). + long_term_capital_gains: 12_600 long_term_capital_gains_on_assets_eligible_for_vt_exclusion: 12_600 output: # Flat: 5000, Percentage: 5040, Chosen: 5040 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..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 @@ -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 @@ -43,3 +45,97 @@ 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 + +- 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 + +- 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/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 cc11058e5b3..11a3cc9ffe5 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 @@ -94,6 +94,23 @@ # 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 + - name: 2024 sub-boxes and total resolved without dropping dollars in WA period: 2024 absolute_error_margin: 0.01 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/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/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/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/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/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/ia/tax/income/ia_reportable_social_security.py b/policyengine_us/variables/gov/states/ia/tax/income/ia_reportable_social_security.py index 8964b2b87f6..10c9798f848 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,13 +14,21 @@ 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 takes 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"]) + 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"]) - + 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) deduction = p.reportable_social_security.deduction[filing_status] return p.reportable_social_security.fraction * min_( 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 211d6102714..28d3d93827a 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,7 +21,12 @@ 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: long-term gains and losses, # capital gain distributions without a federal Schedule D (line 6) @@ -32,7 +37,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 8ac9369f43f..be1c0b9c713 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,8 +11,13 @@ 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: long-term gains and losses, # capital gain distributions without a federal Schedule D (line 6) @@ -35,7 +40,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 3297fdf1f91..785782d4866 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 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_long_term_capital_gains.py b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_long_term_capital_gains.py index adeb46c1a39..b893df1613e 100644 --- a/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_long_term_capital_gains.py +++ b/policyengine_us/variables/gov/states/ma/tax/income/gross_income/ma_long_term_capital_gains.py @@ -11,7 +11,8 @@ class ma_long_term_capital_gains(Variable): "federal Schedule D (line 6) and Form 4797, Part II gains and losses " "(line 7). PolicyEngine has a single Form 4797 input, so it also " "counts any gain or loss on business property held one year or less, " - "which belongs on Schedule B, lines 12 and 17." + "which belongs on Schedule B, lines 12 and 17. Only the head's and " + "spouse's amounts count." ) unit = USD definition_period = YEAR @@ -20,8 +21,12 @@ class ma_long_term_capital_gains(Variable): "https://taxsim.nber.org/historical_state_tax_forms/MA/2023/dor-2023-inc-form1-instructions.pdf#page=27", ) defined_for = StateCode.MA - adds = [ - "long_term_capital_gains", - "non_sch_d_capital_gains", - "other_net_gain", - ] + + def formula(tax_unit, period, parameters): + # A dependent's gains are on the dependent's own return, as + # irs_gross_income leaves them out of the filers' federal AGI. + return tax_unit_non_dep_add( + tax_unit, + period, + ["long_term_capital_gains", "non_sch_d_capital_gains", "other_net_gain"], + ) 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 ae5d11d83bf..e6e3b7ecbfe 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 @@ -17,7 +17,7 @@ def formula(tax_unit, period, parameters): # Long-term amounts on Massachusetts Schedule D ltcg = tax_unit("ma_long_term_capital_gains", period) # 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/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 298e32bf081..058b798d96b 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/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 3772b3c0d8e..656ba822151 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 @@ -44,12 +44,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 takes 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"] + ) # Step 8 counts the Schedule 1 adjustments, so not the 2020 # charitable deduction for non-itemizers (Form 1040 line 10b). charitable_ald = tax_unit("charitable_contributions_ald", 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 9ca64d63be9..36d5abc91bf 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 @@ -10,10 +10,7 @@ class mt_agi_indiv(Variable): reference = ( # Filing separately: each column is its own return "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=12", - # Borrowed for dependents' income: parents filing separately report a - # child's income on the return of the parent with the greater - # taxable income - "https://www.law.cornell.edu/uscode/text/26/1#g_5_B", + # Form 8814 election (not modeled): otherwise a dependent reports their income on their own return. "https://www.irs.gov/instructions/i8814", ) defined_for = "mt_married_filing_separately_on_same_return_eligible" @@ -46,8 +43,8 @@ def formula(person, period, parameters): # 2024 and after: no longer apply the social security adjustment tax_unit_mt_agi = reduced_agi - # The dependents' income the model counts here goes on the return of - # the spouse with the greater income (a modelling convention; see the - # helper). The instructions' Line 5 exclusion of a child's income - # reported on federal Form 8814 is not modelled. - return move_dependent_amounts_to_filer(person, period, tax_unit_mt_agi) + # 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) + return ~is_dependent * tax_unit_mt_agi 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 54029434dd0..e871850035c 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 takes 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,15 +37,16 @@ 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 Schedule line 35 includes the partial pension, annuity, - # and IRA income exemption (line 34). - subtractions = add(tax_unit, period, ["mt_subtractions"]) + tax_unit( - "mt_pension_exemption_joint", period - ) + # and IRA income exemption (line 34). A tax unit dependent's + # subtractions are on the dependent's own return. + subtractions = tax_unit_non_dep_add( + tax_unit, period, ["mt_subtractions"] + ) + tax_unit("mt_pension_exemption_joint", period) 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 03982ead3d3..3d995c17fa7 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 @@ -16,4 +16,7 @@ def formula(person, period, parameters): # The credit is 2% of net capital gains (Form 2 instructions, Line 1), # so a net capital loss produces no credit rather than a negative one. net_capital_gain = max_(person("capital_gains", period), 0) - return p.percentage * net_capital_gain + # 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) + 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 89f5a5f0578..58904739528 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,10 @@ 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 def formula(tax_unit, period, parameters): 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 a4674289814..5ddc631b24f 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 @@ -35,7 +35,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/subtractions/va_national_guard_subtraction.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_national_guard_subtraction.py index 01f65679ad2..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 @@ -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"]) - return min_(national_guard_pay, p.cap) + # 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 94b5274c607..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 @@ -4,21 +4,32 @@ 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) - military_service_income = person("military_service_income", period) - total_military_service_income = person.tax_unit.sum(military_service_income) + p = parameters(period).gov.states.va.tax.income.subtractions.national_guard_pay + # 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) + 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_military_service_income > 0, - military_service_income / total_military_service_income, - 0, + 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 total * share + return person.tax_unit("va_national_guard_subtraction", period) * share 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 07483118ef2..b70f664222d 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,8 +11,11 @@ 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) + tax_unit( - "va_other_subtractions", period - ) + # 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 + ) + tax_unit("va_other_subtractions", period) # 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 df7c93323b3..cdb9eba3b85 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 @@ -37,5 +37,8 @@ def formula(person, period, parameters): ) other_sub = person.tax_unit("va_other_subtractions", period) * prorate total_subtractions = person_subtractions + other_sub + # 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..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): @@ -19,11 +22,48 @@ 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"], ) + # Schedule IN-153 line 15: eligible gains after netting, so no more + # 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) 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 1a3893d6db3..613f4d53742 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,11 @@ class vt_additions(Variable): "https://legislature.vermont.gov/statutes/section/32/151/05811", ) - adds = ["tax_exempt_interest_income", "vt_other_additions"] + 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", "vt_other_additions"] + ) 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 745af1650d8..a31918452e1 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(tax_unit, period, ["total_charitable_donations"]) 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 13efc404621..e9462d8000c 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, period, ["total_charitable_donations"] 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/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" 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"])