From 1f0cd1b0f918fc3e51441ae08711e65a9ca8d181 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 11:03:59 -0400 Subject: [PATCH 1/9] Make head_is_dependent_elsewhere consumers independent of the head label The federal standard deduction and 17 state formulas read only whether the tax-unit head is claimed elsewhere, so a joint return where one spouse can be claimed was taxed differently depending on which spouse was labelled head. Each formula now applies its jurisdiction's rule for that case (either spouse, single filers only, per spouse, or only when every filer is claimed), using two new label-independent helpers. Adds swap-pair YAML cases and a Hypothesis property test. Co-Authored-By: Claude Opus 5.5 --- .../head-dependent-elsewhere-swap.fixed.md | 1 + .../itemized/threshold/dependent.yaml | 2 +- .../exemptions/dependent_on_other_return.yaml | 17 +- .../standard/dependent_elsewhere.yaml | 4 +- ...t_elsewhere_head_spouse_swap_invariance.py | 355 ++++++++++++++++++ .../standard/basic_standard_deduction.yaml | 56 +++ .../hi/tax/income/credits/act_163_sunset.yaml | 4 - .../food_excise/hi_food_excise_credit.yaml | 89 ++++- .../tax/income/credits/hi_act_115_rebate.yaml | 90 +++++ ...w_income_household_renters_tax_credit.yaml | 64 ++++ ...low_income_household_renters_eligible.yaml | 91 ++++- .../tax/income/deductions/hi_deductions.yaml | 134 ++++++- .../hi_salt_deduction_integration.yaml | 6 +- ...me_sales_tax_fairness_credit_eligible.yaml | 85 ++++- .../tax/income/exemptions/mi_exemptions.yaml | 148 +++++++- .../income/credits/wftc/mo_wftc_eligible.yaml | 56 +++ .../income/credits/cdcc/nm_cdcc_eligible.yaml | 105 +++++- .../credits/nm_medical_expense_credit.yaml | 92 ++++- ...ction_for_certain_dependents_eligible.yaml | 86 ++++- .../2021_income/nm_2021_income_rebate.yaml | 90 ++++- .../nm_additional_2021_income_rebate.yaml | 89 ++++- .../nm_supplemental_2021_income_rebate.yaml | 89 ++++- .../deductions/ny_standard_deduction.yaml | 94 +++++ .../standard/or_standard_deduction.yaml | 60 ++- .../va_low_income_tax_credit_eligible.yaml | 56 ++- ...head_or_spouse_is_dependent_elsewhere.yaml | 114 ++++++ ..._spouse_count_not_dependent_elsewhere.yaml | 114 ++++++ .../basic_standard_deduction.py | 14 +- .../food_excise/hi_food_excise_credit.py | 25 +- .../hi_food_excise_exemption_amount.py | 18 +- .../tax/income/credits/hi_act_115_rebate.py | 16 +- ...low_income_household_renters_tax_credit.py | 30 +- ...r_low_income_household_renters_eligible.py | 16 +- .../hi/tax/income/deductions/hi_deductions.py | 27 +- .../me_sales_tax_fairness_credit_eligible.py | 16 +- .../mi/tax/income/exemptions/mi_exemptions.py | 29 +- .../mo/tax/income/credits/mo_wftc_eligible.py | 18 +- .../income/credits/cdcc/nm_cdcc_eligible.py | 20 +- .../credits/nm_medical_expense_credit.py | 16 +- ...duction_for_certain_dependents_eligible.py | 16 +- .../2021_rebate/nm_2021_income_rebate.py | 17 +- .../nm_additional_2021_income_rebate.py | 17 +- .../nm_supplemental_2021_income_rebate.py | 17 +- .../deductions/ny_standard_deduction.py | 20 +- .../deductions/or_standard_deduction.py | 11 +- .../va_low_income_tax_credit_eligible.py | 17 +- .../head_or_spouse_is_dependent_elsewhere.py | 21 ++ ...ad_spouse_count_not_dependent_elsewhere.py | 20 + 48 files changed, 2413 insertions(+), 179 deletions(-) create mode 100644 changelog.d/head-dependent-elsewhere-swap.fixed.md create mode 100644 policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.yaml create mode 100644 policyengine_us/variables/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.py create mode 100644 policyengine_us/variables/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.py diff --git a/changelog.d/head-dependent-elsewhere-swap.fixed.md b/changelog.d/head-dependent-elsewhere-swap.fixed.md new file mode 100644 index 00000000000..4b4d878f15f --- /dev/null +++ b/changelog.d/head-dependent-elsewhere-swap.fixed.md @@ -0,0 +1 @@ +The federal standard deduction and the New York, Michigan, Oregon, Virginia, Missouri, New Mexico, Maine and Hawaii rules for a filer claimed as a dependent elsewhere no longer depend on which spouse is labelled head; each follows its jurisdiction's rule for a joint return where one spouse can be claimed. New York's $3,100 dependent standard deduction now applies only to single filers, and Hawaii now limits the standard deduction of a filer who can be claimed as a dependent. diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml index 0a894e4a148..3ff2ac9a451 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml @@ -1,4 +1,4 @@ -description: Hawaii allows for itemized deductions for dependent filers with deductions above this amount. +description: Hawaii limits the standard deduction of a filer who can be claimed as a dependent to the greater of this amount or earned income, and lets such a filer itemize deductions above that limit. values: 2018-01-01: 500 metadata: diff --git a/policyengine_us/parameters/gov/states/mi/tax/income/exemptions/dependent_on_other_return.yaml b/policyengine_us/parameters/gov/states/mi/tax/income/exemptions/dependent_on_other_return.yaml index 44fbabc57c8..46ca0dabbff 100644 --- a/policyengine_us/parameters/gov/states/mi/tax/income/exemptions/dependent_on_other_return.yaml +++ b/policyengine_us/parameters/gov/states/mi/tax/income/exemptions/dependent_on_other_return.yaml @@ -1,21 +1,10 @@ -description: Michigan provides this exemption amount for a taxpayer who can be claimed as a dependent on someone else’s return. -SINGLE: - 2021-01-01: 1_500 -JOINT: - 2021-01-01: 3_000 -HEAD_OF_HOUSEHOLD: - 2021-01-01: 1_500 -SURVIVING_SPOUSE: - 2021-01-01: 1_500 -SEPARATE: +description: Michigan provides this exemption amount to each individual who can be claimed as a dependent on another taxpayer's return, in place of that individual's personal exemption. +values: 2021-01-01: 1_500 metadata: - propagate_metadata_to_children: true unit: currency-USD period: year - breakdown: - - filing_status - label: Michigan dependent on other return exemption amount + label: Michigan exemption amount per individual claimable as a dependent elsewhere reference: - title: Michigan Legal Code Section 206.30 - (4) href: https://www.legislature.mi.gov/Laws/MCL?objectName=mcl-206-30 diff --git a/policyengine_us/parameters/gov/states/ny/tax/income/deductions/standard/dependent_elsewhere.yaml b/policyengine_us/parameters/gov/states/ny/tax/income/deductions/standard/dependent_elsewhere.yaml index 4744cadf89b..efe6e74704b 100644 --- a/policyengine_us/parameters/gov/states/ny/tax/income/deductions/standard/dependent_elsewhere.yaml +++ b/policyengine_us/parameters/gov/states/ny/tax/income/deductions/standard/dependent_elsewhere.yaml @@ -1,10 +1,10 @@ -description: New York standard deduction for filers who can be claimed as a dependent elsewhere. +description: New York provides this standard deduction to single filers who can be claimed as a dependent on another taxpayer's federal return. values: 2020-01-01: 3_100 metadata: unit: currency-USD period: year - label: New York standard deduction for dependent filers + label: New York standard deduction for single dependent filers reference: - title: 2021 NY Form IT-201 Instructions Line 34 href: https://www.tax.ny.gov/pdf/2021/inc/it201i_2021.pdf#page=19 diff --git a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py new file mode 100644 index 00000000000..8c3e6fc2c48 --- /dev/null +++ b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py @@ -0,0 +1,355 @@ +"""Rules for a filer claimed as a dependent elsewhere do not depend on which +spouse is labelled head. + +A married couple may list either spouse first, so exchanging the two adults' +`is_tax_unit_head` and `is_tax_unit_spouse` labels, with every other input +kept, must not change their taxes. On main, the federal standard deduction and +seventeen state formulas read only `head_is_dependent_elsewhere`, so a joint +return where one spouse can be claimed on another return (a joint return filed +only to recover withholding can meet the federal exception, IRS Publication +501) was taxed differently depending on which spouse was labelled head. Each +formula now applies its jurisdiction's rule for that case: + +- Either spouse limits the return (the IRC 63(c)(5) worksheet): the federal + standard deduction, Oregon's and Hawaii's standard deductions, and + Virginia's credit for low-income individuals. +- Only single filers: New York's dependent standard deduction. +- Each spouse separately: Michigan's exemptions and Hawaii's food/excise, + renters and Act 115 credits count only the filers who cannot be claimed. +- Only when every filer is a dependent: New Mexico's rebates, credits and + dependents deduction, and Maine's sales tax fairness credit. +- Never on a joint return: Missouri's working family credit, since such a + couple files as married filing combined. + +Hypothesis draws batches of married couples, with and without dependents, in +the eight states with such a rule and one without, for 2021-2026, with either, +both or neither spouse claimed elsewhere. A seeded population and crafted +cases add breadth in every year, and two seeded couples plus the review case +in every state guard 2026. Each batch is one vectorized simulation. For each couple: + +1. Swap invariance: federal income tax, state income tax before and after + refundable credits, and each of the eighteen formulas are the same to the + cent (or the same boolean) under either labelling. +2. Monotonicity: marking another filer as claimed elsewhere never raises any + of the eighteen amounts or turns an eligibility on. +3. The helper identities: `head_or_spouse_is_dependent_elsewhere` equals + `head_is_dependent_elsewhere | spouse_is_dependent_elsewhere`, and + `head_spouse_count_not_dependent_elsewhere` plus the claimed filers equals + the number of filers. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.model_api import STATES + +TOLERANCE = 0.01 # dollars: the same to the cent +YEARS = [2021, 2022, 2023, 2024, 2025, 2026] +AFFECTED = ["HI", "ME", "MI", "MO", "NM", "NY", "OR", "VA", "CA"] +ALL_STATES = [s for s in STATES if s not in ("PR", "VI")] +TAX_UNIT_OUTPUTS = [ + "income_tax", + "state_income_tax", + "state_income_tax_before_refundable_credits", +] +# The formulas that read whether a filer is claimed elsewhere. +CONSUMERS = [ + "basic_standard_deduction", + "ny_standard_deduction", + "mi_exemptions", + "or_standard_deduction", + "va_low_income_tax_credit_eligible", + "mo_wftc_eligible", + "nm_2021_income_rebate", + "nm_additional_2021_income_rebate", + "nm_supplemental_2021_income_rebate", + "nm_deduction_for_certain_dependents_eligible", + "nm_medical_expense_credit", + "nm_cdcc_eligible", + "me_sales_tax_fairness_credit_eligible", + "hi_deductions", + "hi_act_115_rebate", + "hi_food_excise_credit", + "hi_tax_credit_for_low_income_household_renters_eligible", + "hi_tax_credit_for_low_income_household_renters", +] +CLAIM_PATTERNS = [(True, False), (False, True), (True, True), (False, False)] + + +def money(high): + return st.one_of(st.just(0.0), st.integers(1, high).map(float)) + + +@st.composite +def adults(draw): + # Low incomes reach the credits' limits; ages reach the 65+ rules. + return { + "age": draw(st.integers(18, 90)), + "is_blind": draw(st.booleans()), + "is_disabled": draw(st.booleans()), + "employment_income": draw(money(60_000)), + "self_employment_income": draw(money(20_000)), + "taxable_interest_income": draw(money(3_000)), + "social_security_retirement": draw(money(25_000)), + "real_estate_taxes": draw(money(6_000)), + "rent": draw(money(18_000)), + "charitable_cash_donations": draw(money(6_000)), + "other_medical_expenses": draw(money(40_000)), + } + + +@st.composite +def dependents(draw): + age = draw(st.integers(0, 23)) + return { + "age": age, + "is_full_time_student": age >= 18, + "employment_income": draw(money(10_000)), + "pre_subsidy_childcare_expenses": draw(money(8_000)), + } + + +@st.composite +def couples(draw): + return { + "state": draw(st.sampled_from(AFFECTED)), + "adults": [draw(adults()), draw(adults())], + "dependents": draw(st.lists(dependents(), max_size=2)), + "claimed": draw(st.sampled_from(CLAIM_PATTERNS)), + } + + +SEED = 20261008 + + +def _seeded_adult(rng): + def some(high, p): + return float(round(rng.uniform(1, high))) if rng.random() < p else 0.0 + + age = int(rng.integers(18, 91)) + return { + "age": age, + "is_blind": bool(rng.random() < 0.1), + "is_disabled": bool(rng.random() < 0.1), + "employment_income": some(60_000, 0.7), + "self_employment_income": some(20_000, 0.15), + "taxable_interest_income": some(3_000, 0.3), + "social_security_retirement": some(25_000, 0.5 if age >= 62 else 0.03), + "real_estate_taxes": some(6_000, 0.3), + "rent": some(18_000, 0.5), + "charitable_cash_donations": some(6_000, 0.3), + "other_medical_expenses": some(40_000, 0.15), + } + + +def _seeded_couples(states, per_state): + rng = np.random.default_rng(SEED) + return [ + { + "state": state, + "adults": [_seeded_adult(rng), _seeded_adult(rng)], + "dependents": [ + {"age": int(rng.integers(0, 18))} + for _ in range(int(rng.choice([0, 0, 1, 2]))) + ], + "claimed": CLAIM_PATTERNS[int(rng.integers(0, 3))], + } + for state in states + for _ in range(per_state) + ] + + +def _crafted_cases(states, review_case_only=False): + # The review case (married adults aged 20 earning $8,000 each, one + # claimed by a parent), with and without a child, and older couples who + # reach the 65+ and renter rules. + young = {"age": 20, "employment_income": 8_000.0, "rent": 6_000.0} + old = { + "age": 70, + "social_security_retirement": 12_000.0, + "rent": 9_000.0, + "other_medical_expenses": 30_000.0, + } + cases = [] + for state in states: + for claimed in CLAIM_PATTERNS[:3]: + cases += [ + { + "state": state, + "adults": [young, young], + "dependents": [], + "claimed": claimed, + }, + ] + if review_case_only: + continue + cases += [ + { + "state": state, + "adults": [young, {"age": 22, "employment_income": 3_000.0}], + "dependents": [{"age": 2}], + "claimed": claimed, + }, + { + "state": state, + "adults": [old, {**old, "age": 66}], + "dependents": [], + "claimed": claimed, + }, + ] + return cases + + +def _situation(units, year, variants): + # Each unit appears once per variant: (head is the first adult, first + # adult claimed, second adult claimed). People keep their order and every + # other input in every copy. + people = {} + groups = { + "tax_units": {}, + "spm_units": {}, + "families": {}, + "marital_units": {}, + "households": {}, + } + for i, unit in enumerate(units): + for copy, (first_is_head, claim_a, claim_b) in enumerate(variants(unit)): + a, b = f"a_{i}_{copy}", f"b_{i}_{copy}" + for name, amounts, is_head, claimed in ( + (a, unit["adults"][0], first_is_head, claim_a), + (b, unit["adults"][1], not first_is_head, claim_b), + ): + people[name] = { + **amounts, + "is_tax_unit_head": is_head, + "is_tax_unit_spouse": not is_head, + "is_tax_unit_dependent": False, + "claimed_as_dependent_on_another_return": claimed, + } + members = [a, b] + groups["marital_units"][f"couple_{i}_{copy}"] = {"members": [a, b]} + for j, amounts in enumerate(unit["dependents"]): + child = f"dependent_{i}_{copy}_{j}" + people[child] = { + **amounts, + "is_tax_unit_head": False, + "is_tax_unit_spouse": False, + "is_tax_unit_dependent": True, + } + members.append(child) + groups["marital_units"][f"single_{i}_{copy}_{j}"] = {"members": [child]} + for group, prefix in ( + ("tax_units", "tax_unit"), + ("spm_units", "spm_unit"), + ("families", "family"), + ): + groups[group][f"{prefix}_{i}_{copy}"] = {"members": members} + groups["households"][f"household_{i}_{copy}"] = { + "members": members, + "state_code": {year: unit["state"]}, + } + people = { + name: {k: {year: v} for k, v in values.items()} + for name, values in people.items() + } + return {"people": people, **groups} + + +def _swap_variants(unit): + claim_a, claim_b = unit["claimed"] + return [(True, claim_a, claim_b), (False, claim_a, claim_b)] + + +def _calc(sim, name, year): + return np.asarray(sim.calculate(name, year), dtype=float) + + +def _check_swap(units, year, outputs): + sim = Simulation(situation=_situation(units, year, _swap_variants)) + states = np.array([u["state"] for u in units]) + for name in outputs: + values = _calc(sim, name, year) + assert np.isfinite(values).all(), f"{name} in {year} is not finite" + drawn, swapped = values[0::2], values[1::2] + differs = ~(np.abs(swapped - drawn) <= TOLERANCE) + assert not differs.any(), ( + f"{name} in {year} changes when the head and spouse labels are " + f"exchanged: " + + ", ".join( + f"{states[i]} claimed={units[i]['claimed']} " + f"{drawn[i]:.2f} -> {swapped[i]:.2f}" + for i in np.flatnonzero(differs)[:10] + ) + ) + # 3. The helper identities. + either = _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) + head = _calc(sim, "head_is_dependent_elsewhere", year) + spouse = _calc(sim, "spouse_is_dependent_elsewhere", year) + np.testing.assert_array_equal(either, np.maximum(head, spouse)) + independent = _calc(sim, "head_spouse_count_not_dependent_elsewhere", year) + np.testing.assert_array_equal(independent + head + spouse, 2) + + +def _monotone_variants(unit): + # Nobody, either adult, then both adults claimed; labels fixed. + return [ + (True, False, False), + (True, True, False), + (True, False, True), + (True, True, True), + ] + + +def _check_monotone(units, year): + sim = Simulation(situation=_situation(units, year, _monotone_variants)) + states = np.array([u["state"] for u in units]) + for name in CONSUMERS: + values = _calc(sim, name, year).reshape(-1, 4) + nobody, first, second, both = values.T + for more, fewer, label in ( + (first, nobody, "first adult"), + (second, nobody, "second adult"), + (both, first, "both after first"), + (both, second, "both after second"), + ): + rises = more > fewer + TOLERANCE + assert not rises.any(), ( + f"{name} in {year} rises when the {label} is claimed " + f"elsewhere: " + + ", ".join( + f"{states[i]} {fewer[i]:.2f} -> {more[i]:.2f}" + for i in np.flatnonzero(rises)[:10] + ) + ) + + +# A batch's cost is mostly per-variable overhead, so each example is a large +# batch and there are few examples. +@settings( + max_examples=6, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.sampled_from(YEARS), st.lists(couples(), min_size=8, max_size=20)) +def test_claimed_spouse_does_not_depend_on_head_label(year, units): + _check_swap(units, year, TAX_UNIT_OUTPUTS + CONSUMERS) + + +@pytest.mark.parametrize("year", YEARS) +def test_seeded_population(year): + units = _seeded_couples(AFFECTED, per_state=4) + _crafted_cases(AFFECTED) + _check_swap(units, year, TAX_UNIT_OUTPUTS + CONSUMERS) + _check_monotone(units, year) + + +def test_every_state_with_a_claimed_spouse(): + # Other states' rules for a claimed filer read both spouses already; + # this guards their state income tax in one year. + units = _seeded_couples(ALL_STATES, per_state=2) + _crafted_cases( + ALL_STATES, review_case_only=True + ) + _check_swap(units, 2026, TAX_UNIT_OUTPUTS) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml index 483f073599a..b9a821293a5 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml @@ -207,3 +207,59 @@ members: [head] output: basic_standard_deduction: 3_650 + +- name: 2025 joint return uses the dependent worksheet when either spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: CA + output: + # Joint earned income $16,000 + $450 = $16,450, below the $31,500 joint amount. + basic_standard_deduction: 16_450 + +- name: 2025 joint return uses the dependent worksheet when either spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: CA + output: + # Joint earned income $16,000 + $450 = $16,450, below the $31,500 joint amount. + basic_standard_deduction: 16_450 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/act_163_sunset.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/act_163_sunset.yaml index 4116a0a6dac..778252cac05 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/act_163_sunset.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/act_163_sunset.yaml @@ -34,7 +34,6 @@ adjusted_gross_income: 10_000 filing_status: JOINT exemptions_count: 2 - head_is_dependent_elsewhere: false households: household: members: [person1, person2] @@ -58,7 +57,6 @@ adjusted_gross_income: 4_000 filing_status: JOINT exemptions_count: 2 - head_is_dependent_elsewhere: false households: household: members: [person1, person2] @@ -80,7 +78,6 @@ adjusted_gross_income: 10_000 filing_status: SINGLE exemptions_count: 1 - head_is_dependent_elsewhere: false households: household: members: [person1] @@ -102,7 +99,6 @@ adjusted_gross_income: 4_000 filing_status: SINGLE exemptions_count: 1 - head_is_dependent_elsewhere: false households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml index 9ad568e7fee..e340975fe5c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml @@ -20,7 +20,6 @@ adjusted_gross_income: 4_000 filing_status: SINGLE exemptions_count: 3 - head_is_dependent_elsewhere: false households: household: members: [person1, person2, person3] @@ -50,7 +49,6 @@ adjusted_gross_income: 4_000 filing_status: SINGLE exemptions_count: 3 - head_is_dependent_elsewhere: false households: household: members: [person1, person2, person3] @@ -71,6 +69,7 @@ person2: age: 39 is_child: false + claimed_as_dependent_on_another_return: true spm_units: spm_unit: members: [person1, person2] @@ -81,7 +80,6 @@ adjusted_gross_income: 4_000 filing_status: SINGLE exemptions_count: 2 - head_is_dependent_elsewhere: true households: household: members: [person1, person2] @@ -111,7 +109,6 @@ adjusted_gross_income: 35_000 filing_status: SINGLE exemptions_count: 3 - head_is_dependent_elsewhere: false households: household: members: [person1, person2, person3] @@ -141,7 +138,6 @@ adjusted_gross_income: 35_000 filing_status: JOINT exemptions_count: 3 - head_is_dependent_elsewhere: false households: household: members: [person1, person2, person3] @@ -171,7 +167,6 @@ adjusted_gross_income: 55_000 filing_status: JOINT exemptions_count: 3 - head_is_dependent_elsewhere: false households: household: members: [person1, person2, person3] @@ -197,7 +192,6 @@ members: [person1, person2] adjusted_gross_income: 6_000 filing_status: SINGLE - head_is_dependent_elsewhere: false households: household: members: [person1, person2] @@ -223,7 +217,6 @@ members: [person1, person2] adjusted_gross_income: 6_000 filing_status: SINGLE - head_is_dependent_elsewhere: false households: household: members: [person1, person2] @@ -258,3 +251,83 @@ exemptions_count: 2 hi_food_excise_credit: 440 hi_income_tax: -440 + +- name: 2027 joint return where only one spouse can be claimed (the claimable spouse is the head) + period: 2027 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + # Form N-311 counts only the spouse who cannot be claimed: 1 x $220. + hi_food_excise_credit: 220 + +- name: 2027 joint return where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2027 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_b, person_a] + state_code: HI + output: + # Form N-311 counts only the spouse who cannot be claimed: 1 x $220. + hi_food_excise_credit: 220 + +- name: 2027 joint return where both spouses can be claimed + period: 2027 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + hi_food_excise_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_act_115_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_act_115_rebate.yaml index 13ebfd8d94a..7c4030e8e4c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_act_115_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_act_115_rebate.yaml @@ -162,3 +162,93 @@ state_code: HI output: hi_act_115_rebate: 0 + +- name: Joint return with a child where only one spouse can be claimed (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + adjusted_gross_income: 5_000 + households: + household: + members: [person_a, person_b, child_1] + state_code: HI + output: + # Only the spouse who cannot be claimed has a qualified exemption; a joint + # return on which a spouse can be claimed claims no dependents. 1 x $300. + hi_act_115_rebate: 300 + +- name: Joint return with a child where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + adjusted_gross_income: 5_000 + households: + household: + members: [person_b, person_a, child_1] + state_code: HI + output: + # Only the spouse who cannot be claimed has a qualified exemption; a joint + # return on which a spouse can be claimed claims no dependents. 1 x $300. + hi_act_115_rebate: 300 + +- name: Joint return where both spouses can be claimed + period: 2021 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 5_000 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + hi_act_115_rebate: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.yaml index cddb436375d..2dd4cd51e42 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.yaml @@ -37,3 +37,67 @@ hi_tax_credit_for_low_income_household_renters_eligible: false output: hi_tax_credit_for_low_income_household_renters: 0 + +- name: Joint return with a child where only one spouse can be claimed (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 66 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 66 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + hi_tax_credit_for_low_income_household_renters_eligible: true + households: + household: + members: [person_a, person_b, child_1] + state_code: HI + output: + # Schedule X lines 8-10: the spouse who cannot be claimed, plus their age + # 65 exemption. 2 x $50. + hi_tax_credit_for_low_income_household_renters: 100 + +- name: Joint return with a child where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 66 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 66 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + hi_tax_credit_for_low_income_household_renters_eligible: true + households: + household: + members: [person_b, person_a, child_1] + state_code: HI + output: + # Schedule X lines 8-10: the spouse who cannot be claimed, plus their age + # 65 exemption. 2 x $50. + hi_tax_credit_for_low_income_household_renters: 100 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.yaml index 4916c7667b8..629efc27e05 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.yaml @@ -2,7 +2,7 @@ period: 2021 input: state_code: HI - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false hi_agi: 29_000 rent: 1_100 output: @@ -12,7 +12,7 @@ period: 2021 input: state_code: HI - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false hi_agi: 31_000 rent: 1_100 output: @@ -22,7 +22,7 @@ period: 2021 input: state_code: HI - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false hi_agi: 29_000 rent: 900 output: @@ -32,8 +32,91 @@ period: 2021 input: state_code: HI - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false hi_agi: 32_000 rent: 900 output: hi_tax_credit_for_low_income_household_renters_eligible: false + +- name: Joint return where only one spouse can be claimed (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + rent: 1_100 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + hi_agi: 29_000 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + # HRS 235-55.7(b): the spouse who cannot be claimed may claim the credit. + hi_tax_credit_for_low_income_household_renters_eligible: true + +- name: Joint return where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 20 + rent: 1_100 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + hi_agi: 29_000 + households: + household: + members: [person_b, person_a] + state_code: HI + output: + # HRS 235-55.7(b): the spouse who cannot be claimed may claim the credit. + hi_tax_credit_for_low_income_household_renters_eligible: true + +- name: Joint return where both spouses can be claimed + period: 2021 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + rent: 1_100 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + hi_agi: 29_000 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + hi_tax_credit_for_low_income_household_renters_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml index fee63c3b063..377d8edd769 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml @@ -7,7 +7,7 @@ hi_agi: 1_000 hi_total_itemized_deductions: 5_000 hi_reduced_itemized_deductions: 1_000 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: hi_deductions: 5_000 @@ -20,7 +20,7 @@ hi_agi: 1_000_000 hi_total_itemized_deductions: 5_000 hi_reduced_itemized_deductions: 1_000 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: hi_deductions: 4_400 @@ -33,7 +33,7 @@ hi_agi: 1_000 hi_total_itemized_deductions: 1_000 hi_reduced_itemized_deductions: 1_000 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: hi_deductions: 4_400 @@ -46,7 +46,7 @@ hi_agi: 1_000_000 hi_total_itemized_deductions: 5_000 hi_reduced_itemized_deductions: 1_000 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: hi_deductions: 2_200 @@ -59,7 +59,7 @@ hi_agi: 1_000_000 hi_total_itemized_deductions: 5_000 hi_reduced_itemized_deductions: 4_500 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: hi_deductions: 4_500 @@ -71,3 +71,127 @@ employment_income: 62_000 output: hi_deductions: 8_800 + +- name: 2025 joint standard deduction limited when either spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + tax_unit_earned_income: 3_000 + hi_itemized_deductions: 0 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + # HRS 235-2.4(a)(3): greater of $500 or earned income ($3,000), up to $8,800. + hi_deductions: 3_000 + +- name: 2025 joint standard deduction limited when either spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + tax_unit_earned_income: 3_000 + hi_itemized_deductions: 0 + households: + household: + members: [person_b, person_a] + state_code: HI + output: + # HRS 235-2.4(a)(3): greater of $500 or earned income ($3,000), up to $8,800. + hi_deductions: 3_000 + +- name: 2025 joint filers with a claimable spouse itemize above the limited standard deduction (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + tax_unit_earned_income: 3_000 + hi_itemized_deductions: 4_000 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + # Itemized deductions of $4,000 exceed the limited $3,000 standard deduction. + hi_deductions: 4_000 + +- name: 2025 joint filers with a claimable spouse itemize above the limited standard deduction (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + tax_unit_earned_income: 3_000 + hi_itemized_deductions: 4_000 + households: + household: + members: [person_b, person_a] + state_code: HI + output: + # Itemized deductions of $4,000 exceed the limited $3,000 standard deduction. + hi_deductions: 4_000 + +- name: 2025 single dependent filer with little earned income gets the $500 minimum + period: 2025 + input: + state_code: HI + filing_status: SINGLE + claimed_as_dependent_on_another_return: true + tax_unit_earned_income: 300 + hi_itemized_deductions: 0 + output: + # HRS 235-2.4(a)(3): greater of $500 or earned income ($300). + hi_deductions: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_salt_deduction_integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_salt_deduction_integration.yaml index 74db648797c..7efb22ba1f0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_salt_deduction_integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_salt_deduction_integration.yaml @@ -4,7 +4,7 @@ state_code: HI filing_status: SINGLE age: 40 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false adjusted_gross_income: 99_999 hi_agi: 99_999 state_withheld_income_tax: 6_000 @@ -32,7 +32,7 @@ state_code: HI filing_status: SINGLE age: 40 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false adjusted_gross_income: 100_000 hi_agi: 100_000 state_withheld_income_tax: 6_000 @@ -60,7 +60,7 @@ state_code: HI filing_status: SINGLE age: 40 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false adjusted_gross_income: 99_999 hi_agi: 99_999 state_withheld_income_tax: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.yaml index b3afa273542..b81ac606775 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/credits/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.yaml @@ -2,7 +2,7 @@ period: 2022 input: state_code: ME - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true filing_status: JOINT output: me_sales_tax_fairness_credit_eligible: false @@ -11,7 +11,7 @@ period: 2022 input: state_code: ME - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false filing_status: SINGLE output: me_sales_tax_fairness_credit_eligible: true @@ -20,7 +20,86 @@ period: 2022 input: state_code: ME - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true filing_status: SEPARATE output: me_sales_tax_fairness_credit_eligible: false + +- name: Joint return where only one spouse can be claimed as a dependent (the claimable spouse is the head) + period: 2022 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: ME + output: + # 36 MRSA 5213-A(6)(C) excludes the claimable individual; the other spouse + # still qualifies for the joint credit. + me_sales_tax_fairness_credit_eligible: true + +- name: Joint return where only one spouse can be claimed as a dependent (the claimable spouse is the spouse) + period: 2022 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: ME + output: + # 36 MRSA 5213-A(6)(C) excludes the claimable individual; the other spouse + # still qualifies for the joint credit. + me_sales_tax_fairness_credit_eligible: true + +- name: Joint return where both spouses can be claimed as dependents + period: 2022 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: ME + output: + me_sales_tax_fairness_credit_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/exemptions/mi_exemptions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/exemptions/mi_exemptions.yaml index f916a47b51d..eee9da8f35d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/exemptions/mi_exemptions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/exemptions/mi_exemptions.yaml @@ -55,20 +55,36 @@ period: 2021 input: mi_personal_exemptions: 5_000 - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true filing_status: SINGLE state_code: MI output: mi_exemptions: 1_500 -- name: not disabled, is a dependent, married +- name: not disabled, both spouses can be claimed as dependents, married period: 2021 input: - mi_personal_exemptions: 10_000 - head_is_dependent_elsewhere: true - filing_status: JOINT - state_code: MI + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: MI output: + # $1,500 for each spouse. mi_exemptions: 3_000 # 2025 test cases - personal exemption $5,800, disabled exemption $3,400 @@ -301,15 +317,131 @@ person1: mi_disabled_exemption_eligible_person: true is_fully_disabled_service_connected_veteran: false + claimed_as_dependent_on_another_return: true tax_units: tax_unit: mi_personal_exemptions: 5_800 - head_is_dependent_elsewhere: true filing_status: SINGLE members: [person1] household: members: [person1] state_code: MI output: - # Dependent exemption: $1,500 (for single) + $3,400 disabled = $4,900 + # Dependent exemption: $1,500 + $3,400 disabled = $4,900 mi_exemptions: 4_900 + +- name: 2025 joint return where only one spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: MI + output: + # MCL 206.30(4): $1,500 for the claimable spouse, plus the other + # spouse's own $5,800 personal exemption. + mi_exemptions: 7_300 + +- name: 2025 joint return where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: MI + output: + # MCL 206.30(4): $1,500 for the claimable spouse, plus the other + # spouse's own $5,800 personal exemption. + mi_exemptions: 7_300 + +- name: 2025 joint return with a child where only one spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child_1] + state_code: MI + output: + # A joint return on which a spouse can be claimed claims no dependents + # (IRS Publication 501), so the child adds no exemption. + mi_exemptions: 7_300 + +- name: 2025 joint return with a child where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child_1] + state_code: MI + output: + # A joint return on which a spouse can be claimed claims no dependents + # (IRS Publication 501), so the child adds no exemption. + mi_exemptions: 7_300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml index dba14ed3fb0..6cb8a3e48c6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml @@ -133,3 +133,59 @@ # $4,401 is below the federal EITC investment income limit ($11,950 in # 2025) but above Missouri's $4,400 limit. mo_wftc_eligible: false + +- name: 2025 married couple filing combined is not stopped by a claimable spouse (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + eitc: 500 + households: + household: + members: [person_a, person_b] + state_code: MO + output: + # A joint federal filer whose spouse can be claimed still files Form MO-1040 + # as married filing combined, not as claimed as a dependent. + mo_wftc_eligible: true + +- name: 2025 married couple filing combined is not stopped by a claimable spouse (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + eitc: 500 + households: + household: + members: [person_b, person_a] + state_code: MO + output: + # A joint federal filer whose spouse can be claimed still files Form MO-1040 + # as married filing combined, not as claimed as a dependent. + mo_wftc_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.yaml index df774c30456..de3db5f2233 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.yaml @@ -1,7 +1,7 @@ - name: Is dependent on another return period: 2022 input: - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true state_code: NM earned_income: 1 is_disabled: false @@ -15,7 +15,7 @@ - name: No earned income period: 2022 input: - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false state_code: NM earned_income: 0 is_disabled: false @@ -29,7 +29,7 @@ - name: No earned income but disabled - eligible period: 2022 input: - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false state_code: NM earned_income: 0 is_disabled: true @@ -43,7 +43,7 @@ - name: Over income threshold period: 2022 input: - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false state_code: NM earned_income: 0 is_disabled: false @@ -57,7 +57,7 @@ - name: Receives tanf period: 2022 input: - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false state_code: NM earned_income: 0 is_disabled: false @@ -71,7 +71,7 @@ - name: Eligible period: 2022 input: - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false state_code: NM earned_income: 1 is_disabled: false @@ -101,7 +101,6 @@ tax_units: tax_unit: members: [head, spouse] - head_is_dependent_elsewhere: false filing_status: JOINT nm_modified_gross_income: 30_159 households: @@ -130,7 +129,6 @@ tax_units: tax_unit: members: [head, spouse] - head_is_dependent_elsewhere: false filing_status: JOINT nm_modified_gross_income: 30_159 households: @@ -159,7 +157,6 @@ tax_units: tax_unit: members: [head, spouse] - head_is_dependent_elsewhere: false filing_status: JOINT nm_modified_gross_income: 30_159 households: @@ -188,7 +185,6 @@ tax_units: tax_unit: members: [head, spouse] - head_is_dependent_elsewhere: false filing_status: JOINT nm_modified_gross_income: 30_159 households: @@ -217,7 +213,6 @@ tax_units: tax_unit: members: [head, spouse] - head_is_dependent_elsewhere: false filing_status: JOINT nm_modified_gross_income: 30_159 households: @@ -230,7 +225,7 @@ - name: Reported TANF receipt disqualifies despite zero computed TANF period: 2022 input: - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false state_code: NM earned_income: 0 is_disabled: true @@ -241,3 +236,89 @@ nm_modified_gross_income: 30_159 output: nm_cdcc_eligible: false + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the head) + period: 2022 + input: + people: + person_a: + age: 30 + earned_income: 1 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + earned_income: 1 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + nm_modified_gross_income: 30_159 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_cdcc_eligible: true + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the spouse) + period: 2022 + input: + people: + person_b: + age: 30 + earned_income: 1 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + earned_income: 1 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + nm_modified_gross_income: 30_159 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_cdcc_eligible: true + +- name: Joint return where both spouses are a dependent of another taxpayer + period: 2022 + input: + people: + person_a: + age: 30 + earned_income: 1 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + earned_income: 1 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + nm_modified_gross_income: 30_159 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + nm_cdcc_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml index df55277d73a..518854ff24e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml @@ -4,7 +4,7 @@ state_code: NM age: 64 other_medical_expenses: 30_000 - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_medical_expense_credit: 0 @@ -16,7 +16,7 @@ medicare_enrolled: false other_medical_expenses: 27_999 filing_status: JOINT - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_medical_expense_credit: 0 @@ -27,7 +27,7 @@ age: 65 other_medical_expenses: 28_000 filing_status: JOINT - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_medical_expense_credit: 2_800 @@ -38,7 +38,7 @@ age: 65 other_medical_expenses: 28_000 filing_status: SEPARATE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_medical_expense_credit: 1_400 @@ -49,7 +49,7 @@ age: 65 other_medical_expenses: 40_000 filing_status: SINGLE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_medical_expense_credit: 2_800 @@ -60,6 +60,86 @@ age: 65 other_medical_expenses: 40_000 filing_status: SINGLE - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true + output: + nm_medical_expense_credit: 0 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the head) + period: 2023 + input: + people: + person_a: + age: 66 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 66 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_medical_expense_credit: 2_800 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the spouse) + period: 2023 + input: + people: + person_b: + age: 66 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 66 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_medical_expense_credit: 2_800 + +- name: Joint return where both spouses are a dependent of another taxpayer + period: 2023 + input: + people: + person_a: + age: 66 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 66 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_a, person_b] + state_code: NM output: nm_medical_expense_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.yaml index 6f65f043918..bcb29cf3062 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.yaml @@ -3,7 +3,7 @@ input: exemptions: 0 state_code: NM - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_deduction_for_certain_dependents_eligible: true @@ -12,7 +12,7 @@ input: exemptions: 1 state_code: NM - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_deduction_for_certain_dependents_eligible: false @@ -22,6 +22,86 @@ input: exemptions: 0 state_code: NM - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true + output: + nm_deduction_for_certain_dependents_eligible: false + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + exemptions: 0 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_deduction_for_certain_dependents_eligible: true + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + exemptions: 0 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_deduction_for_certain_dependents_eligible: true + +- name: Joint return where both spouses are a dependent of another taxpayer + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + exemptions: 0 + households: + household: + members: [person_a, person_b] + state_code: NM output: nm_deduction_for_certain_dependents_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_2021_income_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_2021_income_rebate.yaml index 72c2bb91a67..3a1c22772bc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_2021_income_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_2021_income_rebate.yaml @@ -4,7 +4,7 @@ state_code: NM adjusted_gross_income: 74_999 filing_status: SINGLE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_2021_income_rebate: 250 @@ -14,7 +14,7 @@ state_code: NM adjusted_gross_income: 75_000 filing_status: SEPARATE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_2021_income_rebate: 0 @@ -24,7 +24,7 @@ state_code: NM adjusted_gross_income: 149_999 filing_status: JOINT - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_2021_income_rebate: 500 @@ -34,7 +34,7 @@ state_code: NM adjusted_gross_income: 150_000 filing_status: SURVIVING_SPOUSE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_2021_income_rebate: 0 @@ -44,6 +44,86 @@ state_code: NM adjusted_gross_income: 149_999 filing_status: JOINT - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true + output: + nm_2021_income_rebate: 0 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 50_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_2021_income_rebate: 500 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + adjusted_gross_income: 50_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_2021_income_rebate: 500 + +- name: Joint return where both spouses are a dependent of another taxpayer + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 50_000 + households: + household: + members: [person_a, person_b] + state_code: NM output: nm_2021_income_rebate: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_additional_2021_income_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_additional_2021_income_rebate.yaml index ddf2f6150d1..91a64599414 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_additional_2021_income_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_additional_2021_income_rebate.yaml @@ -3,7 +3,7 @@ input: state_code: NM filing_status: JOINT - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_additional_2021_income_rebate: 1_000 @@ -12,7 +12,7 @@ input: state_code: NM filing_status: SINGLE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_additional_2021_income_rebate: 500 @@ -21,7 +21,7 @@ input: state_code: NM filing_status: SEPARATE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_additional_2021_income_rebate: 500 @@ -30,7 +30,7 @@ input: state_code: NM filing_status: SURVIVING_SPOUSE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_additional_2021_income_rebate: 1_000 @@ -39,7 +39,7 @@ input: state_code: NM filing_status: HEAD_OF_HOUSEHOLD - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_additional_2021_income_rebate: 1_000 @@ -48,6 +48,83 @@ input: state_code: NM filing_status: SEPARATE - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true + output: + nm_additional_2021_income_rebate: 0 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_additional_2021_income_rebate: 1_000 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_additional_2021_income_rebate: 1_000 + +- name: Joint return where both spouses are a dependent of another taxpayer + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NM output: nm_additional_2021_income_rebate: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_supplemental_2021_income_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_supplemental_2021_income_rebate.yaml index ac64cc9f434..0b1b8565f7a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_supplemental_2021_income_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/2021_income/nm_supplemental_2021_income_rebate.yaml @@ -3,7 +3,7 @@ input: state_code: NM filing_status: JOINT - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_supplemental_2021_income_rebate: 1_000 @@ -12,7 +12,7 @@ input: state_code: NM filing_status: SINGLE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_supplemental_2021_income_rebate: 500 @@ -21,7 +21,7 @@ input: state_code: NM filing_status: SEPARATE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_supplemental_2021_income_rebate: 500 @@ -30,7 +30,7 @@ input: state_code: NM filing_status: SURVIVING_SPOUSE - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_supplemental_2021_income_rebate: 1_000 @@ -39,7 +39,7 @@ input: state_code: NM filing_status: HEAD_OF_HOUSEHOLD - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false output: nm_supplemental_2021_income_rebate: 1_000 @@ -48,6 +48,83 @@ input: state_code: NM filing_status: HEAD_OF_HOUSEHOLD - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true + output: + nm_supplemental_2021_income_rebate: 0 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_supplemental_2021_income_rebate: 1_000 + +- name: Joint return where only one spouse is a dependent of another taxpayer (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # A spouse who is not a dependent may still qualify (PIT-RC instructions). + nm_supplemental_2021_income_rebate: 1_000 + +- name: Joint return where both spouses are a dependent of another taxpayer + period: 2021 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NM output: nm_supplemental_2021_income_rebate: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.yaml new file mode 100644 index 00000000000..cd98af8b0dc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.yaml @@ -0,0 +1,94 @@ +- name: 2025 single filer who can be claimed as a dependent + period: 2025 + input: + state_code: NY + filing_status: SINGLE + claimed_as_dependent_on_another_return: true + output: + ny_standard_deduction: 3_100 + +- name: 2025 single filer who cannot be claimed as a dependent + period: 2025 + input: + state_code: NY + filing_status: SINGLE + claimed_as_dependent_on_another_return: false + output: + ny_standard_deduction: 8_000 + +- name: 2025 head of household who can be claimed as a dependent keeps the regular amount + period: 2025 + input: + state_code: NY + filing_status: HEAD_OF_HOUSEHOLD + claimed_as_dependent_on_another_return: true + output: + # The IT-201 table gives the dependent amount only to filing status 1. + ny_standard_deduction: 11_200 + +- name: 2025 separate filer who can be claimed as a dependent keeps the regular amount + period: 2025 + input: + state_code: NY + filing_status: SEPARATE + claimed_as_dependent_on_another_return: true + output: + ny_standard_deduction: 8_000 + +- name: 2025 joint return where only one spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NY + output: + # The IT-201 table gives married filing jointly $16,050 whether or not + # item C is marked. + ny_standard_deduction: 16_050 + +- name: 2025 joint return where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + employment_income: 8_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: NY + output: + # The IT-201 table gives married filing jointly $16,050 whether or not + # item C is marked. + ny_standard_deduction: 16_050 diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml index 6c2182a4783..7818ce120d2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml @@ -68,7 +68,7 @@ state_code: OR filing_status: JOINT age_head: 35 - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true tax_unit_earned_income: 600 output: # $1100 base @@ -80,7 +80,7 @@ state_code: OR filing_status: SINGLE age_head: 35 - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true tax_unit_earned_income: 1_000 output: # $1000 income + $350 addition, below the $2,350 normal standard deduction cap. @@ -92,7 +92,7 @@ state_code: OR filing_status: SINGLE age: 35 - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true tax_unit_earned_income: 1_000 blind_head: true output: @@ -137,3 +137,57 @@ output: # $2,835 base + $1,200 aged or_standard_deduction: 4_035 + +- name: 2025 joint return limited to the dependent amount when either spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 35 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 35 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + tax_unit_earned_income: 3_000 + households: + household: + members: [person_a, person_b] + state_code: OR + output: + # max($1,350, $3,000 + $450) = $3,450, below the $5,670 joint amount. + or_standard_deduction: 3_450 + +- name: 2025 joint return limited to the dependent amount when either spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 35 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 35 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + tax_unit_earned_income: 3_000 + households: + household: + members: [person_b, person_a] + state_code: OR + output: + # max($1,350, $3,000 + $450) = $3,450, below the $5,670 joint amount. + or_standard_deduction: 3_450 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.yaml index 5584f3c0288..b5cf4a21af1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.yaml @@ -2,7 +2,7 @@ period: 2021 input: state_code: VA - head_is_dependent_elsewhere: true + claimed_as_dependent_on_another_return: true va_aged_blind_exemption: 0 va_age_deduction: 0 va_military_benefit_subtraction: 0 @@ -14,7 +14,7 @@ period: 2021 input: state_code: VA - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false va_aged_blind_exemption: 1_600 va_age_deduction: 12_000 va_military_benefit_subtraction: 0 @@ -26,10 +26,60 @@ period: 2021 input: state_code: VA - head_is_dependent_elsewhere: false + claimed_as_dependent_on_another_return: false va_aged_blind_exemption: 0 va_age_deduction: 0 va_military_benefit_subtraction: 0 va_federal_state_employees_subtraction: 0 output: va_low_income_tax_credit_eligible: true + +- name: Joint return is ineligible when either spouse is claimed as a dependent (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: VA + output: + va_low_income_tax_credit_eligible: false + +- name: Joint return is ineligible when either spouse is claimed as a dependent (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: VA + output: + va_low_income_tax_credit_eligible: false diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.yaml new file mode 100644 index 00000000000..80029885451 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.yaml @@ -0,0 +1,114 @@ +- name: Head is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [head, spouse] + output: + head_or_spouse_is_dependent_elsewhere: true + +- name: Spouse is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + output: + head_or_spouse_is_dependent_elsewhere: true + +- name: Both spouses are claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + output: + head_or_spouse_is_dependent_elsewhere: true + +- name: Neither spouse is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [head, spouse] + output: + head_or_spouse_is_dependent_elsewhere: false + +- name: A dependent claimed elsewhere is not a filer + period: 2025 + input: + people: + head: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + child: + age: 10 + is_tax_unit_head: false + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, child] + output: + head_or_spouse_is_dependent_elsewhere: false + +- name: Single filer claimed elsewhere + period: 2025 + input: + people: + head: + age: 19 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + output: + head_or_spouse_is_dependent_elsewhere: true diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.yaml new file mode 100644 index 00000000000..ac5381a1b4b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.yaml @@ -0,0 +1,114 @@ +- name: Head is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [head, spouse] + output: + head_spouse_count_not_dependent_elsewhere: 1 + +- name: Spouse is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + output: + head_spouse_count_not_dependent_elsewhere: 1 + +- name: Both spouses are claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + output: + head_spouse_count_not_dependent_elsewhere: 0 + +- name: Neither spouse is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [head, spouse] + output: + head_spouse_count_not_dependent_elsewhere: 2 + +- name: A dependent claimed elsewhere is not a filer + period: 2025 + input: + people: + head: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + child: + age: 10 + is_tax_unit_head: false + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, child] + output: + head_spouse_count_not_dependent_elsewhere: 1 + +- name: Single filer claimed elsewhere + period: 2025 + input: + people: + head: + age: 19 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + output: + head_spouse_count_not_dependent_elsewhere: 0 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py index e3d3745bdc5..e8575cdcc7e 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py @@ -7,13 +7,23 @@ class basic_standard_deduction(Variable): label = "Basic standard deduction" definition_period = YEAR unit = USD - reference = "https://www.law.cornell.edu/uscode/text/26/63#c_2" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/63#c_2", + "https://www.law.cornell.edu/uscode/text/26/63#c_5", + # Standard Deduction Worksheet for Dependents: "Use this worksheet + # only if someone can claim you, or your spouse if filing jointly, + # as a dependent." + "https://www.irs.gov/pub/irs-prior/i1040gi--2025.pdf#page=35", + ) def formula(tax_unit, period, parameters): p = parameters(period).gov.irs.deductions.standard filing_status = tax_unit("filing_status", period) separate_filer_itemizes = tax_unit("separate_filer_itemizes", period) - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # A joint return uses the dependent worksheet, with the couple's + # earned income and the joint amount as the cap, when either spouse + # can be claimed. + dependent_elsewhere = tax_unit("head_or_spouse_is_dependent_elsewhere", period) standard_deduction = p.amount[filing_status] standard_deduction_if_dependent = min_( standard_deduction, diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py index c4f098d02f1..a98403539f7 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py @@ -8,17 +8,20 @@ class hi_food_excise_credit(Variable): defined_for = StateCode.HI unit = USD definition_period = YEAR - reference = "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=44" + reference = ( + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=50", + "https://files.hawaii.gov/tax/forms/2025/n311_i.pdf#page=1", + ) def formula(tax_unit, period, parameters): - # filer can not be a dependent on another return - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) - total_amount = add( - tax_unit, - period, - [ - "hi_food_excise_credit_minor_child_amount", - "hi_food_excise_exemption_amount", - ], + # HRS 235-55.85(a): only a taxpayer who cannot be claimed as a + # dependent may claim the credit, for each qualified exemption. The + # exemption amount counts only filers who cannot be claimed when + # either can; such a return claims no dependents (IRS Publication + # 501), so it gets no minor child amount. + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + exemption_amount = tax_unit("hi_food_excise_exemption_amount", period) + minor_child_amount = tax_unit( + "hi_food_excise_credit_minor_child_amount", period ) - return ~dependent_elsewhere * total_amount + return exemption_amount + where(dependent_filer, 0, minor_child_amount) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py index 1b4e22e61bc..d11bb0a6cac 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py @@ -7,6 +7,11 @@ class hi_food_excise_exemption_amount(Variable): label = "Exemption amount for Hawaii Food/Excise Tax Credit" definition_period = YEAR defined_for = StateCode.HI + reference = ( + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=50", + "https://files.hawaii.gov/tax/forms/2025/n311_i.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=11", + ) def formula(tax_unit, period, parameters): p = parameters(period).gov.states.hi.tax.income.credits.food_excise_tax @@ -45,4 +50,15 @@ def formula(tax_unit, period, parameters): claimable_exemptions = exemptions - minor_children else: claimable_exemptions = exemptions - return claimable_exemptions * amount_per_exemption + # Form N-311 counts only people who cannot be claimed as a dependent + # by another taxpayer. A return on which a filer can be claimed claims + # no dependents (IRS Publication 501), so it counts only the filers + # who cannot be claimed. + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + independent_filers = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period + ) + qualified_exemptions = where( + dependent_filer, independent_filers, claimable_exemptions + ) + return qualified_exemptions * amount_per_exemption diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py index 8fd79179391..3a229d50ebb 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py @@ -12,6 +12,7 @@ class hi_act_115_rebate(Variable): "https://tax.hawaii.gov/act-115-ref/", "https://data.capitol.hawaii.gov/sessions/session2022/bills/SB514_CD2_.HTM", "https://files.hawaii.gov/tax/news/announce/ann22-03.pdf#page=2", + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=45", ) def formula(tax_unit, period, parameters): @@ -39,6 +40,15 @@ def formula(tax_unit, period, parameters): # person in the tax unit is one exemption. exemptions = tax_unit("exemptions_count", period) # A person who can be claimed as a dependent by another taxpayer is - # not a qualifying resident taxpayer. - head_is_dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) - return where(head_is_dependent_elsewhere, 0, amount_per_exemption * exemptions) + # not a qualifying resident taxpayer, and each qualifying resident + # taxpayer claims the refund for the exemptions they are entitled to. + # A filer who can be claimed has no exemption (HRS 235-54(a)), and a + # return on which a filer can be claimed claims no dependents (IRS + # Publication 501), so such a return counts only the filers who + # cannot be claimed. + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + independent_filers = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period + ) + qualified_exemptions = where(dependent_filer, independent_filers, exemptions) + return amount_per_exemption * qualified_exemptions diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py index e0a69bef748..81a87e2876e 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py @@ -8,18 +8,36 @@ class hi_tax_credit_for_low_income_household_renters(Variable): unit = USD definition_period = YEAR defined_for = "hi_tax_credit_for_low_income_household_renters_eligible" + reference = ( + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=48", + "https://files.hawaii.gov/tax/forms/2025/schx_i.pdf#page=1", + ) def formula(tax_unit, period, parameters): p = parameters(period).gov.states.hi.tax.income.credits.lihrtc - # Aged extra exemptions - aged_head = (tax_unit("age_head", period) >= p.aged_age_threshold).astype(int) - aged_spouse = (tax_unit("age_spouse", period) >= p.aged_age_threshold).astype( - int + # Schedule X lines 8-10 count only people who cannot be claimed as a + # dependent by another taxpayer, with an extra exemption for each + # such filer aged 65 or over. A return on which a filer can be + # claimed claims no dependents (IRS Publication 501), so it counts + # only the filers who cannot be claimed. + aged_head = (tax_unit("age_head", period) >= p.aged_age_threshold) & ~tax_unit( + "head_is_dependent_elsewhere", period ) + aged_spouse = ( + tax_unit("age_spouse", period) >= p.aged_age_threshold + ) & ~tax_unit("spouse_is_dependent_elsewhere", period) + aged_exemptions = aged_head.astype(int) + aged_spouse.astype(int) - aged_exemptions = aged_head + aged_spouse - exemptions = tax_unit("exemptions_count", period) + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + independent_filers = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period + ) + exemptions = where( + dependent_filer, + independent_filers, + tax_unit("exemptions_count", period), + ) total_exemptions = exemptions + aged_exemptions return p.amount * total_exemptions diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py index 3c99b935916..db97f6a626f 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py @@ -6,16 +6,26 @@ class hi_tax_credit_for_low_income_household_renters_eligible(Variable): entity = TaxUnit label = "Eligible for the Hawaii low income household renters tax credit" definition_period = YEAR - reference = "https://files.hawaii.gov/tax/legal/har/har_235.pdf#page=105" # §18-235-55.7 (b) + reference = ( + "https://files.hawaii.gov/tax/legal/har/har_235.pdf#page=105", # §18-235-55.7 (b) + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=48", + "https://files.hawaii.gov/tax/forms/2025/schx_i.pdf#page=1", + ) defined_for = StateCode.HI def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # HRS 235-55.7(b) allows the credit to each resident taxpayer who + # cannot be claimed as a dependent. Schedule X stops at line 3 only + # for the first-listed filer; since a couple may list either spouse + # first, a joint return qualifies unless both spouses can be claimed. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) p = parameters(period).gov.states.hi.tax.income.credits.lihrtc agi = tax_unit("hi_agi", period) rent = add(tax_unit, period, ["rent"]) return ( (rent > p.eligibility.rent_threshold) & (agi < p.eligibility.agi_limit) - & ~dependent_elsewhere + & independent_filer ) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py index 5e84412ac6c..70a783508dd 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py @@ -10,6 +10,11 @@ class hi_deductions(Variable): "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=15\n" # Itemized Deduction "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=20" # Standard Deduction ) + reference = ( + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=11", + # PDF pages 15, 20 + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=15", + ) definition_period = YEAR defined_for = StateCode.HI @@ -25,14 +30,26 @@ def formula(tax_unit, period, parameters): filing_status_eligible = ( total_itemized_deduction > p.threshold.deductions[filing_status] ) - is_dependent_on_another_return = tax_unit("head_is_dependent_elsewhere", period) - standard_cap = min_(tax_unit_earned_income, standard_deduction) - dependent_floor = max_(p.threshold.dependent, standard_cap) + # HRS 235-2.4(a)(3) applies IRC 63(c)(5): the standard deduction of a + # filer who can be claimed as a dependent is the greater of $500 or + # earned income, up to the regular amount. As federally, a joint + # return is limited when either spouse can be claimed. + is_dependent_on_another_return = tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) + dependent_standard_deduction = min_( + max_(p.threshold.dependent, tax_unit_earned_income), standard_deduction + ) + applicable_standard_deduction = where( + is_dependent_on_another_return, + dependent_standard_deduction, + standard_deduction, + ) dependent_eligible = is_dependent_on_another_return & ( - total_itemized_deduction > dependent_floor + total_itemized_deduction > dependent_standard_deduction ) itemized_deductions_eligible = filing_status_eligible | dependent_eligible itemized_deduction = where( itemized_deductions_eligible, total_itemized_deduction, 0 ) - return max_(itemized_deduction, standard_deduction) + return max_(itemized_deduction, applicable_standard_deduction) diff --git a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py index c0ea36df12a..d761c2d8ecf 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py +++ b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py @@ -6,11 +6,21 @@ class me_sales_tax_fairness_credit_eligible(Variable): entity = TaxUnit label = "Eligible for the Maine sales tax fairness credit" definition_period = YEAR - reference = "https://legislature.maine.gov/statutes/36/title36sec5213-A.html" + reference = ( + "https://legislature.maine.gov/statutes/36/title36sec5213-A.html", + "https://www.maine.gov/revenue/sites/maine.gov.revenue/files/inline-files/25_1040me_sch_ptfc_fillable.pdf#page=3", + ) defined_for = StateCode.ME def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # 36 MRSA 5213-A(6)(C) excludes individuals who may be claimed as a + # dependent on another return, and subsection 2 allows each resident + # individual the base credit for the return's filing status. On a + # joint return where only one spouse can be claimed, the other spouse + # still qualifies for the joint credit. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE - return ~dependent_elsewhere & ~separate + return independent_filer & ~separate diff --git a/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py b/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py index 5974fd46303..1c77fb504ce 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py @@ -11,6 +11,8 @@ class mi_exemptions(Variable): reference = ( "https://www.legislature.mi.gov/Laws/MCL?objectName=mcl-206-30", "https://www.legislature.mi.gov/Publications/TaxpayerGuide.pdf", + "https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2025/MI-1040-Book.pdf#page=10", + "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=11", ) def formula(tax_unit, period, parameters): @@ -31,16 +33,31 @@ def formula(tax_unit, period, parameters): ) disabled_veteran_exemption = disabled_veterans * p.disabled.amount.veteran - # Dependent-on-other-return exemptions - filing_status = tax_unit("filing_status", period) - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) - dependent_exemption = ( - dependent_elsewhere * p.dependent_on_other_return[filing_status] + # MCL 206.30(4): an individual who can be claimed as a dependent on + # another return gets no personal exemption but subtracts $1,500. + # This applies to each spouse separately, so on a joint return where + # only one spouse can be claimed, the other keeps their own personal + # exemption (and any stillbirth exemption). A joint return on which + # either spouse can be claimed claims no dependents (IRS Publication + # 501, "Dependent Taxpayer Test"). + filers = add(tax_unit, period, ["is_tax_unit_head_or_spouse"]) + independent_filers = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period ) + dependent_filers = filers - independent_filers + stillborn = tax_unit("tax_unit_stillborn_children", period) + independent_filer_exemptions = ( + independent_filers + where(independent_filers > 0, stillborn, 0) + ) * p.personal + dependent_filer_exemptions = dependent_filers * p.dependent_on_other_return # Total exemptions return ( - where(dependent_elsewhere, dependent_exemption, personal_exemption) + where( + dependent_filers > 0, + independent_filer_exemptions + dependent_filer_exemptions, + personal_exemption, + ) + disabled_exemption + disabled_veteran_exemption ) diff --git a/policyengine_us/variables/gov/states/mo/tax/income/credits/mo_wftc_eligible.py b/policyengine_us/variables/gov/states/mo/tax/income/credits/mo_wftc_eligible.py index d1b71e8d93e..582fdb3ed2a 100644 --- a/policyengine_us/variables/gov/states/mo/tax/income/credits/mo_wftc_eligible.py +++ b/policyengine_us/variables/gov/states/mo/tax/income/credits/mo_wftc_eligible.py @@ -8,7 +8,8 @@ class mo_wftc_eligible(Variable): definition_period = YEAR reference = ( "https://revisor.mo.gov/main/OneSection.aspx?section=143.177&bid=49978&hl=", - "https://dor.mo.gov/forms/MO-1040%20Instructions_2025.pdf#page=43", + # PDF pages 6, 43 + "https://dor.mo.gov/forms/MO-1040%20Instructions_2025.pdf#page=6", ) defined_for = StateCode.MO @@ -23,10 +24,17 @@ def formula(tax_unit, period, parameters): # married filing separately. filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE - # The 2024 and 2025 Form MO-WFTC checklists also stop filers - # claimed as a dependent on another return; the 2023 form does not. - excluded_dependent = p.dependent_filers_excluded & tax_unit( - "head_is_dependent_elsewhere", period + # The 2024 and 2025 Form MO-WFTC checklists also stop filers with a + # filing status of "claimed as a dependent"; the 2023 form does not. + # A couple filing a joint federal return files Form MO-1040 as + # married filing combined even when a spouse can be claimed (2025 + # MO-1040 instructions, "Filing status"), so the stop never applies + # to a joint return. + joint = filing_status == filing_status.possible_values.JOINT + excluded_dependent = ( + p.dependent_filers_excluded + & ~joint + & tax_unit("head_or_spouse_is_dependent_elsewhere", period) ) # Form MO-WFTC applies Missouri's own investment income limit, # reflecting IRC Section 32(i) as of January 1, 2021 per RSMo diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py index ea1c0e9fd46..48198e1b904 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py @@ -6,13 +6,22 @@ class nm_cdcc_eligible(Variable): entity = TaxUnit label = "Eligible household for the New Mexico dependent child day care credit" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503752/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsfAEwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503752/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsfAEwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=2", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): person = tax_unit.members - # Filer can not be a dependent on another tax return - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # Filer can not be a dependent on another tax return, but "If you are + # a dependent with a spouse who was not a dependent of another + # taxpayer, your spouse may still qualify to claim rebates or + # credits" (PIT-RC instructions). So only a return on which every + # filer is a dependent elsewhere is barred. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) p = parameters(period).gov.states.nm.tax.income.credits.cdcc # Filer has to be be gainfully employed to receive credit has_earnings = person("earned_income", period) > 0 @@ -43,8 +52,5 @@ def formula(tax_unit, period, parameters): ) income_eligible = nm_modified_gross_income <= income_limit return ( - ~dependent_elsewhere - & employment_eligible - & ~receives_tanf - & income_eligible + independent_filer & employment_eligible & ~receives_tanf & income_eligible ) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py index e516884f45e..205f3b1b0e4 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py @@ -6,7 +6,10 @@ class nm_medical_expense_credit(Variable): entity = TaxUnit label = "New Mexico unreimbursed medical expense care credit" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503776/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDswgGwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503776/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDswgGwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=2", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): @@ -17,8 +20,15 @@ def formula(tax_unit, period, parameters): medical_expense = tax_unit("itemized_medical_expenses", period) age_eligible = tax_unit.any(age >= p.age_eligibility) expense_eligible = medical_expense >= p.min_expenses - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) - eligible = age_eligible & expense_eligible & ~dependent_elsewhere + # The rebate and credit schedule bars a filer who is a dependent of + # another taxpayer, but "If you are a dependent with a spouse who was + # not a dependent of another taxpayer, your spouse may still qualify + # to claim rebates or credits" (PIT-RC instructions). So only a return + # on which every filer is a dependent elsewhere is barred. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) + eligible = age_eligible & expense_eligible & independent_filer # exemption is halved for married filing separately filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py index d0415b60595..d66ef23f1a6 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py @@ -6,12 +6,22 @@ class nm_deduction_for_certain_dependents_eligible(Variable): entity = TaxUnit label = "Eligibility for New Mexico deduction for certain dependents" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503892/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcATgBMASgA0ybKUIQAiokK4AntADkW6REJhcCFWs069BoyADKeUgCFNAJQCiAGRcA1AIIA5AMIu0qRgAEbQpOySkkA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503892/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcATgBMASgA0ybKUIQAiokK4AntADkW6REJhcCFWs069BoyADKeUgCFNAJQCiAGRcA1AIIA5AMIu0qRgAEbQpOySkkA", + "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=2", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # The deduction uses the same "not a dependent of another individual" + # test as the rebates and credits on Schedule PIT-RC, whose + # instructions let a spouse who is not a dependent still qualify. So + # only a return on which every filer is a dependent elsewhere is + # barred. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) # deduction does not apply if an exemption under IRS 151 is claimed; # IRC 151 refers to the federal personal exemption federal_exemption_amount = tax_unit("exemptions", period) - return ~dependent_elsewhere & (federal_exemption_amount == 0) + return independent_filer & (federal_exemption_amount == 0) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py index 1f37e1c7c21..8ce48277203 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py @@ -6,15 +6,26 @@ class nm_2021_income_rebate(Variable): entity = TaxUnit label = "New Mexico 2021 income tax rebate" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503708/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsPABwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503708/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsPABwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + # PDF pages 2-3 + "https://realfile.tax.newmexico.gov/2021pit-rc-ins.pdf#page=2", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # The rebate and credit schedule bars a filer who is a dependent of + # another taxpayer, but "If you are a dependent with a spouse who was + # not a dependent of another taxpayer, your spouse may still qualify + # to claim rebates or credits" (PIT-RC instructions). So only a return + # on which every filer is a dependent elsewhere is barred. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) agi = tax_unit("adjusted_gross_income", period) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] income_eligible = agi < p.main.income_limit[filing_status] - eligible = income_eligible & ~dependent_elsewhere + eligible = income_eligible & independent_filer amount_if_eligible = p.main.amount[filing_status] return eligible * amount_if_eligible diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py index 891ce07d54e..dd68e862de4 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py @@ -6,11 +6,22 @@ class nm_additional_2021_income_rebate(Variable): entity = TaxUnit label = "New Mexico additional 2021 income tax rebate" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503710/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsHHgEoANMmylCEAIqJCuAJ7QA5BskRCYXAiUr1WnXoMgAynlIAhdQCUAogBknANQCCAOQDCTyVIwACNoUnZxcSA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503710/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsHHgEoANMmylCEAIqJCuAJ7QA5BskRCYXAiUr1WnXoMgAynlIAhdQCUAogBknANQCCAOQDCTyVIwACNoUnZxcSA", + # PDF pages 2-3 + "https://realfile.tax.newmexico.gov/2021pit-rc-ins.pdf#page=2", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # The rebate and credit schedule bars a filer who is a dependent of + # another taxpayer, but "If you are a dependent with a spouse who was + # not a dependent of another taxpayer, your spouse may still qualify + # to claim rebates or credits" (PIT-RC instructions). So only a return + # on which every filer is a dependent elsewhere is barred. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] filing_status = tax_unit("filing_status", period) - return ~dependent_elsewhere * p.additional.amount[filing_status] + return independent_filer * p.additional.amount[filing_status] diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py index 7839b75873d..e98e3be36dd 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py @@ -6,11 +6,22 @@ class nm_supplemental_2021_income_rebate(Variable): entity = TaxUnit label = "New Mexico supplemental 2021 income tax rebate" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503706/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsPAGwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503706/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDsPAGwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + # PDF pages 2-3 + "https://realfile.tax.newmexico.gov/2021pit-rc-ins.pdf#page=2", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # The rebate and credit schedule bars a filer who is a dependent of + # another taxpayer, but "If you are a dependent with a spouse who was + # not a dependent of another taxpayer, your spouse may still qualify + # to claim rebates or credits" (PIT-RC instructions). So only a return + # on which every filer is a dependent elsewhere is barred. + independent_filer = ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 + ) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] filing_status = tax_unit("filing_status", period) - return ~dependent_elsewhere * p.supplemental.amount[filing_status] + return independent_filer * p.supplemental.amount[filing_status] diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.py index d98250ebddb..1720bb49cd2 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/ny_standard_deduction.py @@ -7,17 +7,21 @@ class ny_standard_deduction(Variable): label = "NY standard deduction" unit = USD definition_period = YEAR - reference = "https://www.nysenate.gov/legislation/laws/TAX/613" + reference = ( + "https://www.nysenate.gov/legislation/laws/TAX/614", + # Standard deduction table: the dependent amount applies only to + # filing status 1, "Single and you marked item C Yes". + "https://www.tax.ny.gov/pdf/2025/inc/it201i_2025.pdf#page=11", + ) defined_for = StateCode.NY def formula(tax_unit, period, parameters): - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) - standard_deduction = parameters( - period - ).gov.states.ny.tax.income.deductions.standard + p = parameters(period).gov.states.ny.tax.income.deductions.standard filing_status = tax_unit("filing_status", period) + single = filing_status == filing_status.possible_values.SINGLE + dependent_elsewhere = tax_unit("head_or_spouse_is_dependent_elsewhere", period) return where( - dependent_elsewhere, - standard_deduction.dependent_elsewhere, - standard_deduction.amount[filing_status], + single & dependent_elsewhere, + p.dependent_elsewhere, + p.amount[filing_status], ) diff --git a/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py b/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py index 6d8dde3554d..eb3c543e6fa 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py +++ b/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py @@ -8,9 +8,10 @@ class or_standard_deduction(Variable): unit = USD definition_period = YEAR documentation = "Oregon standard deduction, including bonus for aged or blind and special rules for filers who are claimable as dependents." - reference = ( # TODO: update + reference = ( "https://www.oregon.gov/dor/forms/FormsPubs/form-or-40-inst_101-040-1_2021.pdf#page=18", - "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html", # Subsection 316.695 (7) + "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html", # ORS 316.695(7)-(8) + "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=96", ) defined_for = StateCode.OR @@ -25,7 +26,11 @@ def formula(tax_unit, period, parameters): claimable_dep_earned_amount = ( earned_income + p.claimable_as_dependent.earned_income_addition ) - dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) + # ORS 316.695(8) limits the deduction as IRC 63(c)(5) does, so a joint + # return uses the couple's earned income and the joint amount when + # either spouse can be claimed (Publication OR-17, "Standard deduction + # worksheet for married filing jointly (MFJ) dependents"). + dependent_elsewhere = tax_unit("head_or_spouse_is_dependent_elsewhere", period) # Set floor and ceiling around earned income plus additional amount. floored_claimable_dep_amount = max_( claimable_dep_earned_amount, claimable_dep_floor diff --git a/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py b/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py index 82b96fe10d0..7b746711ad1 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py +++ b/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py @@ -7,16 +7,23 @@ class va_low_income_tax_credit_eligible(Variable): label = "Eligible for the Virginia Low Income Tax Credit" definition_period = YEAR defined_for = StateCode.VA + reference = ( + "https://law.lis.virginia.gov/vacode/title58.1/chapter3/section58.1-339.8/", + "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2025-760-instructions.pdf#page=30", + ) def formula(tax_unit, period, parameters): # Criteria 1: Not qualified to claim EITC if anyone in the tax unit claimed any of the following: p = parameters(period).gov.states.va.tax.income.credits.eitc.low_income_tax program_eligible = add(tax_unit, period, p.ineligible_programs) == 0 - # Criteria 2: Not qualified to claim EITC if the filier is claimed as a dependent on another taxpayer's return - - head_is_not_dependent_elsewhere = ~tax_unit( - "head_is_dependent_elsewhere", period + # Criteria 2: Not qualified if a filer is claimed as a dependent on + # another taxpayer's return. Va. Code 58.1-339.8 bars the credit + # against the tax of a dependent of another individual or couple; a + # joint return carries both spouses' tax, so either spouse being + # claimed bars it. + filer_is_dependent_elsewhere = tax_unit( + "head_or_spouse_is_dependent_elsewhere", period ) - return program_eligible & head_is_not_dependent_elsewhere + return program_eligible & ~filer_is_dependent_elsewhere diff --git a/policyengine_us/variables/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.py b/policyengine_us/variables/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.py new file mode 100644 index 00000000000..493bf92c3c4 --- /dev/null +++ b/policyengine_us/variables/household/demographic/tax_unit/head_or_spouse_is_dependent_elsewhere.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class head_or_spouse_is_dependent_elsewhere(Variable): + value_type = bool + entity = TaxUnit + definition_period = YEAR + label = "Head or spouse is a dependent elsewhere" + documentation = ( + "Whether the head or the spouse of this tax unit is claimed as a " + "dependent in another tax unit. Unlike head_is_dependent_elsewhere, " + "this does not depend on which spouse is labelled head." + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + claimed_on_another_return = person( + "claimed_as_dependent_on_another_return", period + ) + filer = person("is_tax_unit_head_or_spouse", period) + return tax_unit.any(claimed_on_another_return & filer) diff --git a/policyengine_us/variables/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.py b/policyengine_us/variables/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.py new file mode 100644 index 00000000000..348b7a993a2 --- /dev/null +++ b/policyengine_us/variables/household/demographic/tax_unit/head_spouse_count_not_dependent_elsewhere.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class head_spouse_count_not_dependent_elsewhere(Variable): + value_type = int + entity = TaxUnit + definition_period = YEAR + label = "Head and spouse count not dependent elsewhere" + documentation = ( + "Number of tax unit heads and spouses who are not claimed as a " + "dependent in another tax unit." + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + claimed_on_another_return = person( + "claimed_as_dependent_on_another_return", period + ) + filer = person("is_tax_unit_head_or_spouse", period) + return tax_unit.sum(filer & ~claimed_on_another_return) From 3ecc675fe669409f59f1a756cf30103598e59598 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 08:35:58 -0400 Subject: [PATCH 2/9] Keep tax units without a filer eligible where only claimed filers are barred The New Mexico, Maine and Hawaii renters gates counted filers who are not claimed elsewhere, so a tax unit with neither a head nor a spouse (252 in the default dataset) looked like one whose every filer was claimed. A new helper, every_filer_is_dependent_elsewhere, requires a claimed filer and no unclaimed one. Co-Authored-By: Claude Opus 5.5 --- ...t_elsewhere_head_spouse_swap_invariance.py | 19 ++-- .../every_filer_is_dependent_elsewhere.yaml | 89 +++++++++++++++++++ ...r_low_income_household_renters_eligible.py | 6 +- .../me_sales_tax_fairness_credit_eligible.py | 6 +- .../income/credits/cdcc/nm_cdcc_eligible.py | 9 +- .../credits/nm_medical_expense_credit.py | 6 +- ...duction_for_certain_dependents_eligible.py | 6 +- .../2021_rebate/nm_2021_income_rebate.py | 6 +- .../nm_additional_2021_income_rebate.py | 6 +- .../nm_supplemental_2021_income_rebate.py | 6 +- .../every_filer_is_dependent_elsewhere.py | 18 ++++ 11 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.yaml create mode 100644 policyengine_us/variables/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.py diff --git a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py index 8c3e6fc2c48..721d38dff05 100644 --- a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py +++ b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py @@ -33,9 +33,10 @@ 2. Monotonicity: marking another filer as claimed elsewhere never raises any of the eighteen amounts or turns an eligibility on. 3. The helper identities: `head_or_spouse_is_dependent_elsewhere` equals - `head_is_dependent_elsewhere | spouse_is_dependent_elsewhere`, and - `head_spouse_count_not_dependent_elsewhere` plus the claimed filers equals - the number of filers. + `head_is_dependent_elsewhere | spouse_is_dependent_elsewhere`, + `every_filer_is_dependent_elsewhere` equals their conjunction for a + couple, and `head_spouse_count_not_dependent_elsewhere` plus the claimed + filers equals the number of filers. """ import numpy as np @@ -173,9 +174,11 @@ def _crafted_cases(states, review_case_only=False): "rent": 9_000.0, "other_medical_expenses": 30_000.0, } + # One claimed adult covers both labellings of a claimed spouse, since each + # case runs with the labels exchanged. cases = [] for state in states: - for claimed in CLAIM_PATTERNS[:3]: + for claimed in [(True, False), (True, True)]: cases += [ { "state": state, @@ -291,6 +294,8 @@ def _check_swap(units, year, outputs): np.testing.assert_array_equal(either, np.maximum(head, spouse)) independent = _calc(sim, "head_spouse_count_not_dependent_elsewhere", year) np.testing.assert_array_equal(independent + head + spouse, 2) + every = _calc(sim, "every_filer_is_dependent_elsewhere", year) + np.testing.assert_array_equal(every, np.minimum(head, spouse)) def _monotone_variants(unit): @@ -329,19 +334,19 @@ def _check_monotone(units, year): # A batch's cost is mostly per-variable overhead, so each example is a large # batch and there are few examples. @settings( - max_examples=6, + max_examples=4, deadline=None, derandomize=True, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], ) -@given(st.sampled_from(YEARS), st.lists(couples(), min_size=8, max_size=20)) +@given(st.sampled_from(YEARS), st.lists(couples(), min_size=6, max_size=15)) def test_claimed_spouse_does_not_depend_on_head_label(year, units): _check_swap(units, year, TAX_UNIT_OUTPUTS + CONSUMERS) @pytest.mark.parametrize("year", YEARS) def test_seeded_population(year): - units = _seeded_couples(AFFECTED, per_state=4) + _crafted_cases(AFFECTED) + units = _seeded_couples(AFFECTED, per_state=2) + _crafted_cases(AFFECTED) _check_swap(units, year, TAX_UNIT_OUTPUTS + CONSUMERS) _check_monotone(units, year) diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.yaml new file mode 100644 index 00000000000..2b934fda74a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.yaml @@ -0,0 +1,89 @@ +- name: Only the head is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [head, spouse] + output: + every_filer_is_dependent_elsewhere: false + +- name: Only the spouse is claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + output: + every_filer_is_dependent_elsewhere: false + +- name: Both spouses are claimed elsewhere + period: 2025 + input: + people: + head: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + spouse: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head, spouse] + output: + every_filer_is_dependent_elsewhere: true + +- name: Single filer claimed elsewhere + period: 2025 + input: + people: + head: + age: 19 + is_tax_unit_head: true + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + output: + every_filer_is_dependent_elsewhere: true + +- name: A tax unit with neither a head nor a spouse has no filer claimed elsewhere + period: 2025 + input: + people: + child: + age: 10 + is_tax_unit_head: false + is_tax_unit_spouse: false + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [child] + output: + every_filer_is_dependent_elsewhere: false diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py index db97f6a626f..61f4c81eaf2 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py @@ -18,14 +18,12 @@ def formula(tax_unit, period, parameters): # cannot be claimed as a dependent. Schedule X stops at line 3 only # for the first-listed filer; since a couple may list either spouse # first, a joint return qualifies unless both spouses can be claimed. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.hi.tax.income.credits.lihrtc agi = tax_unit("hi_agi", period) rent = add(tax_unit, period, ["rent"]) return ( (rent > p.eligibility.rent_threshold) & (agi < p.eligibility.agi_limit) - & independent_filer + & ~every_filer_dependent ) diff --git a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py index d761c2d8ecf..6d8e7aac8dd 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py +++ b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py @@ -18,9 +18,7 @@ def formula(tax_unit, period, parameters): # individual the base credit for the return's filing status. On a # joint return where only one spouse can be claimed, the other spouse # still qualifies for the joint credit. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE - return independent_filer & ~separate + return ~every_filer_dependent & ~separate diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py index 48198e1b904..3bdf5fa4fed 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible.py @@ -19,9 +19,7 @@ def formula(tax_unit, period, parameters): # taxpayer, your spouse may still qualify to claim rebates or # credits" (PIT-RC instructions). So only a return on which every # filer is a dependent elsewhere is barred. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.nm.tax.income.credits.cdcc # Filer has to be be gainfully employed to receive credit has_earnings = person("earned_income", period) > 0 @@ -52,5 +50,8 @@ def formula(tax_unit, period, parameters): ) income_eligible = nm_modified_gross_income <= income_limit return ( - independent_filer & employment_eligible & ~receives_tanf & income_eligible + ~every_filer_dependent + & employment_eligible + & ~receives_tanf + & income_eligible ) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py index 205f3b1b0e4..27585ed2568 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py @@ -25,10 +25,8 @@ def formula(tax_unit, period, parameters): # not a dependent of another taxpayer, your spouse may still qualify # to claim rebates or credits" (PIT-RC instructions). So only a return # on which every filer is a dependent elsewhere is barred. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) - eligible = age_eligible & expense_eligible & independent_filer + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) + eligible = age_eligible & expense_eligible & ~every_filer_dependent # exemption is halved for married filing separately filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py index d66ef23f1a6..389227abe70 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py @@ -18,10 +18,8 @@ def formula(tax_unit, period, parameters): # instructions let a spouse who is not a dependent still qualify. So # only a return on which every filer is a dependent elsewhere is # barred. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) # deduction does not apply if an exemption under IRS 151 is claimed; # IRC 151 refers to the federal personal exemption federal_exemption_amount = tax_unit("exemptions", period) - return independent_filer & (federal_exemption_amount == 0) + return ~every_filer_dependent & (federal_exemption_amount == 0) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py index 8ce48277203..c49035340e2 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py @@ -19,13 +19,11 @@ def formula(tax_unit, period, parameters): # not a dependent of another taxpayer, your spouse may still qualify # to claim rebates or credits" (PIT-RC instructions). So only a return # on which every filer is a dependent elsewhere is barred. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) agi = tax_unit("adjusted_gross_income", period) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] income_eligible = agi < p.main.income_limit[filing_status] - eligible = income_eligible & independent_filer + eligible = income_eligible & ~every_filer_dependent amount_if_eligible = p.main.amount[filing_status] return eligible * amount_if_eligible diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py index dd68e862de4..f574c92dbab 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py @@ -19,9 +19,7 @@ def formula(tax_unit, period, parameters): # not a dependent of another taxpayer, your spouse may still qualify # to claim rebates or credits" (PIT-RC instructions). So only a return # on which every filer is a dependent elsewhere is barred. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] filing_status = tax_unit("filing_status", period) - return independent_filer * p.additional.amount[filing_status] + return ~every_filer_dependent * p.additional.amount[filing_status] diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py index e98e3be36dd..bf8f1a88d52 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py @@ -19,9 +19,7 @@ def formula(tax_unit, period, parameters): # not a dependent of another taxpayer, your spouse may still qualify # to claim rebates or credits" (PIT-RC instructions). So only a return # on which every filer is a dependent elsewhere is barred. - independent_filer = ( - tax_unit("head_spouse_count_not_dependent_elsewhere", period) > 0 - ) + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] filing_status = tax_unit("filing_status", period) - return independent_filer * p.supplemental.amount[filing_status] + return ~every_filer_dependent * p.supplemental.amount[filing_status] diff --git a/policyengine_us/variables/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.py b/policyengine_us/variables/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.py new file mode 100644 index 00000000000..161339f0358 --- /dev/null +++ b/policyengine_us/variables/household/demographic/tax_unit/every_filer_is_dependent_elsewhere.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class every_filer_is_dependent_elsewhere(Variable): + value_type = bool + entity = TaxUnit + definition_period = YEAR + label = "Every head and spouse is a dependent elsewhere" + documentation = ( + "Whether the tax unit has a head or spouse claimed as a dependent in " + "another tax unit and none who is not. False for a tax unit with " + "neither a head nor a spouse." + ) + + def formula(tax_unit, period, parameters): + return tax_unit("head_or_spouse_is_dependent_elsewhere", period) & ( + tax_unit("head_spouse_count_not_dependent_elsewhere", period) == 0 + ) From 8da831289dabf23c4f36e06fa567f4a32a7293d4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 09:14:47 -0400 Subject: [PATCH 3/9] Address review: Hawaii worksheet earnings, NM same-filer age, childless EITC - Hawaii's dependent standard deduction uses earned income net of the deductible part of self-employment tax, as the N-11 worksheet does. - New Mexico's medical care credit needs a filer who is both 65 or older and not a dependent when a filer is a dependent elsewhere (NMSA 7-2-18.13). - The federal EITC without a qualifying child requires that no filer can be claimed as a dependent (IRC 32(c)(1)(A)(ii)(III), Publication 596), so Missouri's working family credit no longer reaches such a couple. - Hawaii's food/excise credit keeps minor children receiving public support (HRS 235-55.85(c)) on a joint return where one spouse can be claimed. - Comments label the readings the law does not settle, and the property test covers the EITC and the food/excise exemption amount. Co-Authored-By: Claude Opus 5.5 --- .../head-dependent-elsewhere-swap.fixed.md | 2 +- ...t_elsewhere_head_spouse_swap_invariance.py | 36 +++-- .../eitc_demographic_eligible.yaml | 130 ++++++++++++++++++ .../food_excise/hi_food_excise_credit.yaml | 130 ++++++++++++++++++ .../tax/income/deductions/hi_deductions.yaml | 79 +++++++++++ .../income/credits/wftc/mo_wftc_eligible.yaml | 62 +++++++++ .../credits/nm_medical_expense_credit.yaml | 106 ++++++++++++++ .../eligibility/eitc_demographic_eligible.py | 16 ++- .../food_excise/hi_food_excise_credit.py | 11 +- .../hi_food_excise_exemption_amount.py | 20 ++- .../tax/income/credits/hi_act_115_rebate.py | 7 +- ...low_income_household_renters_tax_credit.py | 5 +- .../hi/tax/income/deductions/hi_deductions.py | 7 +- .../me_sales_tax_fairness_credit_eligible.py | 6 +- .../mi/tax/income/exemptions/mi_exemptions.py | 12 +- .../credits/nm_medical_expense_credit.py | 22 +-- ...duction_for_certain_dependents_eligible.py | 9 +- .../2021_rebate/nm_2021_income_rebate.py | 13 +- .../nm_additional_2021_income_rebate.py | 13 +- .../nm_supplemental_2021_income_rebate.py | 13 +- .../va_low_income_tax_credit_eligible.py | 9 +- 21 files changed, 635 insertions(+), 73 deletions(-) diff --git a/changelog.d/head-dependent-elsewhere-swap.fixed.md b/changelog.d/head-dependent-elsewhere-swap.fixed.md index 4b4d878f15f..42a32945776 100644 --- a/changelog.d/head-dependent-elsewhere-swap.fixed.md +++ b/changelog.d/head-dependent-elsewhere-swap.fixed.md @@ -1 +1 @@ -The federal standard deduction and the New York, Michigan, Oregon, Virginia, Missouri, New Mexico, Maine and Hawaii rules for a filer claimed as a dependent elsewhere no longer depend on which spouse is labelled head; each follows its jurisdiction's rule for a joint return where one spouse can be claimed. New York's $3,100 dependent standard deduction now applies only to single filers, and Hawaii now limits the standard deduction of a filer who can be claimed as a dependent. +The federal standard deduction and the New York, Michigan, Oregon, Virginia, Missouri, New Mexico, Maine and Hawaii rules for a filer claimed as a dependent elsewhere no longer depend on which spouse is labelled head; each follows its jurisdiction's rule for a joint return where one spouse can be claimed. New York's $3,100 dependent standard deduction now applies only to single filers; Hawaii now limits the standard deduction of a filer who can be claimed as a dependent; the federal EITC without a qualifying child now requires that no filer can be claimed as a dependent; and New Mexico's medical care credit requires a filer who is both 65 or older and not a dependent when a filer is a dependent elsewhere. diff --git a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py index 721d38dff05..d64c56ff5ba 100644 --- a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py +++ b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py @@ -4,34 +4,40 @@ A married couple may list either spouse first, so exchanging the two adults' `is_tax_unit_head` and `is_tax_unit_spouse` labels, with every other input kept, must not change their taxes. On main, the federal standard deduction and -seventeen state formulas read only `head_is_dependent_elsewhere`, so a joint -return where one spouse can be claimed on another return (a joint return filed -only to recover withholding can meet the federal exception, IRS Publication -501) was taxed differently depending on which spouse was labelled head. Each +16 state formulas read only `head_is_dependent_elsewhere`, so a joint return +where one spouse can be claimed on another return (a joint return filed only +to recover withholding can meet the federal exception, IRS Publication 501) +was taxed differently depending on which spouse was labelled head. Each formula now applies its jurisdiction's rule for that case: - Either spouse limits the return (the IRC 63(c)(5) worksheet): the federal standard deduction, Oregon's and Hawaii's standard deductions, and - Virginia's credit for low-income individuals. + Virginia's credit for low-income individuals. The federal EITC without a + qualifying child now also requires that neither spouse can be claimed. - Only single filers: New York's dependent standard deduction. - Each spouse separately: Michigan's exemptions and Hawaii's food/excise, renters and Act 115 credits count only the filers who cannot be claimed. - Only when every filer is a dependent: New Mexico's rebates, credits and - dependents deduction, and Maine's sales tax fairness credit. -- Never on a joint return: Missouri's working family credit, since such a - couple files as married filing combined. + dependents deduction, and Maine's sales tax fairness credit. New Mexico's + medical care credit also needs a filer who is both 65 or older and not a + dependent. +- Never on a joint return: Missouri's own stop in its working family credit, + since such a couple files as married filing combined. Hypothesis draws batches of married couples, with and without dependents, in the eight states with such a rule and one without, for 2021-2026, with either, -both or neither spouse claimed elsewhere. A seeded population and crafted -cases add breadth in every year, and two seeded couples plus the review case -in every state guard 2026. Each batch is one vectorized simulation. For each couple: +both or neither spouse claimed elsewhere, and checks 1 and 3. A seeded +population and crafted cases check 1, 2 and 3 in every year, and two seeded +couples plus the review case in every state check 1 for federal and state +income tax in 2026. Each batch is one vectorized simulation. For each couple: 1. Swap invariance: federal income tax, state income tax before and after - refundable credits, and each of the eighteen formulas are the same to the - cent (or the same boolean) under either labelling. + refundable credits, and each formula in CONSUMERS are the same to the cent + (or the same boolean) under either labelling. 2. Monotonicity: marking another filer as claimed elsewhere never raises any - of the eighteen amounts or turns an eligibility on. + amount in CONSUMERS or turns an eligibility on. This holds for current + parameters and inputs the model derives; for example, a Hawaii standard + deduction input below the itemization threshold could break it. 3. The helper identities: `head_or_spouse_is_dependent_elsewhere` equals `head_is_dependent_elsewhere | spouse_is_dependent_elsewhere`, `every_filer_is_dependent_elsewhere` equals their conjunction for a @@ -59,6 +65,7 @@ # The formulas that read whether a filer is claimed elsewhere. CONSUMERS = [ "basic_standard_deduction", + "eitc", "ny_standard_deduction", "mi_exemptions", "or_standard_deduction", @@ -73,6 +80,7 @@ "me_sales_tax_fairness_credit_eligible", "hi_deductions", "hi_act_115_rebate", + "hi_food_excise_exemption_amount", "hi_food_excise_credit", "hi_tax_credit_for_low_income_household_renters_eligible", "hi_tax_credit_for_low_income_household_renters", diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml index ae065070ccc..80b379d010b 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml @@ -127,3 +127,133 @@ is_full_time_student: true output: eitc_demographic_eligible: false + +- name: 2025 childless single filer who can be claimed as a dependent is ineligible + period: 2025 + input: + people: + head: + age: 30 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + output: + # IRC 32(c)(1)(A)(ii)(III). + eitc_demographic_eligible: false + +- name: 2025 childless joint return where one spouse can be claimed is ineligible (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: CA + output: + # Publication 596 (2025, p.18): "neither you nor your spouse can be claimed as a + # dependent by another person if you claim the EIC on your joint return." + eitc_demographic_eligible: false + +- name: 2025 childless joint return where one spouse can be claimed is ineligible (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: CA + output: + # Publication 596 (2025, p.18): "neither you nor your spouse can be claimed as a + # dependent by another person if you claim the EIC on your joint return." + eitc_demographic_eligible: false + +- name: 2025 joint return with a child where one spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child_1] + state_code: CA + output: + # The dependent condition in IRC 32(c)(1)(A)(ii)(III) applies only without a + # qualifying child. + eitc_demographic_eligible: true + +- name: 2025 joint return with a child where one spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child_1] + state_code: CA + output: + # The dependent condition in IRC 32(c)(1)(A)(ii)(III) applies only without a + # qualifying child. + eitc_demographic_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml index e340975fe5c..eaf2ffad6cd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml @@ -331,3 +331,133 @@ state_code: HI output: hi_food_excise_credit: 0 + +- name: 2022 joint return with a publicly supported minor where only one spouse can be claimed (the claimable spouse is the head) + period: 2022 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_a, person_b, child_1] + state_code: HI + output: + # The spouse who cannot be claimed: $110; HRS 235-55.85(c) lets the minor + # receiving public support count for this credit: $110. + hi_food_excise_credit: 220 + +- name: 2022 joint return with a publicly supported minor where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2022 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_b, person_a, child_1] + state_code: HI + output: + # The spouse who cannot be claimed: $110; HRS 235-55.85(c) lets the minor + # receiving public support count for this credit: $110. + hi_food_excise_credit: 220 + +- name: 2025 joint return with a publicly supported minor where only one spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_a, person_b, child_1] + state_code: HI + output: + # From 2023 the minor counts as a qualified exemption: 2 x $220. + hi_food_excise_credit: 440 + +- name: 2025 joint return with a publicly supported minor where only one spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_b, person_a, child_1] + state_code: HI + output: + # From 2023 the minor counts as a qualified exemption: 2 x $220. + hi_food_excise_credit: 440 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml index 377d8edd769..584d987cbe7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml @@ -195,3 +195,82 @@ output: # HRS 235-2.4(a)(3): greater of $500 or earned income ($300). hi_deductions: 500 + +- name: 2025 single dependent filer with self-employment income uses earned income net of the self-employment tax deduction + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + self_employment_income: 3_000 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + hi_itemized_deductions: 0 + households: + household: + members: [head] + state_code: HI + output: + # N-11 worksheet earned income subtracts Schedule 1 line 15: + # $3,000 x 0.9235 x 15.3% / 2 = $211.94, so $3,000 - $211.94 = $2,788.06. + hi_deductions: 2_788.06 + +- name: 2025 joint return with a claimable self-employed spouse uses earned income net of the self-employment tax deduction (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + self_employment_income: 3_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + hi_itemized_deductions: 0 + households: + household: + members: [person_a, person_b] + state_code: HI + output: + # $3,000 - $3,000 x 0.9235 x 15.3% / 2 = $2,788.06, below the $8,800 joint amount. + hi_deductions: 2788.06 + +- name: 2025 joint return with a claimable self-employed spouse uses earned income net of the self-employment tax deduction (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + self_employment_income: 3_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + hi_itemized_deductions: 0 + households: + household: + members: [person_b, person_a] + state_code: HI + output: + # $3,000 - $3,000 x 0.9235 x 15.3% / 2 = $2,788.06, below the $8,800 joint amount. + hi_deductions: 2788.06 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml index 6cb8a3e48c6..0cea1d97f45 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/credits/wftc/mo_wftc_eligible.yaml @@ -189,3 +189,65 @@ # A joint federal filer whose spouse can be claimed still files Form MO-1040 # as married filing combined, not as claimed as a dependent. mo_wftc_eligible: true + +- name: 2025 childless couple with a claimable student spouse gets no federal EITC and so no WFTC (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + is_full_time_student: true + employment_income: 8_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + employment_income: 8_000 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: MO + output: + # Without a qualifying child, neither spouse may be claimable for the federal + # EITC (Publication 596), which RSMo 143.177 requires. + eitc: 0 + mo_wftc_eligible: false + +- name: 2025 childless couple with a claimable student spouse gets no federal EITC and so no WFTC (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + employment_income: 8_000 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + is_full_time_student: true + employment_income: 8_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: MO + output: + # Without a qualifying child, neither spouse may be claimable for the federal + # EITC (Publication 596), which RSMo 143.177 requires. + eitc: 0 + mo_wftc_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml index 518854ff24e..f8563d24439 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml @@ -143,3 +143,109 @@ state_code: NM output: nm_medical_expense_credit: 0 + +- name: Joint return where only the claimable spouse is 65 or older (the claimable spouse is the head) + period: 2023 + input: + people: + person_a: + age: 70 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 60 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # NMSA 7-2-18.13(A): the taxpayer who is 65 or older must also not be a dependent. + nm_medical_expense_credit: 0 + +- name: Joint return where only the claimable spouse is 65 or older (the claimable spouse is the spouse) + period: 2023 + input: + people: + person_b: + age: 60 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 70 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # NMSA 7-2-18.13(A): the taxpayer who is 65 or older must also not be a dependent. + nm_medical_expense_credit: 0 + +- name: Joint return where the spouse who is not claimable is 65 or older (the claimable spouse is the head) + period: 2023 + input: + people: + person_a: + age: 60 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 70 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + nm_medical_expense_credit: 2_800 + +- name: Joint return where the spouse who is not claimable is 65 or older (the claimable spouse is the spouse) + period: 2023 + input: + people: + person_b: + age: 70 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 60 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + itemized_medical_expenses: 28_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + nm_medical_expense_credit: 2_800 diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py index 2338356763e..053ca2799c0 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/eligibility/eitc_demographic_eligible.py @@ -6,7 +6,10 @@ class eitc_demographic_eligible(Variable): entity = TaxUnit label = "Meets demographic eligibility for EITC" definition_period = YEAR - reference = "https://www.law.cornell.edu/uscode/text/26/32#c_1_A_ii_II" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/32#c_1_A_ii", + "https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=18", + ) def formula(tax_unit, period, parameters): person = tax_unit.members @@ -27,4 +30,13 @@ def formula(tax_unit, period, parameters): # IRC § 32(c)(1)(A)(ii)(II) applies the age test to the filer or, # on a joint return, either spouse — never to dependents. is_filer_or_spouse = ~person("is_tax_unit_dependent", period) - return has_child | tax_unit.any(meets_age_requirements & is_filer_or_spouse) + # IRC § 32(c)(1)(A)(ii)(III): without a qualifying child, the filer + # must not be a dependent of another taxpayer; on a joint return, + # "neither you nor your spouse can be claimed as a dependent by another + # person" (IRS Publication 596). + dependent_elsewhere = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + childless_eligible = ( + tax_unit.any(meets_age_requirements & is_filer_or_spouse) + & ~dependent_elsewhere + ) + return has_child | childless_eligible diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py index a98403539f7..220561807b8 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.py @@ -15,13 +15,14 @@ class hi_food_excise_credit(Variable): def formula(tax_unit, period, parameters): # HRS 235-55.85(a): only a taxpayer who cannot be claimed as a - # dependent may claim the credit, for each qualified exemption. The + # dependent may claim the credit, for each qualified exemption; the # exemption amount counts only filers who cannot be claimed when - # either can; such a return claims no dependents (IRS Publication - # 501), so it gets no minor child amount. - dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + # either can. Minor children receiving public support count for this + # credit under 235-55.85(c), so they keep their amount unless every + # filer can be claimed. + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) exemption_amount = tax_unit("hi_food_excise_exemption_amount", period) minor_child_amount = tax_unit( "hi_food_excise_credit_minor_child_amount", period ) - return exemption_amount + where(dependent_filer, 0, minor_child_amount) + return exemption_amount + where(every_filer_dependent, 0, minor_child_amount) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py index d11bb0a6cac..c597ba1352b 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_exemption_amount.py @@ -44,21 +44,29 @@ def formula(tax_unit, period, parameters): ], ) exemptions = tax_unit("exemptions_count", period) + minor_children = tax_unit("hi_food_excise_credit_minor_child_count", period) if p.minor_child.in_effect: - # Reduce number of exemptions by the number of minor children - minor_children = tax_unit("hi_food_excise_credit_minor_child_count", period) + # Reduce number of exemptions by the number of minor children, + # who get a fixed amount instead. claimable_exemptions = exemptions - minor_children + minor_child_exemptions = 0 else: claimable_exemptions = exemptions + minor_child_exemptions = minor_children # Form N-311 counts only people who cannot be claimed as a dependent - # by another taxpayer. A return on which a filer can be claimed claims - # no dependents (IRS Publication 501), so it counts only the filers - # who cannot be claimed. + # by another taxpayer. A return on which a filer can be claimed + # generally claims no dependents (IRS Publication 501; its exception + # for a claimer who files only for a refund is not modeled), so it + # counts the filers who cannot be claimed and, through HRS + # 235-55.85(c), their minor children receiving public support. dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) independent_filers = tax_unit( "head_spouse_count_not_dependent_elsewhere", period ) + mixed_return_exemptions = independent_filers + where( + independent_filers > 0, minor_child_exemptions, 0 + ) qualified_exemptions = where( - dependent_filer, independent_filers, claimable_exemptions + dependent_filer, mixed_return_exemptions, claimable_exemptions ) return qualified_exemptions * amount_per_exemption diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py index 3a229d50ebb..734e6305b38 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_act_115_rebate.py @@ -43,9 +43,10 @@ def formula(tax_unit, period, parameters): # not a qualifying resident taxpayer, and each qualifying resident # taxpayer claims the refund for the exemptions they are entitled to. # A filer who can be claimed has no exemption (HRS 235-54(a)), and a - # return on which a filer can be claimed claims no dependents (IRS - # Publication 501), so such a return counts only the filers who - # cannot be claimed. + # return on which a filer can be claimed generally claims no dependents + # (IRS Publication 501; its exception for a claimer who files only for + # a refund is not modeled), so such a return counts only the filers + # who cannot be claimed. dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) independent_filers = tax_unit( "head_spouse_count_not_dependent_elsewhere", period diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py index 81a87e2876e..f60088206d8 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_low_income_household_renters_tax_credit.py @@ -19,8 +19,9 @@ def formula(tax_unit, period, parameters): # Schedule X lines 8-10 count only people who cannot be claimed as a # dependent by another taxpayer, with an extra exemption for each # such filer aged 65 or over. A return on which a filer can be - # claimed claims no dependents (IRS Publication 501), so it counts - # only the filers who cannot be claimed. + # claimed generally claims no dependents (IRS Publication 501; its + # exception for a claimer who files only for a refund is not modeled), + # so it counts only the filers who cannot be claimed. aged_head = (tax_unit("age_head", period) >= p.aged_age_threshold) & ~tax_unit( "head_is_dependent_elsewhere", period ) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py index 70a783508dd..ee104646ba4 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py @@ -37,8 +37,13 @@ def formula(tax_unit, period, parameters): is_dependent_on_another_return = tax_unit( "head_or_spouse_is_dependent_elsewhere", period ) + # Worksheet earned income: wages and self-employment income minus the + # deductible part of self-employment tax (Schedule 1, line 15). + worksheet_earned_income = max_( + tax_unit_earned_income - tax_unit("self_employment_tax_ald", period), 0 + ) dependent_standard_deduction = min_( - max_(p.threshold.dependent, tax_unit_earned_income), standard_deduction + max_(p.threshold.dependent, worksheet_earned_income), standard_deduction ) applicable_standard_deduction = where( is_dependent_on_another_return, diff --git a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py index 6d8e7aac8dd..cf13c6de8ea 100644 --- a/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py +++ b/policyengine_us/variables/gov/states/me/tax/income/credits/fairness/sales_tax_fairness_credit/me_sales_tax_fairness_credit_eligible.py @@ -15,9 +15,9 @@ class me_sales_tax_fairness_credit_eligible(Variable): def formula(tax_unit, period, parameters): # 36 MRSA 5213-A(6)(C) excludes individuals who may be claimed as a # dependent on another return, and subsection 2 allows each resident - # individual the base credit for the return's filing status. On a - # joint return where only one spouse can be claimed, the other spouse - # still qualifies for the joint credit. + # individual the base credit for the return's filing status. Neither + # settles a joint return where only one spouse can be claimed; we read + # them as letting the other spouse qualify for the joint credit. every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py b/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py index 1c77fb504ce..1940d6d1af8 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/exemptions/mi_exemptions.py @@ -35,11 +35,13 @@ def formula(tax_unit, period, parameters): # MCL 206.30(4): an individual who can be claimed as a dependent on # another return gets no personal exemption but subtracts $1,500. - # This applies to each spouse separately, so on a joint return where - # only one spouse can be claimed, the other keeps their own personal - # exemption (and any stillbirth exemption). A joint return on which - # either spouse can be claimed claims no dependents (IRS Publication - # 501, "Dependent Taxpayer Test"). + # Treasury's instructions do not address a joint return where only + # one spouse can be claimed; since subsection (4) speaks of an + # individual, we apply it to each spouse, so the other keeps their own + # personal exemption (and any stillbirth exemption). A joint return on + # which either spouse can be claimed generally claims no dependents + # (IRS Publication 501, "Dependent Taxpayer Test"; its exception for a + # claimer who files only for a refund is not modeled). filers = add(tax_unit, period, ["is_tax_unit_head_or_spouse"]) independent_filers = tax_unit( "head_spouse_count_not_dependent_elsewhere", period diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py index 27585ed2568..1ee85c8bfae 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py @@ -18,15 +18,21 @@ def formula(tax_unit, period, parameters): p = pcredits.unreimbursed_medical_care_expense age = person("age", period) medical_expense = tax_unit("itemized_medical_expenses", period) - age_eligible = tax_unit.any(age >= p.age_eligibility) + aged = age >= p.age_eligibility expense_eligible = medical_expense >= p.min_expenses - # The rebate and credit schedule bars a filer who is a dependent of - # another taxpayer, but "If you are a dependent with a spouse who was - # not a dependent of another taxpayer, your spouse may still qualify - # to claim rebates or credits" (PIT-RC instructions). So only a return - # on which every filer is a dependent elsewhere is barred. - every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) - eligible = age_eligible & expense_eligible & ~every_filer_dependent + # NMSA 7-2-18.13(A) allows the credit to a taxpayer "who is sixty-five + # years of age or older and who is not a dependent of another + # taxpayer", and the PIT-RC instructions let a spouse who is not a + # dependent claim it when the other spouse is. So when a filer is a + # dependent elsewhere, a filer who is not must be 65 or older. + filer = person("is_tax_unit_head_or_spouse", period) + claimed = person("claimed_as_dependent_on_another_return", period) + independent_aged_filer = tax_unit.any(filer & ~claimed & aged) + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + age_eligible = where( + dependent_filer, independent_aged_filer, tax_unit.any(aged) + ) + eligible = age_eligible & expense_eligible # exemption is halved for married filing separately filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py index 389227abe70..c083077aebd 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents_eligible.py @@ -14,10 +14,11 @@ class nm_deduction_for_certain_dependents_eligible(Variable): def formula(tax_unit, period, parameters): # The deduction uses the same "not a dependent of another individual" - # test as the rebates and credits on Schedule PIT-RC, whose - # instructions let a spouse who is not a dependent still qualify. So - # only a return on which every filer is a dependent elsewhere is - # barred. + # test as the rebates and credits on Schedule PIT-RC, and the law does + # not settle a joint return where only one spouse is a dependent. We + # apply the PIT-RC instructions' reading, which lets a spouse who is + # not a dependent still qualify, so only a return on which every + # filer is a dependent elsewhere is barred. every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) # deduction does not apply if an exemption under IRS 151 is claimed; # IRC 151 refers to the federal personal exemption diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py index c49035340e2..0dab3d96509 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_2021_income_rebate.py @@ -14,11 +14,14 @@ class nm_2021_income_rebate(Variable): defined_for = StateCode.NM def formula(tax_unit, period, parameters): - # The rebate and credit schedule bars a filer who is a dependent of - # another taxpayer, but "If you are a dependent with a spouse who was - # not a dependent of another taxpayer, your spouse may still qualify - # to claim rebates or credits" (PIT-RC instructions). So only a return - # on which every filer is a dependent elsewhere is barred. + # The rebate goes to "a resident ... who is not a dependent of another + # individual", and the law does not settle a joint return where only + # one spouse is. For the same phrase on its rebate and credit schedule, + # TRD says "If you are a dependent with a spouse who was not a + # dependent of another taxpayer, your spouse may still qualify to + # claim rebates or credits" (PIT-RC instructions); we apply that + # reading, so only a return on which every filer is a dependent + # elsewhere is barred. every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) agi = tax_unit("adjusted_gross_income", period) filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py index f574c92dbab..78493990ef0 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_additional_2021_income_rebate.py @@ -14,11 +14,14 @@ class nm_additional_2021_income_rebate(Variable): defined_for = StateCode.NM def formula(tax_unit, period, parameters): - # The rebate and credit schedule bars a filer who is a dependent of - # another taxpayer, but "If you are a dependent with a spouse who was - # not a dependent of another taxpayer, your spouse may still qualify - # to claim rebates or credits" (PIT-RC instructions). So only a return - # on which every filer is a dependent elsewhere is barred. + # The rebate goes to "a resident ... who is not a dependent of another + # individual", and the law does not settle a joint return where only + # one spouse is. For the same phrase on its rebate and credit schedule, + # TRD says "If you are a dependent with a spouse who was not a + # dependent of another taxpayer, your spouse may still qualify to + # claim rebates or credits" (PIT-RC instructions); we apply that + # reading, so only a return on which every filer is a dependent + # elsewhere is barred. every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py index bf8f1a88d52..cd82e30a49b 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/2021_rebate/nm_supplemental_2021_income_rebate.py @@ -14,11 +14,14 @@ class nm_supplemental_2021_income_rebate(Variable): defined_for = StateCode.NM def formula(tax_unit, period, parameters): - # The rebate and credit schedule bars a filer who is a dependent of - # another taxpayer, but "If you are a dependent with a spouse who was - # not a dependent of another taxpayer, your spouse may still qualify - # to claim rebates or credits" (PIT-RC instructions). So only a return - # on which every filer is a dependent elsewhere is barred. + # The rebate goes to "a resident ... who is not a dependent of another + # individual", and the law does not settle a joint return where only + # one spouse is. For the same phrase on its rebate and credit schedule, + # TRD says "If you are a dependent with a spouse who was not a + # dependent of another taxpayer, your spouse may still qualify to + # claim rebates or credits" (PIT-RC instructions); we apply that + # reading, so only a return on which every filer is a dependent + # elsewhere is barred. every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.nm.tax.income.rebates["2021_income"] filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py b/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py index 7b746711ad1..58934026c25 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py +++ b/policyengine_us/variables/gov/states/va/tax/income/credits/low_income_credit/va_low_income_tax_credit_eligible.py @@ -18,10 +18,11 @@ def formula(tax_unit, period, parameters): program_eligible = add(tax_unit, period, p.ineligible_programs) == 0 # Criteria 2: Not qualified if a filer is claimed as a dependent on - # another taxpayer's return. Va. Code 58.1-339.8 bars the credit - # against the tax of a dependent of another individual or couple; a - # joint return carries both spouses' tax, so either spouse being - # claimed bars it. + # another taxpayer's return. Neither Va. Code 58.1-339.8 nor the + # instructions settle a joint return where only one spouse is + # claimed. We read the statute's bar on the credit "against such tax + # of a dependent" as reaching a joint return, which carries both + # spouses' tax, so either spouse being claimed bars it. filer_is_dependent_elsewhere = tax_unit( "head_or_spouse_is_dependent_elsewhere", period ) From e8386234d207195fbcc487daa650d584e25a83e2 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 10:00:44 -0400 Subject: [PATCH 4/9] Allow a cent of rounding in the Hawaii self-employed joint pair Co-Authored-By: Claude Opus 5.5 --- .../gov/states/hi/tax/income/deductions/hi_deductions.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml index 584d987cbe7..56eb95f2a84 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/hi_deductions.yaml @@ -221,6 +221,7 @@ - name: 2025 joint return with a claimable self-employed spouse uses earned income net of the self-employment tax deduction (the claimable spouse is the head) period: 2025 + absolute_error_margin: 0.01 input: people: person_a: @@ -249,6 +250,7 @@ - name: 2025 joint return with a claimable self-employed spouse uses earned income net of the self-employment tax deduction (the claimable spouse is the spouse) period: 2025 + absolute_error_margin: 0.01 input: people: person_b: From 41fb4a55471a77d8a4fd7508d5cd177d802557fd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 10:25:58 -0400 Subject: [PATCH 5/9] Address review round 2: worksheet earnings, minors' own status, input meaning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The federal and Oregon dependent worksheets use earned income net of the deductible part of self-employment tax, as Hawaii's now does. - Hawaii's food/excise minor-child count excludes children who can be claimed by another taxpayer and the filers themselves (Form N-311 line 3). - claimed_as_dependent_on_another_return documents what it stands for: a §151 deduction for the person is allowable to another taxpayer, so a person whose would-be claimer files only for a refund is not one (Publications 501 and 596); rules on actually being claimed read it too. - Hawaii's renters comment labels the mixed joint reading as an inference; the Hawaii dependent threshold cites HRS 235-2.4(a)(3) on its page. Co-Authored-By: Claude Opus 5.5 --- .../itemized/threshold/dependent.yaml | 2 +- ...t_elsewhere_head_spouse_swap_invariance.py | 6 +- .../eitc_demographic_eligible.yaml | 21 +++ .../standard/basic_standard_deduction.yaml | 22 +++ .../food_excise/hi_food_excise_credit.yaml | 134 ++++++++++++++++++ .../standard/or_standard_deduction.yaml | 22 +++ .../basic_standard_deduction.py | 10 +- ...hi_food_excise_credit_minor_child_count.py | 9 +- ...r_low_income_household_renters_eligible.py | 8 +- .../deductions/or_standard_deduction.py | 10 +- .../claimed_as_dependent_on_another_return.py | 10 +- 11 files changed, 241 insertions(+), 13 deletions(-) diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml index 3ff2ac9a451..36300fde6de 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/threshold/dependent.yaml @@ -16,6 +16,6 @@ metadata: - title: 2018 Hawaii Resident Income Tax Instructions Itemized Deductions Worksheet href: https://files.hawaii.gov/tax/forms/2018/n11ins.pdf#page=38 - title: Hawaii Income Tax Law, Chapter 235, Section 235-2.4, (a)(3) - href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=10 + href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=11 - title: 2025 Hawaii N-11 Instructions Itemized Deductions href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=32 diff --git a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py index d64c56ff5ba..4e681240eb5 100644 --- a/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py +++ b/policyengine_us/tests/core/test_dependent_elsewhere_head_spouse_swap_invariance.py @@ -18,9 +18,9 @@ - Each spouse separately: Michigan's exemptions and Hawaii's food/excise, renters and Act 115 credits count only the filers who cannot be claimed. - Only when every filer is a dependent: New Mexico's rebates, credits and - dependents deduction, and Maine's sales tax fairness credit. New Mexico's - medical care credit also needs a filer who is both 65 or older and not a - dependent. + dependents deduction, and Maine's sales tax fairness credit. When a filer + is a dependent elsewhere, New Mexico's medical care credit also needs a + filer who is both 65 or older and not a dependent. - Never on a joint return: Missouri's own stop in its working family credit, since such a couple files as married filing combined. diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml index 80b379d010b..b922b61cb47 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/eitc_demographic_eligible.yaml @@ -257,3 +257,24 @@ # The dependent condition in IRC 32(c)(1)(A)(ii)(III) applies only without a # qualifying child. eitc_demographic_eligible: true + +- name: 2025 childless single filer whose parent files only to claim a refund is not a dependent + period: 2025 + input: + people: + head: + age: 26 + employment_income: 2_000 + claimed_as_dependent_on_another_return: false + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: CA + output: + # Publication 596 (2025, p.18): the credit is allowed when the person who can claim + # you files only to claim a refund; such a person is not a dependent, so the + # input is false. + eitc_demographic_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml index b9a821293a5..4099214070c 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml @@ -263,3 +263,25 @@ output: # Joint earned income $16,000 + $450 = $16,450, below the $31,500 joint amount. basic_standard_deduction: 16_450 + +- name: 2025 dependent filer with self-employment income uses earned income net of the self-employment tax deduction + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + self_employment_income: 3_000 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + households: + household: + members: [head] + state_code: CA + output: + # Worksheet earned income subtracts Schedule 1 line 15: + # $3,000 - $3,000 x 0.9235 x 15.3% / 2 = $2,788.06; plus $450 = $3,238.06. + basic_standard_deduction: 3238.06 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml index eaf2ffad6cd..260651381eb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit.yaml @@ -461,3 +461,137 @@ output: # From 2023 the minor counts as a qualified exemption: 2 x $220. hi_food_excise_credit: 440 + +- name: 2022 joint return where one spouse and the publicly supported minor can be claimed elsewhere (the claimable spouse is the head) + period: 2022 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_a, person_b, child_1] + state_code: HI + output: + # Form N-311 line 3 lists only minors who cannot be claimed by another taxpayer, + # so only the spouse who cannot be claimed counts: $110. + hi_food_excise_credit: 110 + +- name: 2022 joint return where one spouse and the publicly supported minor can be claimed elsewhere (the claimable spouse is the spouse) + period: 2022 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_b, person_a, child_1] + state_code: HI + output: + # Form N-311 line 3 lists only minors who cannot be claimed by another taxpayer, + # so only the spouse who cannot be claimed counts: $110. + hi_food_excise_credit: 110 + +- name: 2025 joint return where one spouse and the publicly supported minor can be claimed elsewhere (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a, person_b, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_a, person_b, child_1] + state_code: HI + output: + # Only the spouse who cannot be claimed counts: 1 x $220. + hi_food_excise_credit: 220 + +- name: 2025 joint return where one spouse and the publicly supported minor can be claimed elsewhere (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + is_tax_unit_head: false + is_tax_unit_spouse: false + hi_food_excise_credit_child_receiving_public_support: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_b, person_a, child_1] + filing_status: JOINT + adjusted_gross_income: 4_000 + households: + household: + members: [person_b, person_a, child_1] + state_code: HI + output: + # Only the spouse who cannot be claimed counts: 1 x $220. + hi_food_excise_credit: 220 diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml index 7818ce120d2..c040dee6619 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/deductions/standard/or_standard_deduction.yaml @@ -191,3 +191,25 @@ output: # max($1,350, $3,000 + $450) = $3,450, below the $5,670 joint amount. or_standard_deduction: 3_450 + +- name: 2025 dependent filer with self-employment income uses earned income net of the self-employment tax deduction + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + self_employment_income: 1_500 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + households: + household: + members: [head] + state_code: OR + output: + # $1,500 - $1,500 x 0.9235 x 15.3% / 2 = $1,394.03; plus $450 = $1,844.03, + # above the $1,350 minimum and below the $2,835 single amount. + or_standard_deduction: 1844.03 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py index e8575cdcc7e..952da61a05f 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/standard_deduction/basic_standard_deduction.py @@ -25,11 +25,17 @@ def formula(tax_unit, period, parameters): # can be claimed. dependent_elsewhere = tax_unit("head_or_spouse_is_dependent_elsewhere", period) standard_deduction = p.amount[filing_status] + # Worksheet earned income: wages and self-employment income minus the + # deductible part of self-employment tax (Schedule 1, line 15). + worksheet_earned_income = max_( + tax_unit("tax_unit_earned_income", period) + - tax_unit("self_employment_tax_ald", period), + 0, + ) standard_deduction_if_dependent = min_( standard_deduction, max_( - p.dependent.additional_earned_income - + tax_unit("tax_unit_earned_income", period), + p.dependent.additional_earned_income + worksheet_earned_income, p.dependent.amount, ), ) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit_minor_child_count.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit_minor_child_count.py index ba06f9635a3..c4cc41a8d42 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit_minor_child_count.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/food_excise/hi_food_excise_credit_minor_child_count.py @@ -16,6 +16,13 @@ def formula(tax_unit, period, parameters): ) is_child = person("is_child", period) minor = person("age", period) < p.minor_child.age_threshold - eligible_minor_child = is_child & minor & public_support_over_half + # Form N-311 line 3 lists minor children who, among other tests, + # "Cannot be claimed as a dependent by another taxpayer"; the filers + # are listed on line 2. + claimed_elsewhere = person("claimed_as_dependent_on_another_return", period) + filer = person("is_tax_unit_head_or_spouse", period) + eligible_minor_child = ( + is_child & minor & public_support_over_half & ~claimed_elsewhere & ~filer + ) return tax_unit.sum(eligible_minor_child) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py index 61f4c81eaf2..e25924271ee 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/credits/hi_low_income_household_renters/hi_tax_credit_for_low_income_household_renters_eligible.py @@ -15,9 +15,11 @@ class hi_tax_credit_for_low_income_household_renters_eligible(Variable): def formula(tax_unit, period, parameters): # HRS 235-55.7(b) allows the credit to each resident taxpayer who - # cannot be claimed as a dependent. Schedule X stops at line 3 only - # for the first-listed filer; since a couple may list either spouse - # first, a joint return qualifies unless both spouses can be claimed. + # cannot be claimed as a dependent, and Schedule X stops at line 3 if + # "you" can be claimed. Neither settles a joint return where only one + # spouse can be claimed; since the credit and its exemption count are + # per person, we read them as letting the other spouse claim it, so a + # joint return qualifies unless both spouses can be claimed. every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) p = parameters(period).gov.states.hi.tax.income.credits.lihrtc agi = tax_unit("hi_agi", period) diff --git a/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py b/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py index eb3c543e6fa..54d2dc18fbc 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py +++ b/policyengine_us/variables/gov/states/or/tax/income/deductions/or_standard_deduction.py @@ -20,8 +20,14 @@ def formula(tax_unit, period, parameters): # Core deduction based on filing status. filing_status = tax_unit("filing_status", period) initial_core_deduction = p.amount[filing_status] - # Replace if claimable as a dependent. - earned_income = tax_unit("tax_unit_earned_income", period) + # Replace if claimable as a dependent. Earned income is figured as in + # the federal worksheet, net of the deductible part of self-employment + # tax (Schedule 1, line 15). + earned_income = max_( + tax_unit("tax_unit_earned_income", period) + - tax_unit("self_employment_tax_ald", period), + 0, + ) claimable_dep_floor = p.claimable_as_dependent.min claimable_dep_earned_amount = ( earned_income + p.claimable_as_dependent.earned_income_addition diff --git a/policyengine_us/variables/household/demographic/person/claimed_as_dependent_on_another_return.py b/policyengine_us/variables/household/demographic/person/claimed_as_dependent_on_another_return.py index a62ae0c4703..f941fcb3254 100644 --- a/policyengine_us/variables/household/demographic/person/claimed_as_dependent_on_another_return.py +++ b/policyengine_us/variables/household/demographic/person/claimed_as_dependent_on_another_return.py @@ -6,4 +6,12 @@ class claimed_as_dependent_on_another_return(Variable): entity = Person definition_period = YEAR label = "Is claimed as a dependent elsewhere" - documentation = "Whether the person is claimed as a dependent in another tax unit." + documentation = ( + "Whether another taxpayer can claim the person as a dependent, that " + "is, a deduction under IRC section 151 for the person is allowable to " + "another taxpayer, whether or not it is claimed. A person whose " + "would-be claimer is not required to file and files only to claim a " + "refund is not such a dependent (IRS Publications 501 and 596). Rules " + "that turn on actually being claimed also read this input; the model " + "has no separate input for that." + ) From 436805e3e0c3d386e66016e54f69967d37be4a1b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 10:27:15 -0400 Subject: [PATCH 6/9] Merge the Hawaii deductions references after syncing main Co-Authored-By: Claude Opus 5.5 --- .../gov/states/hi/tax/income/deductions/hi_deductions.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py index 646fee7433c..fe50a345a3c 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/hi_deductions.py @@ -6,12 +6,10 @@ class hi_deductions(Variable): entity = TaxUnit label = "Hawaii deductions" unit = USD - reference = ( - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=15", # Itemized Deduction - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=20", # Standard Deduction - ) reference = ( "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=11", + # PDF pages 15 (itemized deduction), 20 (standard deduction) + "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=15", # PDF pages 15, 20 "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=15", ) From 23c6229b2004fdc2a8a370c94399eae50dcaccfe Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 12:03:39 -0400 Subject: [PATCH 7/9] Apply the federal rules for a filer who can be claimed as a dependent A filer who can be claimed on another return is treated as having no dependents (IRC 152(b)(1); on a joint return either spouse counts) and has no personal exemption (IRC 151(d)(2)); one who is claimed gets no education credits (25A(g)(3)), student loan interest deduction (221(c)) or recovery rebate (6428(d)(2), 6428A(d)(2), 6428B(c)(2)). The model read none of these. Now the child tax credit and credit for other dependents, the under-13 child and dependent care qualifying person, exemptions_count and its RI, DE, OH, OR, WI, HI and NM readers, the recovery rebates, the education credits and the student loan interest deduction apply them symmetrically for joint returns. Georgia keeps its own counts; the HI, WI and Treasury exemption reforms follow the corrected count. Co-Authored-By: Claude Opus 5.5 --- changelog.d/claimable-filer-federal.fixed.md | 1 + .../hi_dependent_exemption_reform.py | 24 ++- .../wi_dependent_exemption_reform.py | 23 ++- .../treasury/repeal_dependent_exemptions.py | 10 +- ...test_claimable_filer_federal_invariants.py | 153 ++++++++++++++++++ .../irs/credits/cdcc/is_cdcc_eligible.yaml | 96 +++++++++++ .../ctc/maximum/ctc_qualifying_child.yaml | 103 ++++++++++++ .../ctc_adult_individual_maximum.yaml | 93 +++++++++++ ...gible_for_american_opportunity_credit.yaml | 130 +++++++++++++++ ...eligible_for_lifetime_learning_credit.yaml | 107 ++++++++++++ .../recovery_rebate_credit.yaml | 112 +++++++++++++ .../student_loan_interest_ald_eligible.yaml | 69 ++++++++ .../exemptions/exemptions_count.yaml | 133 +++++++++++++++ .../personal/de_personal_credit_indv.yaml | 51 ++++++ .../credits/personal/de_personal_credits.yaml | 55 +++++++ .../states/ga/tax/income/credits/ga_ctc.yaml | 30 +++- .../income/credits/ga_low_income_credit.yaml | 16 +- .../nm_low_and_middle_income_exemption.yaml | 99 ++++++++++++ ...m_low_income_comprehensive_tax_rebate.yaml | 80 +++++++++ ...e_comprehensive_tax_rebate_exemptions.yaml | 101 ++++++++++++ .../oh_personal_exemptions_eligible.yaml | 54 +++++++ .../tax/income/exemption/ri_exemptions.yaml | 79 +++++++++ .../wi/tax/income/wi_base_exemption.yaml | 76 +++++++++ .../gov/irs/credits/cdcc/cdcc_eligible.py | 17 +- .../ctc/maximum/ctc_qualifying_child.py | 18 ++- .../ctc_adult_individual_maximum.py | 8 + ...ligible_for_american_opportunity_credit.py | 6 + ...s_eligible_for_lifetime_learning_credit.py | 6 + .../rrc_adult_count_with_valid_ssn.py | 8 +- .../rrc_arpa_dependents_with_valid_ssn.py | 8 +- .../student_loan_interest_ald_eligible.py | 8 +- .../exemptions/exemptions_count.py | 28 +++- .../personal/de_personal_credit_indv.py | 25 ++- .../income/credits/ctc/ga_ctc_potential.py | 12 +- .../credits/ga_low_income_credit_potential.py | 5 +- .../nm_low_and_middle_income_exemption.py | 3 + .../nm_low_income_comprehensive_tax_rebate.py | 8 +- ...ome_comprehensive_tax_rebate_exemptions.py | 6 + .../oh_personal_exemptions_eligible_person.py | 9 +- 39 files changed, 1820 insertions(+), 50 deletions(-) create mode 100644 changelog.d/claimable-filer-federal.fixed.md create mode 100644 policyengine_us/tests/core/test_claimable_filer_federal_invariants.py create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/exemptions/exemptions_count.yaml diff --git a/changelog.d/claimable-filer-federal.fixed.md b/changelog.d/claimable-filer-federal.fixed.md new file mode 100644 index 00000000000..545d474e1a6 --- /dev/null +++ b/changelog.d/claimable-filer-federal.fixed.md @@ -0,0 +1 @@ +Apply the federal rules for a filer who can be claimed as a dependent on another return: no child tax credit, credit for other dependents or under-13 care qualifying child on such a return (IRC 24, 21, 152(b)(1)), no personal exemption for that filer (IRC 151(d)(2)) in the exemption count Rhode Island, Delaware, Ohio, Wisconsin, Oregon, Hawaii and New Mexico read, no recovery rebate for that filer, and no education credits or student loan interest deduction on a return with a claimed filer (IRC 25A(g)(3), 221(c)). diff --git a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py index c2ec738773b..3349f9a9f31 100644 --- a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py @@ -15,7 +15,11 @@ def formula(tax_unit, period, parameters): person = tax_unit.members age = person("age", period) - is_dependent = person("is_tax_unit_dependent", period) + # A return on which the filer (or, if joint, either spouse) can be + # claimed as a dependent has no dependents (IRC 152(b)(1)). + is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) # Apply age limit if in effect if p.age_limit.in_effect: @@ -49,7 +53,11 @@ class hi_older_dependents_count(Variable): def formula(tax_unit, period, parameters): person = tax_unit.members - is_dependent = person("is_tax_unit_dependent", period) + # A return on which the filer (or, if joint, either spouse) can be + # claimed as a dependent has no dependents (IRC 152(b)(1)). + is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) total_dependents = tax_unit.sum(is_dependent) eligible_dependent_exemptions = tax_unit( "hi_eligible_dependents_count", period @@ -66,14 +74,16 @@ class hi_regular_exemptions(Variable): defined_for = StateCode.HI def formula(tax_unit, period, parameters): - exemptions_count = tax_unit("exemptions_count", period) p = parameters(period).gov.states.hi.tax.income.exemptions - # Personal exemptions exclude the dependent portion. Over-age - # dependents fall back to the personal count. - dependents = tax_unit("tax_unit_dependents", period) + # Personal exemptions exclude the dependent portion: the filers' + # own exemptions (none for a filer who can be claimed as a + # dependent). Over-age dependents fall back to the personal count. + own_exemptions = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period + ) older_dependents = tax_unit("hi_older_dependents_count", period) - personal_count = exemptions_count - dependents + older_dependents + personal_count = own_exemptions + older_dependents # Aged heads and spouses get an extra base exemption (preserved # exactly as the baseline computes it). diff --git a/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py b/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py index cf44d6a86f2..85e40eefe7f 100644 --- a/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py @@ -15,7 +15,11 @@ def formula(tax_unit, period, parameters): person = tax_unit.members age = person("age", period) - is_dependent = person("is_tax_unit_dependent", period) + # A return on which the filer (or, if joint, either spouse) can be + # claimed as a dependent has no dependents (IRC 152(b)(1)). + is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) # Apply age limit if in effect if p.age_limit.in_effect: @@ -49,7 +53,11 @@ class wi_older_dependents_count(Variable): def formula(tax_unit, period, parameters): person = tax_unit.members - is_dependent = person("is_tax_unit_dependent", period) + # A return on which the filer (or, if joint, either spouse) can be + # claimed as a dependent has no dependents (IRC 152(b)(1)). + is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) total_dependents = tax_unit.sum(is_dependent) eligible_dependent_exemptions = tax_unit( "wi_eligible_dependents_count", period @@ -68,12 +76,13 @@ def formula(tax_unit, period, parameters): p_base = parameters(period).gov.states.wi.tax.income # Personal exemptions exclude the dependent portion. - total_exemptions_count = tax_unit("exemptions_count", period) - dependents = tax_unit("tax_unit_dependents", period) - older_dependents = tax_unit("wi_older_dependents_count", period) - personal_exemptions_count = ( - total_exemptions_count - dependents + older_dependents + # These are the filers' own exemptions (none for a filer who can + # be claimed as a dependent) plus over-age dependents. + own_exemptions = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period ) + older_dependents = tax_unit("wi_older_dependents_count", period) + personal_exemptions_count = own_exemptions + older_dependents personal_exemption_amount = ( personal_exemptions_count * p_base.exemption.base ) diff --git a/policyengine_us/reforms/treasury/repeal_dependent_exemptions.py b/policyengine_us/reforms/treasury/repeal_dependent_exemptions.py index ab54afe5e89..fbfd6b94c1e 100644 --- a/policyengine_us/reforms/treasury/repeal_dependent_exemptions.py +++ b/policyengine_us/reforms/treasury/repeal_dependent_exemptions.py @@ -13,7 +13,15 @@ class exemptions_count(Variable): def formula(tax_unit, period, parameters): total_unit_size = tax_unit("tax_unit_size", period) dependents = tax_unit("tax_unit_dependents", period) - return total_unit_size - dependents + # Only the filers' own exemptions remain; a filer who can be + # claimed as a dependent has none (IRC 151(d)(2)). + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + independent_filers = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period + ) + return where( + dependent_filer, independent_filers, total_unit_size - dependents + ) class reform(Reform): def apply(self): diff --git a/policyengine_us/tests/core/test_claimable_filer_federal_invariants.py b/policyengine_us/tests/core/test_claimable_filer_federal_invariants.py new file mode 100644 index 00000000000..58eb58887b4 --- /dev/null +++ b/policyengine_us/tests/core/test_claimable_filer_federal_invariants.py @@ -0,0 +1,153 @@ +"""Federal rules for a filer who can be claimed as a dependent elsewhere. + +A filer who can be claimed as a dependent on another return is treated as +having no dependents (IRC 152(b)(1); on a joint return, if either spouse can be +claimed, "you and your spouse can't claim any dependents", Publication 501) and +has no personal exemption (IRC 151(d)(2)). So: + +- `exemptions_count` counts only the filers who cannot be claimed, plus the + dependents when no filer can be claimed; +- `ctc_qualifying_child` and the credit for other dependents are zero on a + return with a claimable filer; +- a child under 13 is not a child and dependent care qualifying person on such + a return (a disabled person still is, IRC 21(b)(1)(B)); +- recovery rebates leave out a claimable filer and, on such a return, the + dependents. + +For couples drawn by Hypothesis and a seeded population, with either, both or +neither spouse claimed, in 2021-2026: + +1. Swap invariance: each output is the same under either head/spouse + labelling. +2. Monotonicity: marking another filer as claimed never raises any of the + amounts or counts below. +3. Identities: `exemptions_count` equals the filers who cannot be claimed plus + the dependents when no filer can be claimed, and a return with a claimable + filer has no CTC-qualifying child and no credit for other dependents. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.tests.core.test_dependent_elsewhere_head_spouse_swap_invariance import ( + CLAIM_PATTERNS, + TOLERANCE, + YEARS, + _calc, + _crafted_cases, + _monotone_variants, + _seeded_couples, + _situation, + _swap_variants, + adults, + dependents, +) + +# Texas has no income tax; the others read the federal exemption count. +STATES = ["TX", "OR", "HI", "NM", "GA", "RI", "DE", "OH", "WI"] +OUTPUTS = [ + "income_tax", + "ctc_maximum", + "ctc_qualifying_children", + "capped_count_cdcc_eligible", + "exemptions_count", + "rrc_arpa", +] +# Amounts that marking another filer as claimed must never raise. +MONOTONE = [ + "ctc_maximum", + "ctc_qualifying_children", + "capped_count_cdcc_eligible", + "exemptions_count", + "rrc_arpa", +] + + +@st.composite +def couples(draw): + return { + "state": draw(st.sampled_from(STATES)), + "adults": [draw(adults()), draw(adults())], + "dependents": draw(st.lists(dependents(), max_size=2)), + "claimed": draw(st.sampled_from(CLAIM_PATTERNS)), + } + + +def check_swap(units, year, outputs, identities=None): + """Each output is the same under either head/spouse labelling.""" + sim = Simulation(situation=_situation(units, year, _swap_variants)) + for name in outputs: + values = _calc(sim, name, year) + assert np.isfinite(values).all(), f"{name} in {year} is not finite" + drawn, swapped = values[0::2], values[1::2] + differs = ~(np.abs(swapped - drawn) <= TOLERANCE) + assert not differs.any(), ( + f"{name} in {year} changes when the head and spouse labels are " + "exchanged: " + + ", ".join( + f"{units[i]['state']} claimed={units[i]['claimed']} " + f"{drawn[i]:.2f} -> {swapped[i]:.2f}" + for i in np.flatnonzero(differs)[:10] + ) + ) + if identities is not None: + identities(sim, year) + + +def check_monotone(units, year, outputs, identities=None): + """Marking another filer as claimed never raises an output.""" + sim = Simulation(situation=_situation(units, year, _monotone_variants)) + for name in outputs: + values = _calc(sim, name, year).reshape(-1, 4) + nobody, first, second, both = values.T + for more, fewer, label in ( + (first, nobody, "first adult"), + (second, nobody, "second adult"), + (both, first, "both after first"), + (both, second, "both after second"), + ): + rises = more > fewer + TOLERANCE + assert not rises.any(), ( + f"{name} in {year} rises when the {label} is claimed " + "elsewhere: " + + ", ".join( + f"{units[i]['state']} {fewer[i]:.2f} -> {more[i]:.2f}" + for i in np.flatnonzero(rises)[:10] + ) + ) + if identities is not None: + identities(sim, year) + + +def _federal_identities(sim, year): + claimed_filer = _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) > 0 + independent = _calc(sim, "head_spouse_count_not_dependent_elsewhere", year) + dependents_count = _calc(sim, "tax_unit_count_dependents", year) + expected = independent + np.where(claimed_filer, 0, dependents_count) + np.testing.assert_array_equal(_calc(sim, "exemptions_count", year), expected) + assert not _calc(sim, "ctc_qualifying_children", year)[claimed_filer].any() + odc = np.asarray( + sim.calculate("ctc_adult_individual_maximum", year, map_to="tax_unit") + ) + assert not odc[claimed_filer].any() + + +@settings( + max_examples=4, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.sampled_from(YEARS), st.lists(couples(), min_size=6, max_size=15)) +def test_claimable_filer_federal_rules_are_label_free(year, units): + check_swap(units, year, OUTPUTS, _federal_identities) + + +@pytest.mark.parametrize("year", YEARS) +def test_claimable_filer_federal_rules_seeded_population(year): + units = _seeded_couples(STATES, per_state=2) + _crafted_cases(STATES) + check_swap(units, year, OUTPUTS, _federal_identities) + check_monotone(units, year, MONOTONE, _federal_identities) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/is_cdcc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/is_cdcc_eligible.yaml index 9a4d8616b69..1c590c6f3cf 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/is_cdcc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/cdcc/is_cdcc_eligible.yaml @@ -54,3 +54,99 @@ is_tax_unit_spouse: false output: is_cdcc_eligible: true + +- name: 2025 child under 13 is not a qualifying person when the single filer can be claimed as a dependent + period: 2025 + input: + people: + parent: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 5 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + # Publication 503: "Your qualifying child who is your dependent and who + # was under age 13"; IRC 152(b)(1). + is_cdcc_eligible: [false, false] + +- name: 2025 joint return with one claimable spouse has no under-13 qualifying person (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + is_cdcc_eligible: [false, false, false] + +- name: 2025 joint return with one claimable spouse has no under-13 qualifying person (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + is_cdcc_eligible: [false, false, false] + +- name: 2025 disabled person still qualifies when the filer can be claimed as a dependent + period: 2025 + input: + people: + filer: + age: 30 + claimed_as_dependent_on_another_return: true + relative: + age: 60 + is_incapable_of_self_care: true + tax_units: + tax_unit: + members: [filer, relative] + households: + household: + members: [filer, relative] + state_code: TX + output: + # IRC 21(b)(1)(B) determines dependency "without regard to subsections + # (b)(1), (b)(2), and (d)(1)(B)" of section 152; Publication 503 item + # 3(b)(iii). + is_cdcc_eligible: [false, true] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/ctc_qualifying_child.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/ctc_qualifying_child.yaml index 3f4a66e3619..0a40c086281 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/ctc_qualifying_child.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/ctc_qualifying_child.yaml @@ -56,3 +56,106 @@ members: [adult, child, child2] output: ctc_qualifying_child: [False, True, False] + +- name: 2025 single filer who can be claimed as a dependent has no CTC-qualifying child + period: 2025 + input: + people: + parent: + age: 20 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + child: + age: 5 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + # IRC 24(a), 152(b)(1); Publication 501, Dependent Taxpayer Test. + ctc_qualifying_child: [false, false] + ctc_maximum: 0 + +- name: 2025 joint return with one claimable spouse has no CTC-qualifying child (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # "If you are filing a joint return and your spouse can be claimed as a + # dependent by another taxpayer, you and your spouse can't claim any + # dependents on your joint return" (Publication 501). + ctc_qualifying_child: [false, false, false] + ctc_maximum: 0 + +- name: 2025 joint return with one claimable spouse has no CTC-qualifying child (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + ctc_qualifying_child: [false, false, false] + ctc_maximum: 0 + +- name: 2025 joint return with neither spouse claimable keeps the CTC-qualifying child + period: 2025 + input: + people: + person_a: + age: 20 + person_b: + age: 20 + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + ctc_qualifying_child: [false, false, true] + ctc_maximum: 2_200 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.yaml index fec059bb2d7..9c46915a88e 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.yaml @@ -43,3 +43,96 @@ ctc_child_individual_maximum: 0 output: ctc_adult_individual_maximum: 500 + +- name: 2025 single filer who can be claimed as a dependent gets no credit for other dependents + period: 2025 + input: + people: + filer: + age: 22 + claimed_as_dependent_on_another_return: true + sibling: + age: 18 + tax_units: + tax_unit: + members: [filer, sibling] + households: + household: + members: [filer, sibling] + state_code: TX + output: + # IRC 24(h)(4) "any dependent of the taxpayer"; IRC 152(b)(1). + ctc_adult_individual_maximum: [0, 0] + +- name: 2025 joint return with one claimable spouse gets no credit for other dependents (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 22 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 22 + is_tax_unit_head: false + is_tax_unit_spouse: true + sibling: + age: 18 + tax_units: + tax_unit: + members: [person_a, person_b, sibling] + filing_status: JOINT + households: + household: + members: [person_a, person_b, sibling] + state_code: TX + output: + ctc_adult_individual_maximum: [0, 0, 0] + +- name: 2025 joint return with one claimable spouse gets no credit for other dependents (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 22 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 22 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + sibling: + age: 18 + tax_units: + tax_unit: + members: [person_b, person_a, sibling] + filing_status: JOINT + households: + household: + members: [person_b, person_a, sibling] + state_code: TX + output: + ctc_adult_individual_maximum: [0, 0, 0] + +- name: 2025 joint return with neither spouse claimable gets the credit for other dependents + period: 2025 + input: + people: + person_a: + age: 22 + person_b: + age: 22 + sibling: + age: 18 + tax_units: + tax_unit: + members: [person_a, person_b, sibling] + filing_status: JOINT + households: + household: + members: [person_a, person_b, sibling] + state_code: TX + output: + ctc_adult_individual_maximum: [0, 0, 500] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.yaml index 0d102e18b78..908d3d3c657 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.yaml @@ -177,3 +177,133 @@ has_tin: true output: is_eligible_for_american_opportunity_credit: false + + +- name: 2024 student claimed as a dependent elsewhere cannot claim the credit + period: 2024 + input: + people: + student: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [student] + households: + household: + members: [student] + state_code: TX + output: + # IRC 25A(g)(3); Form 8863: "You're claimed as a dependent on another + # person's tax return". + is_eligible_for_american_opportunity_credit: false + +- name: 2024 joint return with one spouse claimed elsewhere cannot claim the credit for either spouse (the claimable spouse is the head) + period: 2024 + input: + people: + person_a: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # Form 1040 instructions: "or your spouse if filing jointly". + is_eligible_for_american_opportunity_credit: [false, false] + +- name: 2024 joint return with one spouse claimed elsewhere cannot claim the credit for either spouse (the claimable spouse is the spouse) + period: 2024 + input: + people: + person_b: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: TX + output: + is_eligible_for_american_opportunity_credit: [false, false] + +- name: 2024 joint return with neither spouse claimed keeps both students' eligibility + period: 2024 + input: + people: + person_a: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + person_b: + age: 20 + is_pursuing_credential_for_american_opportunity_credit: true + attends_eligible_educational_institution_for_american_opportunity_credit: true + is_enrolled_at_least_half_time_for_american_opportunity_credit: true + has_american_opportunity_credit_1098_t_or_exception: true + has_american_opportunity_credit_institution_ein: true + has_tin: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + is_eligible_for_american_opportunity_credit: [true, true] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/education/lifetime_learning_credit/is_eligible_for_lifetime_learning_credit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/education/lifetime_learning_credit/is_eligible_for_lifetime_learning_credit.yaml index 72ca159a702..4bff0f55a7d 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/education/lifetime_learning_credit/is_eligible_for_lifetime_learning_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/education/lifetime_learning_credit/is_eligible_for_lifetime_learning_credit.yaml @@ -70,3 +70,110 @@ has_tin: true output: is_eligible_for_lifetime_learning_credit: false + + +- name: 2024 student claimed as a dependent elsewhere cannot claim the credit + period: 2024 + input: + people: + student: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [student] + households: + household: + members: [student] + state_code: TX + output: + # IRC 25A(g)(3). + is_eligible_for_lifetime_learning_credit: false + +- name: 2024 joint return with one spouse claimed elsewhere cannot claim the credit for either spouse (the claimable spouse is the head) + period: 2024 + input: + people: + person_a: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + is_eligible_for_lifetime_learning_credit: [false, false] + +- name: 2024 joint return with one spouse claimed elsewhere cannot claim the credit for either spouse (the claimable spouse is the spouse) + period: 2024 + input: + people: + person_b: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: TX + output: + is_eligible_for_lifetime_learning_credit: [false, false] + +- name: 2024 joint return with neither spouse claimed keeps both students' eligibility + period: 2024 + input: + people: + person_a: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + person_b: + age: 30 + attends_eligible_educational_institution_for_lifetime_learning_credit: true + has_lifetime_learning_credit_1098_t_or_exception: true + has_tin: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + is_eligible_for_lifetime_learning_credit: [true, true] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/recovery_rebate_credit/recovery_rebate_credit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/recovery_rebate_credit/recovery_rebate_credit.yaml index e84c1db994a..0d21682feaf 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/recovery_rebate_credit/recovery_rebate_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/recovery_rebate_credit/recovery_rebate_credit.yaml @@ -47,3 +47,115 @@ members: [parent, child_1, child_2] output: recovery_rebate_credit: 3_920 # https://www.efile.com/stimulus-check-three/ + + +- name: 2021 single filer who can be claimed as a dependent gets no ARPA rebate + period: 2021 + input: + people: + filer: + age: 20 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [filer] + households: + household: + members: [filer] + state_code: TX + output: + # IRC 6428B(c)(2): an eligible individual excludes "any individual who is a + # dependent of another taxpayer". + rrc_arpa: 0 + +- name: 2021 claimable single filer's child adds no ARPA rebate + period: 2021 + input: + people: + parent: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + # IRC 152(b)(1): such a filer has no dependents. + rrc_arpa: 0 + +- name: 2021 joint return with one claimable spouse gets the other spouse's ARPA rebate (the claimable spouse is the head) + period: 2021 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 22 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # Each spouse is an eligible individual or not on their own; $1,400 for + # the spouse who cannot be claimed. + rrc_arpa: 1_400 + +- name: 2021 joint return with one claimable spouse gets the other spouse's ARPA rebate (the claimable spouse is the spouse) + period: 2021 + input: + people: + person_b: + age: 22 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: TX + output: + rrc_arpa: 1_400 + +- name: 2021 joint return with neither spouse claimable gets both rebates and the child's + period: 2021 + input: + people: + person_a: + age: 20 + person_b: + age: 22 + child: + age: 2 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + # 3 x $1,400. + rrc_arpa: 4_200 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/student_loan_interest/student_loan_interest_ald_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/student_loan_interest/student_loan_interest_ald_eligible.yaml index ca6f654768e..97566eae089 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/student_loan_interest/student_loan_interest_ald_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/student_loan_interest/student_loan_interest_ald_eligible.yaml @@ -37,3 +37,72 @@ filing_status: SEPARATE output: student_loan_interest_ald_eligible: false + + +- name: 2024 single filer claimed as a dependent elsewhere cannot deduct student loan interest + period: 2024 + input: + people: + filer: + age: 24 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [filer] + households: + household: + members: [filer] + state_code: TX + output: + # IRC 221(c). + student_loan_interest_ald_eligible: false + +- name: 2024 joint return with one spouse claimed elsewhere cannot deduct student loan interest (the claimable spouse is the head) + period: 2024 + input: + people: + person_a: + age: 24 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 24 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: TX + output: + # Form 1040 instructions: "you, or your spouse if filing jointly, aren't + # claimed as a dependent". + student_loan_interest_ald_eligible: [false, false] + +- name: 2024 joint return with one spouse claimed elsewhere cannot deduct student loan interest (the claimable spouse is the spouse) + period: 2024 + input: + people: + person_b: + age: 24 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 24 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: TX + output: + student_loan_interest_ald_eligible: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/exemptions/exemptions_count.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/exemptions/exemptions_count.yaml new file mode 100644 index 00000000000..16c899b35cf --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/exemptions/exemptions_count.yaml @@ -0,0 +1,133 @@ +- name: 2025 single filer with a child counts both + period: 2025 + input: + people: + parent: + age: 30 + child: + age: 5 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + exemptions_count: 2 + +- name: 2025 single filer who can be claimed as a dependent has no exemptions + period: 2025 + input: + people: + parent: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 5 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + # IRC 151(d)(2) for the filer; IRC 152(b)(1) for the child. + exemptions_count: 0 + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + exemptions_count: 1 + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: TX + output: + exemptions_count: 1 + +- name: 2025 joint return with both spouses claimable has no exemptions + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + person_b: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + exemptions_count: 0 + +- name: 2025 joint return with neither spouse claimable counts everyone + period: 2025 + input: + people: + person_a: + age: 20 + person_b: + age: 20 + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: TX + output: + exemptions_count: 3 diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.yaml index 406b0d0f8f9..5964fe8cf76 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.yaml @@ -71,3 +71,54 @@ output: # Equal capacity: split evenly (both columns can absorb their share) de_personal_credit_indv: [110, 110] + + +- name: 2025 spouse who can be claimed as a dependent has no personal credit (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + de_income_tax_before_non_refundable_credits_indv: 1000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + de_income_tax_before_non_refundable_credits_indv: 1000 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: DE + output: + # 30 Del. C. 1110(b)(1): $110 for each personal exemption to which the + # individual is entitled for federal purposes; IRC 151(d)(2). + de_personal_credit_indv: [0, 110] + +- name: 2025 spouse who can be claimed as a dependent has no personal credit (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + de_income_tax_before_non_refundable_credits_indv: 1000 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + de_income_tax_before_non_refundable_credits_indv: 1000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: DE + output: + de_personal_credit_indv: [110, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credits.yaml b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credits.yaml index fd12e7c01ce..b13f8f2f86d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credits.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/de/tax/income/credits/personal/de_personal_credits.yaml @@ -30,3 +30,58 @@ de_income_tax_before_non_refundable_credits_unit: 1000000 output: de_personal_credit: 0 + + +- name: 2025 joint return with one claimable spouse gets one personal credit (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: DE + output: + # Federal exemptions: the other spouse only; IRC 152(b)(1) leaves no + # dependents. + de_personal_credit_potential: 110 + +- name: 2025 joint return with one claimable spouse gets one personal credit (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: DE + output: + de_personal_credit_potential: 110 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_ctc.yaml index 658c2647ad9..0690ba455ca 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_ctc.yaml @@ -6,7 +6,6 @@ age: 30 child: age: 2 - ctc_qualifying_child: true tax_units: tax_unit: members: @@ -30,7 +29,6 @@ age: 30 child: age: 6 - ctc_qualifying_child: true tax_units: tax_unit: members: @@ -54,10 +52,8 @@ age: 32 child1: age: 1 - ctc_qualifying_child: true child2: age: 5 - ctc_qualifying_child: true tax_units: tax_unit: members: @@ -83,7 +79,6 @@ age: 30 child: age: 2 - ctc_qualifying_child: true tax_units: tax_unit: members: @@ -107,7 +102,6 @@ age: 30 child: age: 2 - ctc_qualifying_child: true tax_units: tax_unit: members: @@ -123,3 +117,27 @@ state_code: GA output: ga_non_refundable_credits: 0 + + +- name: 2026 GA CTC still counts the child of a filer who can be claimed as a dependent + period: 2026 + input: + people: + parent: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [parent, child] + filing_status: SINGLE + ga_income_tax_before_non_refundable_credits: 10000 + households: + household: + members: [parent, child] + state_code: GA + output: + # Georgia uses the IRC 24(c) qualifying-child definition only; whether IRC + # 152(b)(1) reaches it is not settled, so the child test is kept. + ga_ctc: 250 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_low_income_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_low_income_credit.yaml index 5a059239e46..2a410aa94df 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_low_income_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/credits/ga_low_income_credit.yaml @@ -5,7 +5,7 @@ adjusted_gross_income: 14000 age_head: 67 age_spouse: 70 - exemptions_count: 2 + tax_unit_size: 2 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 32 @@ -16,7 +16,7 @@ adjusted_gross_income: 4000 age_head: 67 age_spouse: 56 - exemptions_count: 2 + tax_unit_size: 2 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 78 @@ -27,7 +27,7 @@ adjusted_gross_income: 15500 age_head: 80 age_spouse: 75 - exemptions_count: 3 + tax_unit_size: 3 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 25 @@ -38,7 +38,7 @@ adjusted_gross_income: 4000 age_head: 67 age_spouse: 56 - exemptions_count: 0 + tax_unit_size: 0 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 26 @@ -49,7 +49,7 @@ adjusted_gross_income: 10500 age_head: 67 age_spouse: 56 - exemptions_count: 0 + tax_unit_size: 0 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 8 @@ -60,7 +60,7 @@ adjusted_gross_income: 10500 age_head: 56 age_spouse: 56 - exemptions_count: 0 + tax_unit_size: 0 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 0 @@ -71,7 +71,7 @@ adjusted_gross_income: 10500 age_head: 65 age_spouse: 64 - exemptions_count: 1 + tax_unit_size: 1 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 16 @@ -82,7 +82,7 @@ adjusted_gross_income: 10000 age_head: 64 age_spouse: 64 - exemptions_count: 2 + tax_unit_size: 2 ga_income_tax_before_non_refundable_credits: 1000000 output: ga_low_income_credit: 16 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.yaml index f4acfca8392..5f236216e86 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.yaml @@ -27,3 +27,102 @@ exemptions_count: 3 output: nm_low_and_middle_income_exemption: 4_500 + + +- name: 2025 single filer who can be claimed as a dependent has no exemptions + period: 2025 + input: + people: + filer: + age: 30 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [filer] + adjusted_gross_income: 10_000 + households: + household: + members: [filer] + state_code: NM + output: + # NMSA 7-2-5.8 counts federal exemptions; IRC 151(d)(2). + nm_low_and_middle_income_exemption: 0 + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # One exemption (the child is not a dependent, IRC 152(b)(1)) x $2,500. + nm_low_and_middle_income_exemption: 2_500 + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_b, person_a, child] + state_code: NM + output: + # One exemption (the child is not a dependent, IRC 152(b)(1)) x $2,500. + nm_low_and_middle_income_exemption: 2_500 + +- name: 2025 joint return with neither spouse claimable counts everyone + period: 2025 + input: + people: + person_a: + age: 30 + person_b: + age: 30 + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # 3 exemptions x $2,500. + nm_low_and_middle_income_exemption: 7_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.yaml index 7bcd28c10fa..6c7e83fb126 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.yaml @@ -246,3 +246,83 @@ state_code: NM output: nm_low_income_comprehensive_tax_rebate: 583 + +- name: 2025 single filer who can be claimed as a dependent gets no rebate + period: 2025 + input: + people: + person: + age: 20 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person] + nm_modified_gross_income: 5_000 + households: + household: + members: [person] + state_code: NM + output: + # NMSA 7-2-14(A): only a resident "who is not a dependent of another + # individual" may claim the rebate. + nm_low_income_comprehensive_tax_rebate: 0 + +- name: 2025 single filer who cannot be claimed gets the one-exemption rebate + period: 2025 + input: + people: + person: + age: 20 + tax_units: + tax_unit: + members: [person] + nm_modified_gross_income: 5_000 + households: + household: + members: [person] + state_code: NM + output: + # $1,000-$7,500 row, one exemption. + nm_low_income_comprehensive_tax_rebate: 252 + +- name: 2025 single filer aged 65 or older who can be claimed as a dependent gets no rebate + period: 2025 + input: + people: + person: + age: 70 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person] + nm_modified_gross_income: 5_000 + households: + household: + members: [person] + state_code: NM + output: + # The age-65 exemptions do not make a dependent of another individual a + # claimant (NMSA 7-2-14(A)). + nm_low_income_comprehensive_tax_rebate: 0 + +- name: 2025 joint return with one claimable spouse keeps the rebate with the couple's combined income + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + person_b: + age: 30 + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + nm_modified_gross_income: 8_500 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # One exemption; the $8,000-$9,000 row of the one-exemption table. + nm_low_income_comprehensive_tax_rebate: 212 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.yaml index 5b10b5cf2d8..6fa092fcffe 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.yaml @@ -24,3 +24,104 @@ blind_head: true output: nm_low_income_comprehensive_tax_rebate_exemptions: 2 + +- name: 2025 single filer who can be claimed as a dependent has no rebate exemptions + period: 2025 + input: + people: + parent: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NM + output: + # NMSA 7-2-14(A),(C); PIT-RC line 2a. + nm_low_income_comprehensive_tax_rebate_exemptions: 0 + +- name: 2025 joint return with one claimable spouse drops its ordinary exemption and the dependents (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 70 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 40 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 2 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # "the non-qualifying spouse is not eligible" (PIT-RC line 2a) and a + # return on which a filer can be claimed has no dependents (IRC + # 152(b)(1)), leaving 1 ordinary exemption; NMSA 7-2-14(C) still adds 2 + # for "each individual ... included in the return who is sixty-five years + # of age or older". + nm_low_income_comprehensive_tax_rebate_exemptions: 3 + +- name: 2025 joint return with one claimable spouse drops its ordinary exemption and the dependents (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 40 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 70 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 2 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NM + output: + nm_low_income_comprehensive_tax_rebate_exemptions: 3 + +- name: 2025 joint return with neither spouse claimable counts everyone plus the age-65 exemptions + period: 2025 + input: + people: + person_a: + age: 70 + person_b: + age: 40 + child: + age: 2 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # 3 federal exemptions + 2 for the spouse aged 65 or older. + nm_low_income_comprehensive_tax_rebate_exemptions: 5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/exemptions/personal/oh_personal_exemptions_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/exemptions/personal/oh_personal_exemptions_eligible.yaml index 97f70fe2376..88f43d6e9af 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/exemptions/personal/oh_personal_exemptions_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/exemptions/personal/oh_personal_exemptions_eligible.yaml @@ -27,3 +27,57 @@ is_tax_unit_dependent: true output: oh_personal_exemptions_eligible_person: true + + +- name: 2025 joint return with one claimable spouse makes neither that spouse nor the child eligible (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: OH + output: + # Ohio R.C. 5747.025; IRC 152(b)(1) leaves the return no dependents. + oh_personal_exemptions_eligible_person: [false, true, false] + +- name: 2025 joint return with one claimable spouse makes neither that spouse nor the child eligible (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: OH + output: + oh_personal_exemptions_eligible_person: [true, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/exemption/ri_exemptions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/exemption/ri_exemptions.yaml index 874ee89a624..539a40f00a6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/exemption/ri_exemptions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/exemption/ri_exemptions.yaml @@ -135,3 +135,82 @@ exemptions_count: 2 output: ri_exemptions: 0 + + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + ri_agi: 10000 + households: + household: + members: [person_a, person_b, child] + state_code: RI + output: + # R.I. Gen. Laws 44-30-2.6: "If someone else can claim you on their return, + # your exemption amount is zero"; IRC 152(b)(1). + ri_exemptions: 5_100 + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + ri_agi: 10000 + households: + household: + members: [person_b, person_a, child] + state_code: RI + output: + ri_exemptions: 5_100 + +- name: 2025 joint return with neither spouse claimable counts everyone + period: 2025 + input: + people: + person_a: + age: 30 + person_b: + age: 30 + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + ri_agi: 10000 + households: + household: + members: [person_a, person_b, child] + state_code: RI + output: + ri_exemptions: 15_300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_base_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_base_exemption.yaml index 9e54d8bd87f..f1f8087f317 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_base_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wi/tax/income/wi_base_exemption.yaml @@ -5,3 +5,79 @@ state_code: WI output: wi_base_exemption: 5 * 700 + + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: WI + output: + # Form 1 instructions: "either you or your spouse (not both) can be claimed"; + # IRC 152(b)(1). + wi_base_exemption: 700 + +- name: 2025 joint return with one claimable spouse counts only the other spouse (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: WI + output: + wi_base_exemption: 700 + +- name: 2025 joint return with neither spouse claimable counts everyone + period: 2025 + input: + people: + person_a: + age: 30 + person_b: + age: 30 + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: WI + output: + wi_base_exemption: 2_100 diff --git a/policyengine_us/variables/gov/irs/credits/cdcc/cdcc_eligible.py b/policyengine_us/variables/gov/irs/credits/cdcc/cdcc_eligible.py index 82861496784..20d82cc32e0 100644 --- a/policyengine_us/variables/gov/irs/credits/cdcc/cdcc_eligible.py +++ b/policyengine_us/variables/gov/irs/credits/cdcc/cdcc_eligible.py @@ -9,6 +9,9 @@ class is_cdcc_eligible(Variable): reference = ( "https://www.law.cornell.edu/uscode/text/26/21#b_1", "https://www.law.cornell.edu/uscode/text/26/21#e_5", + "https://www.law.cornell.edu/uscode/text/26/152#b_1", + # Publication 503, Who Is a Qualifying Person? + "https://www.irs.gov/pub/irs-prior/p503--2025.pdf#page=3", ) def formula(person, period, parameters): @@ -21,8 +24,18 @@ def formula(person, period, parameters): # qualifying individual even when the noncustodial parent claims the # dependent, so any under-age member other than the head or spouse # qualifies. - qualifies_by_age = ~head_or_spouse & (age < p.child_age) - # Subsections (b)(1)(B) (dependent) and (b)(1)(C) (spouse). + # A child qualifies only as the filer's dependent ("Your qualifying + # child who is your dependent", Publication 503), and under IRC + # 152(b)(1) a return on which the filer (or, if joint, either spouse) + # can be claimed as a dependent has no dependents. + filer_is_dependent = person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) + qualifies_by_age = ~head_or_spouse & (age < p.child_age) & ~filer_is_dependent + # Subsections (b)(1)(B) (dependent) and (b)(1)(C) (spouse). Subsection + # (b)(1)(B) determines dependency "without regard to subsections + # (b)(1), (b)(2), and (d)(1)(B)" of section 152, so a disabled person + # still qualifies when the filer can be claimed as a dependent. disabled = person("is_incapable_of_self_care", period) married = person.tax_unit("tax_unit_married", period) qualifies_by_disability = disabled & (is_dependent | (head_or_spouse & married)) diff --git a/policyengine_us/variables/gov/irs/credits/ctc/maximum/ctc_qualifying_child.py b/policyengine_us/variables/gov/irs/credits/ctc/maximum/ctc_qualifying_child.py index ae2c1bfab24..bd478b60870 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/maximum/ctc_qualifying_child.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/maximum/ctc_qualifying_child.py @@ -8,7 +8,13 @@ class ctc_qualifying_child(Variable): documentation = "Child qualifies for the Child Tax Credit" definition_period = YEAR defined_for = "is_tax_unit_dependent" - reference = "https://www.law.cornell.edu/uscode/text/26/24#c" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/24#a", + "https://www.law.cornell.edu/uscode/text/26/24#c", + "https://www.law.cornell.edu/uscode/text/26/152#b_1", + # Publication 501, Dependent Taxpayer Test. + "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=11", + ) def formula(person, period, parameters): age = person("age", period) @@ -18,4 +24,12 @@ def formula(person, period, parameters): meets_identification_requirements = person( "meets_ctc_child_identification_requirements", period ) - return age_eligible & meets_identification_requirements + # IRC 24(a) allows the credit only for a qualifying child for whom the + # taxpayer is allowed a deduction under section 151. Under IRC + # 152(b)(1) a taxpayer who can be claimed as a dependent is treated as + # having no dependents, and on a joint return neither spouse may be + # claimable (Publication 501, Dependent Taxpayer Test). + filer_is_dependent = person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) + return age_eligible & meets_identification_requirements & ~filer_is_dependent diff --git a/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.py b/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.py index 4caaab2f978..70f5b877edd 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/ctc_adult_individual_maximum.py @@ -15,6 +15,7 @@ class ctc_adult_individual_maximum(Variable): "https://www.law.cornell.edu/uscode/text/26/24#a", "https://www.law.cornell.edu/uscode/text/26/24#h", "https://www.law.cornell.edu/uscode/text/26/24#i", + "https://www.law.cornell.edu/uscode/text/26/152#b_1", ) def formula(person, period, parameters): @@ -24,9 +25,16 @@ def formula(person, period, parameters): filer_meets_tin_requirement = person.tax_unit( "filer_meets_ctc_identification_requirements", period ) + # IRC 24(h)(4) gives the credit for "any dependent of the taxpayer"; + # under IRC 152(b)(1) a return on which the filer (or, if joint, + # either spouse) can be claimed as a dependent has no dependents. + filer_is_dependent = person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) return ( is_adult * dependent_has_tin * filer_meets_tin_requirement + * ~filer_is_dependent * p.amount.adult_dependent ) diff --git a/policyengine_us/variables/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.py b/policyengine_us/variables/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.py index 6509aa1f045..c3a557b0d8f 100644 --- a/policyengine_us/variables/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.py +++ b/policyengine_us/variables/gov/irs/credits/education/american_opportunity_credit/is_eligible_for_american_opportunity_credit.py @@ -18,6 +18,11 @@ def formula(person, period, parameters): filing_status = tax_unit("filing_status", period) filing_status_values = filing_status.possible_values filing_status_eligible = filing_status != filing_status_values.SEPARATE + # IRC 25A(g)(3): no credit to an individual claimed as a dependent by + # another taxpayer, and the Form 1040 instructions extend this to + # "your spouse if filing jointly", so either filer being claimed bars + # the return's credit. + filer_claimed = tax_unit("head_or_spouse_is_dependent_elsewhere", period) requires_1098_t = aoc.eligibility.requires_1098_t_or_exception has_1098_t_or_exception = ( person("has_american_opportunity_credit_1098_t_or_exception", period) @@ -66,4 +71,5 @@ def formula(person, period, parameters): period, ) & filing_status_eligible + & ~filer_claimed ) diff --git a/policyengine_us/variables/gov/irs/credits/education/is_eligible_for_lifetime_learning_credit.py b/policyengine_us/variables/gov/irs/credits/education/is_eligible_for_lifetime_learning_credit.py index 3d80f23da5f..75c2e133d15 100644 --- a/policyengine_us/variables/gov/irs/credits/education/is_eligible_for_lifetime_learning_credit.py +++ b/policyengine_us/variables/gov/irs/credits/education/is_eligible_for_lifetime_learning_credit.py @@ -19,6 +19,11 @@ def formula(person, period, parameters): filing_status = tax_unit("filing_status", period) filing_status_values = filing_status.possible_values filing_status_eligible = filing_status != filing_status_values.SEPARATE + # IRC 25A(g)(3): no credit to an individual claimed as a dependent by + # another taxpayer, and the Form 1040 instructions extend this to + # "your spouse if filing jointly", so either filer being claimed bars + # the return's credit. + filer_claimed = tax_unit("head_or_spouse_is_dependent_elsewhere", period) requires_1098_t = llc.eligibility.requires_1098_t_or_exception has_1098_t_or_exception = ( person("has_lifetime_learning_credit_1098_t_or_exception", period) @@ -45,4 +50,5 @@ def formula(person, period, parameters): period, ) & filing_status_eligible + & ~filer_claimed ) diff --git a/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_adult_count_with_valid_ssn.py b/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_adult_count_with_valid_ssn.py index 2250e5d6698..4e08716df78 100644 --- a/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_adult_count_with_valid_ssn.py +++ b/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_adult_count_with_valid_ssn.py @@ -13,6 +13,7 @@ class rrc_adult_count_with_valid_ssn(Variable): "https://www.law.cornell.edu/uscode/text/26/6428A#g_2", "https://www.law.cornell.edu/uscode/text/26/6428B#e_2_A", "https://www.law.cornell.edu/uscode/text/26/6428B#e_2_B", + "https://www.law.cornell.edu/uscode/text/26/6428#d_2", ) def formula(tax_unit, period, parameters): @@ -20,4 +21,9 @@ def formula(tax_unit, period, parameters): head_or_spouse = person("is_tax_unit_head_or_spouse", period) # Reuse existing EITC SSN check - identical definition (SSN required) has_valid_ssn = person("meets_eitc_identification_requirements", period) - return tax_unit.sum(head_or_spouse & has_valid_ssn) + # An "eligible individual" excludes "any individual with respect to + # whom a deduction under section 151 is allowable to another + # taxpayer" (IRC 6428(d)(2), 6428A(d)(2)) or "who is a dependent of + # another taxpayer" (IRC 6428B(c)(2)). + claimed = person("claimed_as_dependent_on_another_return", period) + return tax_unit.sum(head_or_spouse & has_valid_ssn & ~claimed) diff --git a/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_arpa_dependents_with_valid_ssn.py b/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_arpa_dependents_with_valid_ssn.py index 00b05333a86..e91e2ee9a48 100644 --- a/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_arpa_dependents_with_valid_ssn.py +++ b/policyengine_us/variables/gov/irs/credits/recovery_rebate_credit/rrc_arpa_dependents_with_valid_ssn.py @@ -13,6 +13,12 @@ class rrc_arpa_dependents_with_valid_ssn(Variable): def formula(tax_unit, period, parameters): person = tax_unit.members - is_dependent = person("is_tax_unit_dependent", period) + # IRC 6428B(b)(2) counts "the number of dependents of the taxpayer", and + # under 152(b)(1) a return on which the filer (or, if joint, either + # spouse) can be claimed as a dependent has none. + dependent_filer = person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) + is_dependent = person("is_tax_unit_dependent", period) & ~dependent_filer has_valid_ssn = person("meets_eitc_identification_requirements", period) return tax_unit.sum(is_dependent & has_valid_ssn) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/student_loan_interest/student_loan_interest_ald_eligible.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/student_loan_interest/student_loan_interest_ald_eligible.py index 9bf6a41feb0..3b45af22df5 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/student_loan_interest/student_loan_interest_ald_eligible.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/student_loan_interest/student_loan_interest_ald_eligible.py @@ -19,9 +19,13 @@ class student_loan_interest_ald_eligible(Variable): ) def formula(person, period, parameters): - # Per IRC § 221(c), taxpayer cannot be claimed as a dependent head_or_spouse = person("is_tax_unit_head_or_spouse", period) + # Per IRC § 221(c), no deduction for an individual claimed as a + # dependent by another taxpayer; the Form 1040 instructions require + # that "you, or your spouse if filing jointly, aren't claimed as a + # dependent", so either filer being claimed bars the return. + filer_claimed = person.tax_unit("head_or_spouse_is_dependent_elsewhere", period) # Per IRC § 221(e)(2), taxpayer cannot file as Married Filing Separately filing_status = person.tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE - return head_or_spouse & ~separate + return head_or_spouse & ~separate & ~filer_claimed diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/exemptions/exemptions_count.py b/policyengine_us/variables/gov/irs/income/taxable_income/exemptions/exemptions_count.py index b8e19acbdab..2fec52064f4 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/exemptions/exemptions_count.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/exemptions/exemptions_count.py @@ -7,5 +7,31 @@ class exemptions_count(Variable): label = "Number of tax exemptions" unit = USD definition_period = YEAR + documentation = ( + "Number of personal exemptions on the federal return: one for each " + "filer who cannot be claimed as a dependent by another taxpayer, plus " + "one for each dependent unless a filer can be claimed." + ) + reference = ( + "https://www.law.cornell.edu/uscode/text/26/151#d_2", + "https://www.law.cornell.edu/uscode/text/26/152#b_1", + # Publication 501, Dependent Taxpayer Test. + "https://www.irs.gov/pub/irs-prior/p501--2025.pdf#page=11", + # 2017 Form 1040, line 6a: "If someone can claim you as a dependent, + # do not check box 6a"; instructions, line 6b: the spouse box only + # if the "spouse can't be claimed as a dependent". + "https://www.irs.gov/pub/irs-prior/f1040--2017.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/i1040gi--2017.pdf#page=15", + ) - adds = ["tax_unit_size"] + def formula(tax_unit, period, parameters): + # IRC 151(d)(2) zeroes the exemption of a filer whom another taxpayer + # can claim, and IRC 152(b)(1) treats a return on which the filer (or, + # if joint, either spouse) can be claimed as having no dependents. So + # such a return counts only its filers who cannot be claimed. + tax_unit_size = tax_unit("tax_unit_size", period) + filer_is_dependent = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + independent_filers = tax_unit( + "head_spouse_count_not_dependent_elsewhere", period + ) + return where(filer_is_dependent, independent_filers, tax_unit_size) diff --git a/policyengine_us/variables/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.py b/policyengine_us/variables/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.py index 6722f22b3c2..9701ad23833 100644 --- a/policyengine_us/variables/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.py +++ b/policyengine_us/variables/gov/states/de/tax/income/credits/personal/de_personal_credit_indv.py @@ -32,16 +32,27 @@ def formula(person, period, parameters): fixed = person("de_aged_personal_credit_indv", period) + person( "de_cdcc_indv", period ) - # Each column's own $110 personal credit is fixed to that column. - head_fixed = person.tax_unit.sum(is_head * fixed) + credit_per - spouse_fixed = person.tax_unit.sum(is_spouse * fixed) + credit_per + # Each column's own $110 personal credit is fixed to that column. 30 + # Del. C. 1110(b)(1) gives it "for each personal exemption to which + # such individual is entitled ... for federal income tax purposes", + # and a spouse who can be claimed as a dependent has none (IRC + # 151(d)(2)). + head_own = where( + person.tax_unit("head_is_dependent_elsewhere", period), 0, credit_per + ) + spouse_own = where( + person.tax_unit("spouse_is_dependent_elsewhere", period), 0, credit_per + ) + head_fixed = person.tax_unit.sum(is_head * fixed) + head_own + spouse_fixed = person.tax_unit.sum(is_spouse * fixed) + spouse_own head_capacity = max_(head_tax - head_fixed, 0) spouse_capacity = max_(spouse_tax - spouse_fixed, 0) total_units = person.tax_unit("exemptions_count", period) - # Dependent increments: exemptions beyond the two spouses. - dep_units = max_(total_units - 2, 0) + # Dependent increments: exemptions beyond the spouses' own. + own_units = person.tax_unit("head_spouse_count_not_dependent_elsewhere", period) + dep_units = max_(total_units - own_units, 0) # Optimal dependent split: try proportional floor and ceil, pick # whichever maximises effective credits (min of alloc vs @@ -62,7 +73,7 @@ def formula(person, period, parameters): ) n_head_dep = where(eff_high > eff_low, n_high, n_low) - head_alloc = credit_per + n_head_dep * credit_per - spouse_alloc = credit_per + (dep_units - n_head_dep) * credit_per + head_alloc = head_own + n_head_dep * credit_per + spouse_alloc = spouse_own + (dep_units - n_head_dep) * credit_per return is_head_or_spouse * where(is_head, head_alloc, spouse_alloc) diff --git a/policyengine_us/variables/gov/states/ga/tax/income/credits/ctc/ga_ctc_potential.py b/policyengine_us/variables/gov/states/ga/tax/income/credits/ctc/ga_ctc_potential.py index 46ceaac454b..61195c8cb2c 100644 --- a/policyengine_us/variables/gov/states/ga/tax/income/credits/ctc/ga_ctc_potential.py +++ b/policyengine_us/variables/gov/states/ga/tax/income/credits/ctc/ga_ctc_potential.py @@ -16,7 +16,17 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ga.tax.income.credits.ctc person = tax_unit.members age = person("age", period) - ctc_eligible_child = person("ctc_qualifying_child", period) + # Georgia gives "qualifying child" the meaning of IRC 24(c) only, so + # it is not limited to children for whom the federal credit is + # allowed (IRC 24(a), 152(b)(1)). Whether a filer who can be claimed + # as a dependent can claim it is not settled; this keeps the IRC + # 24(c) child test. + p_federal = parameters(period).gov.irs.credits.ctc + ctc_eligible_child = ( + person("is_tax_unit_dependent", period) + & (age < p_federal.amount.base.thresholds[-1]) + & person("meets_ctc_child_identification_requirements", period) + ) ga_child_age_eligible = age < p.age_threshold eligible_children = tax_unit.sum(ctc_eligible_child & ga_child_age_eligible) diff --git a/policyengine_us/variables/gov/states/ga/tax/income/credits/ga_low_income_credit_potential.py b/policyengine_us/variables/gov/states/ga/tax/income/credits/ga_low_income_credit_potential.py index 4487e9e448a..07708fa354f 100644 --- a/policyengine_us/variables/gov/states/ga/tax/income/credits/ga_low_income_credit_potential.py +++ b/policyengine_us/variables/gov/states/ga/tax/income/credits/ga_low_income_credit_potential.py @@ -15,7 +15,10 @@ def formula(tax_unit, period, parameters): # "multiplied by the number of dependents which the taxpayer is entitled to claim." # The tax form excludes adult dependents: # "Exemptions are self, spouse and natural or legally adopted children" - exemptions = tax_unit("exemptions_count", period) + # Georgia counts its own exemptions (O.C.G.A. 48-7A-3 deems each joint + # spouse a dependent), not the federal count, which leaves out a + # filer who can be claimed as a dependent. + exemptions = tax_unit("tax_unit_size", period) p = parameters(period).gov.states.ga.tax.income.credits.low_income # age threshold age_threshold = p.supplement_age_eligibility diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.py index 98a265dc9e8..c1540e1b65f 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_low_and_middle_income_exemption.py @@ -25,5 +25,8 @@ def formula(tax_unit, period, parameters): exemption_amount = p.max_amount - reduction_amount # Multiply the exemption amount by the number of exemptions + # NMSA 7-2-5.8 multiplies by the number of federal exemptions, which + # leaves out a filer who can be claimed as a dependent and, on such a + # return, the dependents. exemptions_count = tax_unit("exemptions_count", period) return eligible * exemption_amount * exemptions_count diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.py index 1fbb3a4d6b7..33bf6d2e2a3 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate.py @@ -49,4 +49,10 @@ def formula(tax_unit, period, parameters): p.divisor, 1, ) - return rebate / divisor + # NMSA 7-2-14(A) allows the rebate only to a resident "who is not a + # dependent of another individual"; on a joint return the spouse who + # is not may still claim it (PIT-RC). The table starts at one + # exemption. + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) + eligible = ~every_filer_dependent & (exemptions > 0) + return where(eligible, rebate / divisor, 0) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.py index cd8f89ad180..05621600269 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/low_income_comprehensive/nm_low_income_comprehensive_tax_rebate_exemptions.py @@ -9,6 +9,7 @@ class nm_low_income_comprehensive_tax_rebate_exemptions(Variable): definition_period = YEAR reference = ( "https://law.justia.com/codes/new-mexico/chapter-7/article-2/section-7-2-14/", + "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=3", ) defined_for = StateCode.NM @@ -21,6 +22,11 @@ def formula(tax_unit, period, parameters): tax purposes for each individual included in the return who is domiciled in New Mexico... """ + # The federal count leaves out a filer who can be claimed as a + # dependent and, on such a return, the dependents, as PIT-RC line 2a + # does: "If either you or your spouse meet the general + # qualifications, but not both, then the non-qualifying spouse is not + # eligible". federal_exemptions = tax_unit("exemptions_count", period) """ ...plus two additional exemptions for each individual domiciled diff --git a/policyengine_us/variables/gov/states/oh/tax/income/exemptions/oh_personal_exemptions_eligible_person.py b/policyengine_us/variables/gov/states/oh/tax/income/exemptions/oh_personal_exemptions_eligible_person.py index e222b7bf362..b78575932fb 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/exemptions/oh_personal_exemptions_eligible_person.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/exemptions/oh_personal_exemptions_eligible_person.py @@ -18,6 +18,11 @@ def formula(person, period, parameters): dependent_on_another_return = person( "claimed_as_dependent_on_another_return", period ) - # The personal exemption is also provided to dependents - dependent = person("is_tax_unit_dependent", period) + # The personal exemption is also provided to dependents, but a return + # on which the filer (or, if joint, either spouse) can be claimed as a + # dependent has none (IRC 152(b)(1)). + dependent_filer = person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) + dependent = person("is_tax_unit_dependent", period) & ~dependent_filer return (~dependent_on_another_return & head_or_spouse) | dependent From 4b0709e55f26e0e9932cfffb39c2f650168bdbca Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 09:40:51 -0400 Subject: [PATCH 8/9] Apply New Mexico's rules for a filer who can be claimed as a dependent NMSA chapter 7 article 2 bars "a dependent of another individual" from several credits and exemptions that the model gave anyway, and counts as dependents only those allowed under IRC 152, which leaves a return with a claimable filer none (IRC 152(b)(1)). Now: - the child income tax credit (7-2-18.34) needs a filer who cannot be claimed; - the 65+ medical care credit (7-2-18.13) needs a filer who is both 65+ and not claimable, and it and the 65+ medical exemption (7-2-5.9) no longer count a dependent's age; - the elderly property tax rebate (7-2-18) and the 100+ exemption (7-2-5.7) apply per filer; - the working families credit (7-2-18.15) applies the federal childless-filer dependent test and the filer-only age test; - the child day care credit (7-2-18.1) and the deduction for certain dependents (7-2-39) count no dependents when a filer can be claimed. On a joint return, a spouse who cannot be claimed keeps the credits that TRD's PIT-RC instructions say "your spouse may still qualify to claim". Co-Authored-By: Claude Opus 5.5 --- changelog.d/claimable-filer-nm.fixed.md | 1 + .../test_claimable_filer_nm_invariants.py | 69 +++++++++ .../credits/cdcc/nm_cdcc_eligible_child.yaml | 78 ++++++++++ .../eitc/nm_eitc_demographic_eligible.yaml | 138 ++++++++++++++++++ .../states/nm/tax/income/credits/nm_ctc.yaml | 104 +++++++++++++ .../credits/nm_medical_expense_credit.yaml | 55 +++++++ .../nm_deduction_for_certain_dependents.yaml | 85 +++++++++++ .../exemptions/nm_hundred_year_exemption.yaml | 107 ++++++++++++++ .../nm_medical_expense_exemption.yaml | 78 ++++++++++ .../nm_property_tax_rebate_eligibility.yaml | 121 +++++++++++++++ .../credits/cdcc/nm_cdcc_eligible_child.py | 8 +- .../eitc/nm_eitc_demographic_eligible.py | 14 +- .../states/nm/tax/income/credits/nm_ctc.py | 12 +- .../credits/nm_medical_expense_credit.py | 25 ++-- .../nm_deduction_for_certain_dependents.py | 5 +- .../exemptions/nm_hundred_year_exemption.py | 11 +- .../nm_medical_expense_exemption.py | 13 +- .../nm_property_tax_rebate_eligible.py | 14 +- 18 files changed, 912 insertions(+), 26 deletions(-) create mode 100644 changelog.d/claimable-filer-nm.fixed.md create mode 100644 policyengine_us/tests/core/test_claimable_filer_nm_invariants.py diff --git a/changelog.d/claimable-filer-nm.fixed.md b/changelog.d/claimable-filer-nm.fixed.md new file mode 100644 index 00000000000..8d7be5cbe9f --- /dev/null +++ b/changelog.d/claimable-filer-nm.fixed.md @@ -0,0 +1 @@ +Apply New Mexico's rules for a filer who can be claimed as a dependent: no child income tax credit when every filer can be claimed (NMSA 7-2-18.34), no childless EITC, child care credit child or certain-dependents deduction on a return with a claimable filer, and the medical care credit, property tax rebate and centenarian exemption only for a filer who meets the age test and cannot be claimed; the medical expense credit and exemption and the childless EITC now test filers' ages, not a dependent's. diff --git a/policyengine_us/tests/core/test_claimable_filer_nm_invariants.py b/policyengine_us/tests/core/test_claimable_filer_nm_invariants.py new file mode 100644 index 00000000000..5b6b3f8589d --- /dev/null +++ b/policyengine_us/tests/core/test_claimable_filer_nm_invariants.py @@ -0,0 +1,69 @@ +"""New Mexico rules for a filer who can be claimed as a dependent. + +The child income tax credit, the 65+ medical care credit and exemption, the +working families credit, the elderly property tax rebate, the child day care +credit, the 100+ exemption and the deduction for certain dependents apply +NMSA chapter 7 article 2's "not a dependent of another individual" tests per +filer, and count no dependents on a return with a claimable filer. + +For couples drawn by Hypothesis and a seeded population in NM, with +either, both or neither spouse claimed, each output is the same under either +head/spouse labelling, and marking another filer as claimed never raises it. +""" + +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us.tests.core.test_claimable_filer_federal_invariants import ( + check_monotone, + check_swap, +) +from policyengine_us.tests.core.test_dependent_elsewhere_head_spouse_swap_invariance import ( + CLAIM_PATTERNS, + _crafted_cases, + _seeded_couples, + adults, + dependents, +) + +STATES = ["NM"] +YEARS = [2023, 2025, 2026] +OUTPUTS = [ + "nm_ctc", + "nm_medical_expense_credit", + "nm_medical_expense_exemption", + "nm_eitc", + "nm_deduction_for_certain_dependents", + "nm_property_tax_rebate", + "nm_cdcc_max_amount", + "nm_hundred_year_exemption", +] + + +@st.composite +def couples(draw): + return { + "state": draw(st.sampled_from(STATES)), + "adults": [draw(adults()), draw(adults())], + "dependents": draw(st.lists(dependents(), max_size=2)), + "claimed": draw(st.sampled_from(CLAIM_PATTERNS)), + } + + +@settings( + max_examples=2, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.sampled_from(YEARS), st.lists(couples(), min_size=6, max_size=12)) +def test_nm_claimable_filer_rules_are_label_free(year, units): + check_swap(units, year, OUTPUTS) + + +@pytest.mark.parametrize("year", YEARS) +def test_nm_claimable_filer_rules_seeded_population(year): + units = _seeded_couples(STATES, per_state=3) + _crafted_cases(STATES) + check_swap(units, year, OUTPUTS) + check_monotone(units, year, OUTPUTS) diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.yaml index 107b50cc08d..67a802934ba 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.yaml @@ -24,3 +24,81 @@ is_tax_unit_dependent: true output: nm_cdcc_eligible_child: true + + +- name: 2025 joint return with one claimable spouse has no eligible child (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # NMSA 7-2-18.1 counts IRC 152 dependents; IRC 152(b)(1) leaves such a + # return none. + nm_cdcc_eligible_child: [false, false, false] + +- name: 2025 joint return with one claimable spouse has no eligible child (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 5 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NM + output: + # NMSA 7-2-18.1 counts IRC 152 dependents; IRC 152(b)(1) leaves such a + # return none. + nm_cdcc_eligible_child: [false, false, false] + +- name: 2025 joint return with neither spouse claimable has an eligible child + period: 2025 + input: + people: + person_a: + age: 30 + person_b: + age: 30 + child: + age: 5 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + nm_cdcc_eligible_child: [false, false, true] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.yaml index 4b6d82715d8..db987bd02f0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.yaml @@ -48,3 +48,141 @@ state_code: NM output: nm_eitc_demographic_eligible: true + + +- name: 2025 childless single filer who can be claimed as a dependent is ineligible + period: 2025 + input: + people: + filer: + age: 21 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [filer] + households: + household: + members: [filer] + state_code: NM + output: + # NMSA 7-2-18.15 imports IRC 32(c)(1)(A)(ii)(III). + nm_eitc_demographic_eligible: false + +- name: 2025 childless joint return with one claimable spouse is ineligible (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 21 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 25 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # Publication 596, Rule 12: neither spouse may be claimable. + nm_eitc_demographic_eligible: false + +- name: 2025 childless joint return with one claimable spouse is ineligible (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 25 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 21 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # Publication 596, Rule 12: neither spouse may be claimable. + nm_eitc_demographic_eligible: false + +- name: 2025 childless joint return with neither spouse claimable is eligible + period: 2025 + input: + people: + person_a: + age: 21 + person_b: + age: 25 + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NM + output: + nm_eitc_demographic_eligible: true + +- name: 2025 claimable single filer with a child is not barred by the dependent test + period: 2025 + input: + people: + filer: + age: 35 + claimed_as_dependent_on_another_return: true + child: + age: 3 + tax_units: + tax_unit: + members: [filer, child] + households: + household: + members: [filer, child] + state_code: NM + output: + # The dependent test applies only without a qualifying child. + nm_eitc_demographic_eligible: true + +- name: 2025 a dependent's age does not make a childless return eligible + period: 2025 + input: + people: + person_a: + age: 70 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 70 + is_tax_unit_head: false + is_tax_unit_spouse: true + relative: + age: 40 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, relative] + filing_status: JOINT + households: + household: + members: [person_a, person_b, relative] + state_code: NM + output: + # The age test applies to the filers (IRC 32(c)(1)(A)(ii)(II)), both + # above the federal maximum age of 64; the dependent aged 40 is within + # the age range but is not a filer, and is not a child. + nm_eitc_demographic_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml index afe322b813f..e0145ad405e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml @@ -37,3 +37,107 @@ filing_status: SINGLE output: nm_ctc: 75 + +- name: 2025 single filer who can be claimed as a dependent gets no child income tax credit + period: 2025 + input: + people: + parent: + age: 20 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NM + output: + # NMSA 7-2-18.34(A): "a taxpayer who is a resident and is not a dependent + # of another individual". + nm_ctc: 0 + +- name: 2025 joint return with one claimable spouse keeps the child income tax credit (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 2 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # "your spouse may still qualify to claim rebates or credits" (PIT-RC); + # AGI $10,000 is in the first bracket: $637 per qualifying child. + nm_ctc: 637 + +- name: 2025 joint return with one claimable spouse keeps the child income tax credit (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + claimed_as_dependent_on_another_return: false + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 2 + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NM + output: + nm_ctc: 637 + +- name: 2025 joint return with both spouses claimable gets no child income tax credit + period: 2025 + input: + people: + person_a: + age: 20 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + person_b: + age: 20 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + nm_ctc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml index f8563d24439..832af529754 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml @@ -249,3 +249,58 @@ state_code: NM output: nm_medical_expense_credit: 2_800 + +- name: 2025 a dependent aged 65 or older does not qualify the return + period: 2025 + input: + people: + person_a: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 40 + is_tax_unit_head: false + is_tax_unit_spouse: true + parent: + age: 70 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, parent] + filing_status: JOINT + itemized_medical_expenses: 30_000 + households: + household: + members: [person_a, person_b, parent] + state_code: NM + output: + # NMSA 7-2-18.13(A): "a taxpayer ... who is sixty-five years of age or + # older"; PIT-RC line 23: "If you or your spouse are 65 years of age or + # older". + nm_medical_expense_credit: 0 + +- name: 2025 joint return with a spouse aged 65 or older qualifies whichever spouse is the head + period: 2025 + input: + people: + person_b: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 70 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + itemized_medical_expenses: 30_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + nm_medical_expense_credit: 2_800 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.yaml index c819101fe40..9c40e8ef783 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.yaml @@ -51,3 +51,88 @@ nm_deduction_for_certain_dependents_eligible: true output: nm_deduction_for_certain_dependents: 16_000 + + +- name: 2025 joint return with one claimable spouse has no dependents to deduct (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + child_2: + age: 8 + tax_units: + tax_unit: + members: [person_a, person_b, child_1, child_2] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child_1, child_2] + state_code: NM + output: + # IRC 152(b)(1): such a return has no dependents (NMSA 7-2-39 defines + # dependent by IRC 152). + nm_deduction_for_certain_dependents: 0 + +- name: 2025 joint return with one claimable spouse has no dependents to deduct (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child_1: + age: 5 + child_2: + age: 8 + tax_units: + tax_unit: + members: [person_b, person_a, child_1, child_2] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child_1, child_2] + state_code: NM + output: + # IRC 152(b)(1): such a return has no dependents (NMSA 7-2-39 defines + # dependent by IRC 152). + nm_deduction_for_certain_dependents: 0 + +- name: 2025 joint return with neither spouse claimable deducts all but one dependent + period: 2025 + input: + people: + person_a: + age: 30 + person_b: + age: 30 + child_1: + age: 5 + child_2: + age: 8 + tax_units: + tax_unit: + members: [person_a, person_b, child_1, child_2] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child_1, child_2] + state_code: NM + output: + # $4,000 x (2 - 1). + nm_deduction_for_certain_dependents: 4_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.yaml index 9ed4d307148..8160a003512 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.yaml @@ -51,3 +51,110 @@ filing_status: SINGLE output: nm_hundred_year_exemption: 10_000 + + +- name: 2025 claimable spouse aged 100 or older gets no exemption (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 100 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # NMSA 7-2-5.7: an individual 100 or older "who is not a dependent of + # another individual". + nm_hundred_year_exemption: 0 + +- name: 2025 claimable spouse aged 100 or older gets no exemption (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 100 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # NMSA 7-2-5.7: an individual 100 or older "who is not a dependent of + # another individual". + nm_hundred_year_exemption: 0 + +- name: 2025 spouse aged 100 or older keeps the exemption when the other spouse can be claimed (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 100 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + # Half of the joint income, as for any one eligible spouse. + nm_hundred_year_exemption: 5_000 + +- name: 2025 spouse aged 100 or older keeps the exemption when the other spouse can be claimed (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 100 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + adjusted_gross_income: 10_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + # Half of the joint income, as for any one eligible spouse. + nm_hundred_year_exemption: 5_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml index 65264be9353..d1c6c67950a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml @@ -48,3 +48,81 @@ filing_status: SURVIVING_SPOUSE output: nm_medical_expense_exemption: 3_000 + +- name: 2025 a dependent aged 65 or older does not qualify the return for the exemption + period: 2025 + input: + people: + person_a: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 40 + is_tax_unit_head: false + is_tax_unit_spouse: true + parent: + age: 70 + is_tax_unit_head: false + is_tax_unit_spouse: false + tax_units: + tax_unit: + members: [person_a, person_b, parent] + filing_status: JOINT + itemized_medical_expenses: 30_000 + households: + household: + members: [person_a, person_b, parent] + state_code: NM + output: + # NMSA 7-2-5.9(A): "any individual sixty-five years of age or older"; + # PIT-ADJ line 18: "If you or your spouse are 65 years of age or older". + nm_medical_expense_exemption: 0 + +- name: 2025 joint return with a spouse aged 65 or older gets the exemption (the older spouse is the head) + period: 2025 + input: + people: + person_a: + age: 70 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 40 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + itemized_medical_expenses: 30_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + nm_medical_expense_exemption: 3_000 + +- name: 2025 joint return with a spouse aged 65 or older gets the exemption (the older spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 40 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 70 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + itemized_medical_expenses: 30_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + nm_medical_expense_exemption: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/nm_property_tax_rebate_eligibility.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/nm_property_tax_rebate_eligibility.yaml index d673a251d8f..f886a19850b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/nm_property_tax_rebate_eligibility.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/rebates/nm_property_tax_rebate_eligibility.yaml @@ -47,3 +47,124 @@ nm_modified_gross_income: 16_000 output: nm_property_tax_rebate_eligible: true + + +- name: 2025 single filer aged 65 or older who can be claimed as a dependent is ineligible + period: 2025 + input: + people: + filer: + age: 70 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [filer] + nm_modified_gross_income: 5_000 + households: + household: + members: [filer] + state_code: NM + output: + # NMSA 7-2-18: a resident 65 or over "who is not a dependent of another + # individual". + nm_property_tax_rebate_eligible: false + +- name: 2025 joint return whose only spouse aged 65 or older can be claimed is ineligible (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 70 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 30 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + nm_modified_gross_income: 5_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + nm_property_tax_rebate_eligible: false + +- name: 2025 joint return whose only spouse aged 65 or older can be claimed is ineligible (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 30 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 70 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + nm_modified_gross_income: 5_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + nm_property_tax_rebate_eligible: false + +- name: 2025 joint return whose spouse aged 65 or older cannot be claimed is eligible (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 70 + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + nm_modified_gross_income: 5_000 + households: + household: + members: [person_a, person_b] + state_code: NM + output: + nm_property_tax_rebate_eligible: true + +- name: 2025 joint return whose spouse aged 65 or older cannot be claimed is eligible (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 70 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 30 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [person_b, person_a] + filing_status: JOINT + nm_modified_gross_income: 5_000 + households: + household: + members: [person_b, person_a] + state_code: NM + output: + nm_property_tax_rebate_eligible: true diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.py index 14d728cf50b..34c1e2a375f 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/cdcc/nm_cdcc_eligible_child.py @@ -14,4 +14,10 @@ def formula(person, period, parameters): age = person("age", period) age_eligible = age < p.age_eligible dependent = person("is_tax_unit_dependent", period) - return age_eligible & dependent + # NMSA 7-2-18.1 counts a dependent as defined in IRC 152, and under + # IRC 152(b)(1) a return on which the filer (or, if joint, either + # spouse) can be claimed as a dependent has no dependents. + dependent_filer = person.tax_unit( + "head_or_spouse_is_dependent_elsewhere", period + ) + return age_eligible & dependent & ~dependent_filer diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.py index 743b732d797..cc49085a4ad 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/eitc/nm_eitc_demographic_eligible.py @@ -21,4 +21,16 @@ def formula(tax_unit, period, parameters): ) max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) meets_age_requirements = (age >= min_age) & (age <= max_age) - return has_child | tax_unit.any(meets_age_requirements) + # NMSA 7-2-18.15 takes the federal credit's eligibility rules apart + # from the age and identification exceptions. As federally (IRC + # 32(c)(1)(A)(ii)(II)), the age test applies to the filers, not to + # dependents. A filer without a qualifying child must not be a + # dependent of another taxpayer (IRC 32(c)(1)(A)(ii)(III)), and on a + # joint return neither spouse may be claimable (Publication 596, + # Rule 12). + filer = person("is_tax_unit_head_or_spouse", period) + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + childless_eligible = ( + tax_unit.any(meets_age_requirements & filer) & ~dependent_filer + ) + return has_child | childless_eligible diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py index 7295a240d3b..df105c86537 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py @@ -7,7 +7,10 @@ class nm_ctc(Variable): label = "New Mexico child income tax credit" definition_period = YEAR unit = USD - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503818/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcHYQEoANMmylCEAIqJCuAJ7QA5BskRCYXAiUr1WnXoMgAynlIAhdQCUAogBknANQCCAOQDCTyVIwACNoUnZxcSA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503818/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcHYQEoANMmylCEAIqJCuAJ7QA5BskRCYXAiUr1WnXoMgAynlIAhdQCUAogBknANQCCAOQDCTyVIwACNoUnZxcSA", + "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=10", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): @@ -25,4 +28,9 @@ def formula(tax_unit, period, parameters): filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE denominator = where(separate, 2, 1) - return amount / denominator + # NMSA 7-2-18.34(A) allows the credit to "a taxpayer who is a resident + # and is not a dependent of another individual". On a joint return a + # spouse who is not a dependent may still claim it (PIT-RC: "your + # spouse may still qualify to claim rebates or credits"). + every_filer_dependent = tax_unit("every_filer_is_dependent_elsewhere", period) + return where(every_filer_dependent, 0, amount / denominator) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py index 1ee85c8bfae..a094790e1d1 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py @@ -8,6 +8,7 @@ class nm_medical_expense_credit(Variable): definition_period = YEAR reference = ( "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503776/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgDswgGwBKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + # PDF pages 2, 9 "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=2", ) defined_for = StateCode.NM @@ -18,21 +19,19 @@ def formula(tax_unit, period, parameters): p = pcredits.unreimbursed_medical_care_expense age = person("age", period) medical_expense = tax_unit("itemized_medical_expenses", period) - aged = age >= p.age_eligibility expense_eligible = medical_expense >= p.min_expenses - # NMSA 7-2-18.13(A) allows the credit to a taxpayer "who is sixty-five - # years of age or older and who is not a dependent of another - # taxpayer", and the PIT-RC instructions let a spouse who is not a - # dependent claim it when the other spouse is. So when a filer is a - # dependent elsewhere, a filer who is not must be 65 or older. + # NMSA 7-2-18.13(A) allows the credit to "a taxpayer ... who is + # sixty-five years of age or older and who is not a dependent of + # another taxpayer", so both tests apply to the same filer: a + # dependent aged 65 or older does not qualify the return (PIT-RC line + # 23: "If you or your spouse are 65 years of age or older"), and on a + # joint return a spouse who is not a dependent may still qualify + # ("If you are a dependent with a spouse who was not a dependent of + # another taxpayer, your spouse may still qualify"). filer = person("is_tax_unit_head_or_spouse", period) - claimed = person("claimed_as_dependent_on_another_return", period) - independent_aged_filer = tax_unit.any(filer & ~claimed & aged) - dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) - age_eligible = where( - dependent_filer, independent_aged_filer, tax_unit.any(aged) - ) - eligible = age_eligible & expense_eligible + claimed_elsewhere = person("claimed_as_dependent_on_another_return", period) + qualifying_filer = filer & (age >= p.age_eligibility) & ~claimed_elsewhere + eligible = tax_unit.any(qualifying_filer) & expense_eligible # exemption is halved for married filing separately filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.py index 886cc8d6029..d6713d4d171 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/certain_dependents/nm_deduction_for_certain_dependents.py @@ -16,7 +16,10 @@ def formula(tax_unit, period, parameters): # The law 7-2-39(D) defines dependents as those from IRC 152. # IRC 152 refers to all dependents. # https://www.law.cornell.edu/uscode/text/26/152 - dependents = tax_unit("tax_unit_dependents", period) + # IRC 152(b)(1): a return on which the filer (or, if joint, either + # spouse) can be claimed as a dependent has no dependents. + dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + dependents = where(dependent_filer, 0, tax_unit("tax_unit_dependents", period)) # New Mexico reduces the number of claimable dependents by one. countable_dependents = max_(dependents - 1, 0) amount_per_dependent = p.amount[filing_status] diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.py index 68d8253cc04..a6bc609baf3 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_hundred_year_exemption.py @@ -14,8 +14,15 @@ def formula(tax_unit, period, parameters): age_head = tax_unit("age_head", period) age_spouse = tax_unit("age_spouse", period) p = parameters(period).gov.states.nm.tax.income.exemptions.hundred_year - head_eligible = age_head >= p.age_eligibility - spouse_eligible = age_spouse >= p.age_eligibility + # NMSA 7-2-5.7 exempts an individual 100 or older "who is not a + # dependent of another individual", so each spouse qualifies on their + # own. + head_eligible = (age_head >= p.age_eligibility) & ~tax_unit( + "head_is_dependent_elsewhere", period + ) + spouse_eligible = (age_spouse >= p.age_eligibility) & ~tax_unit( + "spouse_is_dependent_elsewhere", period + ) filing_status = tax_unit("filing_status", period) joint = filing_status == filing_status.possible_values.JOINT # Halve the exemption if only one of head and spouse is eligible of a joint filer. diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py index 3b5bed34f4a..dd02d6d3eb8 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py @@ -6,7 +6,10 @@ class nm_medical_expense_exemption(Variable): entity = TaxUnit label = "New Mexico unreimbursed medical expense care exemption" definition_period = YEAR - reference = "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503680/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgBsADh4BKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA" + reference = ( + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503680/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgBsADh4BKADTJspQhACKiQrgCe0AOSapEQmFwJlqjdt37DIAMp5SAIQ0AlAKIAZZwDUAggDkAws5SpGAARtCk7BISQA", + "https://klvg4oyd4j.execute-api.us-west-2.amazonaws.com/prod/PublicFiles/34821a9573ca43e7b06dfad20f5183fd/0558a902-6362-47ca-8e3b-7cca3bc69b9d/2025%20PIT%20Packet_Final.pdf#page=50", + ) defined_for = StateCode.NM def formula(tax_unit, period, parameters): @@ -16,7 +19,13 @@ def formula(tax_unit, period, parameters): ).gov.states.nm.tax.income.exemptions.unreimbursed_medical_care_expense age = person("age", period) medical_expense = tax_unit("itemized_medical_expenses", period) - age_eligible = tax_unit.any(age >= p.age_eligibility) + # NMSA 7-2-5.9(A) allows the exemption to "any individual sixty-five + # years of age or older" for expenses paid "for that individual or + # for the individual's spouse or dependent", and PIT-ADJ line 18 asks + # whether "you or your spouse are 65 years of age or older". A + # dependent aged 65 or older does not qualify the return. + filer = person("is_tax_unit_head_or_spouse", period) + age_eligible = tax_unit.any(filer & (age >= p.age_eligibility)) expense_eligible = medical_expense >= p.min_expenses eligible = age_eligible & expense_eligible # Exemption is halved for married filing separately diff --git a/policyengine_us/variables/gov/states/nm/tax/income/rebates/property_tax_rebate/nm_property_tax_rebate_eligible.py b/policyengine_us/variables/gov/states/nm/tax/income/rebates/property_tax_rebate/nm_property_tax_rebate_eligible.py index 5a84b11372c..02153417f15 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/rebates/property_tax_rebate/nm_property_tax_rebate_eligible.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/rebates/property_tax_rebate/nm_property_tax_rebate_eligible.py @@ -11,12 +11,18 @@ class nm_property_tax_rebate_eligible(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.nm.tax.income.rebates.property_tax - # Head or spouse eligible if 65 or over. + # NMSA 7-2-18 allows the rebate to a resident 65 or over "who is not + # a dependent of another individual", so the same filer must meet + # both tests; on a joint return either spouse may. age_head = tax_unit("age_head", period) age_spouse = tax_unit("age_spouse", period) - head_age_eligible = age_head >= p.age_eligibility - spouse_age_eligible = age_spouse >= p.age_eligibility - age_eligible = head_age_eligible | spouse_age_eligible + head_eligible = (age_head >= p.age_eligibility) & ~tax_unit( + "head_is_dependent_elsewhere", period + ) + spouse_eligible = (age_spouse >= p.age_eligibility) & ~tax_unit( + "spouse_is_dependent_elsewhere", period + ) + age_eligible = head_eligible | spouse_eligible # Person eligible if income at or below $16,000 agi = tax_unit("nm_modified_gross_income", period) agi_eligible = agi <= p.income_threshold From 9a27b3640625778ef04da406fa81dd247c2d0548 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 9 Oct 2026 12:12:25 -0400 Subject: [PATCH 9/9] Count New Mexico child credit children under IRC 152(c); do not halve the 65+ medical exemption for separate filers NMSA 7-2-18.34(J)(2) defines the child income tax credit's "qualifying child" by IRC 152(c), which has no identification number requirement, but nm_ctc counted eitc_child_count, which applies the EITC's Social Security number rule (IRC 32(c)(3)(D)). The new nm_ctc_qualifying_children counts 152(c) children whatever their Social Security number status, so a child with an ITIN now gets the credit. It also applies 152(c)(3)(A)'s rule that a child who is not disabled be younger than the filer (or either spouse on a joint return), which the shared EITC helper does not. NMSA 7-2-5.9 and the PIT-ADJ line 18 instructions give the $3,000 65+ medical care expense exemption with no halving for married filing separately; the halving sentence on PIT-ADJ belongs to the organ donation deduction on the next line (7-2-36(B)). The threshold stays at the PIT-ADJ "$28,000 or more"; the statute says expenses that "exceed" $28,000, a reading left for review. Co-Authored-By: Claude Opus 5.5 --- .../nm-ctc-152c-medical-exemption.fixed.md | 1 + ...c_children_medical_exemption_invariants.py | 377 ++++++++++++++++++ .../states/nm/tax/income/credits/nm_ctc.yaml | 79 +++- .../credits/nm_ctc_qualifying_children.yaml | 308 ++++++++++++++ .../nm_medical_expense_exemption.yaml | 42 +- .../states/nm/tax/income/credits/nm_ctc.py | 7 +- .../credits/nm_ctc_qualifying_children.py | 56 +++ .../nm_medical_expense_exemption.py | 15 +- 8 files changed, 869 insertions(+), 16 deletions(-) create mode 100644 changelog.d/nm-ctc-152c-medical-exemption.fixed.md create mode 100644 policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.yaml create mode 100644 policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.py diff --git a/changelog.d/nm-ctc-152c-medical-exemption.fixed.md b/changelog.d/nm-ctc-152c-medical-exemption.fixed.md new file mode 100644 index 00000000000..5efe5966da7 --- /dev/null +++ b/changelog.d/nm-ctc-152c-medical-exemption.fixed.md @@ -0,0 +1 @@ +Count New Mexico child income tax credit children under IRC 152(c), so a child with an ITIN qualifies, and stop halving New Mexico's 65+ medical care expense exemption for married filing separately. diff --git a/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py b/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py new file mode 100644 index 00000000000..96154868af8 --- /dev/null +++ b/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py @@ -0,0 +1,377 @@ +"""New Mexico child income tax credit children and the 65+ medical exemption. + +NMSA 7-2-18.34(J)(2) defines the child income tax credit's "qualifying child" +by IRC 152(c), which has no taxpayer identification number requirement, so +`nm_ctc_qualifying_children` counts 152(c) children whatever their Social +Security number status. NMSA 7-2-5.9 gives $3,000 to a filer or spouse 65 or +older with at least $28,000 of medical care expenses (the PIT-ADJ reading of +the threshold), with no halving for married filing separately. + +For households drawn by Hypothesis and a seeded population, in 2023-2026: + +1. Reference count: `nm_ctc_qualifying_children` equals an independent count + of the 152(c) tests the model records: a dependent who is the filer's + child (not a parent or grandparent) and is either permanently and totally + disabled, or younger than the older filer and under 19 or a full-time + student under 24. Outside New Mexico it is 0. +2. Superset: where every dependent is younger than the older filer, it is + never below the federal `eitc_child_count`, which adds the Social Security + number requirement. (The federal helper does not apply the relative-age + test of IRC 152(c)(3)(A), so the two counts are not ordered otherwise.) +3. Identification invariance: giving every dependent a Social Security + number changes neither the count nor `nm_ctc`. +4. Label invariance: exchanging the head and spouse labels changes neither + the count, `nm_ctc` nor `nm_medical_expense_exemption`. +5. Separate filing: a single adult filing separately gets half of `nm_ctc` + (7-2-18.34(G)) and the whole `nm_medical_expense_exemption`. +6. Reference exemption: `nm_medical_expense_exemption` is $3,000 exactly when + a filer (not a dependent) is 65 or older and medical expenses are at least + $28,000, and 0 otherwise. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +YEARS = [2023, 2024, 2025, 2026] +STATES = ["NM", "NM", "NM", "TX"] +SSN_CARD_TYPES = ["CITIZEN", "NON_CITIZEN_VALID_EAD", "OTHER_NON_CITIZEN", "NONE"] +RELATIONS = ["child", "child", "child", "parent", "grandparent"] +EXEMPTION = 3_000 +MIN_EXPENSES = 28_000 +TOLERANCE = 0.01 +# Each drawn unit is computed four times: as drawn; with every dependent +# given a Social Security number; with the head and spouse labels exchanged; +# and filing separately (a single adult only). +DRAWN, CITIZEN, SWAPPED, SEPARATE = range(4) + + +def money(high): + return st.one_of(st.just(0), st.integers(1, high)) + + +@st.composite +def adults(draw): + return { + "age": draw(st.integers(18, 90)), + "employment_income": draw(money(120_000)), + "claimed_as_dependent_on_another_return": draw(st.booleans()), + } + + +@st.composite +def dependents(draw): + relation = draw(st.sampled_from(RELATIONS)) + if relation == "child": + age = draw(st.integers(0, 30)) + else: + age = draw(st.integers(40, 95)) + return { + "relation": relation, + "age": age, + "is_full_time_student": draw(st.booleans()) if 18 <= age <= 25 else False, + "is_permanently_and_totally_disabled": draw( + st.booleans() if relation == "child" else st.just(False) + ), + "ssn_card_type": draw(st.sampled_from(SSN_CARD_TYPES)), + } + + +@st.composite +def units(draw): + return { + "state": draw(st.sampled_from(STATES)), + "adults": draw(st.lists(adults(), min_size=1, max_size=2)), + "dependents": draw(st.lists(dependents(), max_size=3)), + "medical_expenses": draw( + st.one_of( + st.sampled_from([27_999, 28_000, 28_001]), + money(60_000), + ) + ), + } + + +def _seeded_units(n, seed=20261009): + rng = np.random.default_rng(seed) + out = [] + for i in range(n): + drawn_adults = [] + for _ in range(int(rng.integers(1, 3))): + drawn_adults.append( + { + "age": int(rng.integers(18, 91)), + "employment_income": float(round(rng.uniform(0, 120_000))), + "claimed_as_dependent_on_another_return": bool(rng.random() < 0.2), + } + ) + drawn_dependents = [] + for _ in range(int(rng.integers(0, 4))): + relation = RELATIONS[int(rng.integers(len(RELATIONS)))] + age = int( + rng.integers(0, 31) if relation == "child" else rng.integers(40, 96) + ) + drawn_dependents.append( + { + "relation": relation, + "age": age, + "is_full_time_student": bool( + 18 <= age <= 25 and rng.random() < 0.5 + ), + "is_permanently_and_totally_disabled": bool( + relation == "child" and rng.random() < 0.1 + ), + "ssn_card_type": SSN_CARD_TYPES[ + int(rng.integers(len(SSN_CARD_TYPES))) + ], + } + ) + out.append( + { + "state": STATES[i % len(STATES)], + "adults": drawn_adults, + "dependents": drawn_dependents, + "medical_expenses": float( + [27_999, 28_000, 28_001][i % 3] + if i % 4 == 0 + else round(rng.uniform(0, 60_000)) + ), + } + ) + return out + + +def _crafted_units(): + """Edge cases the seeded draw rarely reaches.""" + + def adult(age, income=20_000.0): + return { + "age": age, + "employment_income": income, + "claimed_as_dependent_on_another_return": False, + } + + def dependent(age, student=False, disabled=False, ssn="OTHER_NON_CITIZEN"): + return { + "relation": "child", + "age": age, + "is_full_time_student": student, + "is_permanently_and_totally_disabled": disabled, + "ssn_card_type": ssn, + } + + def unit(adults, dependents, expenses=0.0): + return { + "state": "NM", + "adults": adults, + "dependents": dependents, + "medical_expenses": expenses, + } + + elderly_parent = {**dependent(70, ssn="CITIZEN"), "relation": "parent"} + return [ + # A student sibling older than, or as old as, the filer. + unit([adult(20)], [dependent(22, student=True)]), + unit([adult(20)], [dependent(20, student=True)]), + # A disabled sibling older than the filer. + unit([adult(20)], [dependent(30, disabled=True)]), + # Younger than the older spouse only. + unit([adult(25), adult(20)], [dependent(22, student=True)]), + # The only 65+ person is a dependent parent. + unit([adult(40)], [elderly_parent], expenses=30_000.0), + # A 65+ filer with exactly $28,000 of expenses. + unit([adult(70)], [], expenses=28_000.0), + ] + + +def _situation(drawn_units, year): + people, tax_units, households = {}, {}, {} + for i, unit in enumerate(drawn_units): + for copy in (DRAWN, CITIZEN, SWAPPED, SEPARATE): + members = [] + adult_list = list(unit["adults"]) + if copy == SWAPPED: + adult_list = adult_list[::-1] + for j, adult in enumerate(adult_list): + name = f"adult_{i}_{copy}_{j}" + people[name] = { + "age": adult["age"], + "employment_income": adult["employment_income"], + "claimed_as_dependent_on_another_return": adult[ + "claimed_as_dependent_on_another_return" + ], + "is_tax_unit_head": j == 0, + "is_tax_unit_spouse": j == 1, + "is_tax_unit_dependent": False, + "medicare_enrolled": False, + } + members.append(name) + people[members[0]]["other_medical_expenses"] = unit["medical_expenses"] + for j, dependent in enumerate(unit["dependents"]): + name = f"dependent_{i}_{copy}_{j}" + people[name] = { + "age": dependent["age"], + "is_full_time_student": dependent["is_full_time_student"], + "is_permanently_and_totally_disabled": dependent[ + "is_permanently_and_totally_disabled" + ], + "is_parent_of_filer_or_spouse": dependent["relation"] == "parent", + "is_grandparent_of_filer_or_spouse": dependent["relation"] + == "grandparent", + "ssn_card_type": ( + "CITIZEN" if copy == CITIZEN else dependent["ssn_card_type"] + ), + "is_tax_unit_head": False, + "is_tax_unit_spouse": False, + "is_tax_unit_dependent": True, + "medicare_enrolled": False, + } + members.append(name) + tax_unit = {"members": members} + if len(unit["adults"]) == 2: + tax_unit["filing_status"] = {year: "JOINT"} + elif copy == SEPARATE: + tax_unit["filing_status"] = {year: "SEPARATE"} + tax_units[f"tax_unit_{i}_{copy}"] = tax_unit + households[f"household_{i}_{copy}"] = { + "members": members, + "state_code": {year: unit["state"]}, + } + people = { + name: {k: {year: v} for k, v in values.items()} + for name, values in people.items() + } + return {"people": people, "tax_units": tax_units, "households": households} + + +def _reference_children(unit): + if unit["state"] != "NM": + return 0 + oldest_filer = max(a["age"] for a in unit["adults"]) + return sum( + d["relation"] == "child" + and ( + d["is_permanently_and_totally_disabled"] + or ( + d["age"] < oldest_filer + and (d["age"] < 19 or (d["is_full_time_student"] and d["age"] < 24)) + ) + ) + for d in unit["dependents"] + ) + + +def _reference_exemption(unit): + if unit["state"] != "NM": + return 0 + filer_65 = any(a["age"] >= 65 for a in unit["adults"]) + return EXEMPTION * (filer_65 and unit["medical_expenses"] >= MIN_EXPENSES) + + +def _describe(unit): + return ( + f"{unit['state']} adults={[(a['age'], a['claimed_as_dependent_on_another_return']) for a in unit['adults']]} " + f"dependents={[(d['relation'], d['age'], d['ssn_card_type']) for d in unit['dependents']]} " + f"expenses={unit['medical_expenses']}" + ) + + +def check_invariants(drawn_units, year): + sim = Simulation(situation=_situation(drawn_units, year)) + + def calc(name): + return np.asarray(sim.calculate(name, year), dtype=float).reshape(-1, 4) + + children = calc("nm_ctc_qualifying_children") + eitc_children = calc("eitc_child_count") + credit = calc("nm_ctc") + exemption = calc("nm_medical_expense_exemption") + for i, unit in enumerate(drawn_units): + where = f"{year} unit {i}: {_describe(unit)}" + single = len(unit["adults"]) == 1 + # 1. Reference count. + assert children[i, DRAWN] == _reference_children(unit), where + # 2. Superset of the EITC count where every dependent is younger than + # the older filer (the New Mexico count is 0 elsewhere). + oldest_filer = max(a["age"] for a in unit["adults"]) + if unit["state"] == "NM" and all( + d["age"] < oldest_filer for d in unit["dependents"] + ): + assert children[i, DRAWN] >= eitc_children[i, DRAWN], where + # 3. Identification invariance. + assert children[i, CITIZEN] == children[i, DRAWN], where + assert abs(credit[i, CITIZEN] - credit[i, DRAWN]) <= TOLERANCE, where + # 4. Label invariance. + assert children[i, SWAPPED] == children[i, DRAWN], where + assert abs(credit[i, SWAPPED] - credit[i, DRAWN]) <= TOLERANCE, where + assert abs(exemption[i, SWAPPED] - exemption[i, DRAWN]) <= TOLERANCE, where + # 5. Separate filing. + if single: + assert abs(credit[i, SEPARATE] - credit[i, DRAWN] / 2) <= TOLERANCE, where + assert abs(exemption[i, SEPARATE] - exemption[i, DRAWN]) <= TOLERANCE, where + # 6. Reference exemption. + assert abs(exemption[i, DRAWN] - _reference_exemption(unit)) <= TOLERANCE, where + return credit[:, DRAWN], exemption[:, DRAWN] + + +def _assert_coverage(drawn_units, credit, exemption): + """The seeded population reaches the cases the invariants are about.""" + single = np.array([len(u["adults"]) == 1 for u in drawn_units]) + child_without_ssn = np.array( + [ + any( + d["relation"] == "child" + and d["age"] < 19 + and d["ssn_card_type"] in ("OTHER_NON_CITIZEN", "NONE") + for d in u["dependents"] + ) + for u in drawn_units + ] + ) + elderly_dependent_only = np.array( + [ + u["state"] == "NM" + and all(a["age"] < 65 for a in u["adults"]) + and any(d["age"] >= 65 for d in u["dependents"]) + and u["medical_expenses"] >= MIN_EXPENSES + for u in drawn_units + ] + ) + not_younger_than_filers = np.array( + [ + any( + d["relation"] == "child" + and not d["is_permanently_and_totally_disabled"] + and d["age"] >= max(a["age"] for a in u["adults"]) + and (d["age"] < 19 or (d["is_full_time_student"] and d["age"] < 24)) + for d in u["dependents"] + ) + for u in drawn_units + ] + ) + assert (child_without_ssn & (credit > 0)).any() + assert not_younger_than_filers.any() + assert (single & (credit > 0)).any() + assert (single & (exemption > 0)).any() + assert elderly_dependent_only.any() + + +@settings( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.sampled_from(YEARS), st.lists(units(), min_size=8, max_size=20)) +def test_nm_ctc_children_and_medical_exemption_hypothesis(year, drawn_units): + check_invariants(drawn_units, year) + + +@pytest.mark.parametrize("year", YEARS) +def test_nm_ctc_children_and_medical_exemption_seeded_population(year): + drawn_units = _seeded_units(40) + _crafted_units() + credit, exemption = check_invariants(drawn_units, year) + _assert_coverage(drawn_units, credit, exemption) diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml index e0145ad405e..0b02490d17b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc.yaml @@ -2,7 +2,7 @@ period: 2023 input: state_code: NM - eitc_child_count: 0 + nm_ctc_qualifying_children: 0 adjusted_gross_income: 10_000 filing_status: JOINT output: @@ -12,7 +12,7 @@ period: 2023 input: state_code: NM - eitc_child_count: 2 + nm_ctc_qualifying_children: 2 adjusted_gross_income: 50_000 filing_status: JOINT output: @@ -22,7 +22,7 @@ period: 2023 input: state_code: NM - eitc_child_count: 2 + nm_ctc_qualifying_children: 2 adjusted_gross_income: 50_000 filing_status: SEPARATE output: @@ -32,7 +32,7 @@ period: 2023 input: state_code: NM - eitc_child_count: 3 + nm_ctc_qualifying_children: 3 adjusted_gross_income: 350_001 filing_status: SINGLE output: @@ -141,3 +141,74 @@ state_code: NM output: nm_ctc: 0 + +- name: 2025 independent filer with an otherwise qualifying child who has an ITIN gets the child income tax credit + period: 2025 + input: + people: + parent: + age: 30 + employment_income: 20_000 + child: + age: 5 + # An ITIN holder has no valid Social Security number. + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NM + output: + # The federal EITC needs a Social Security number (IRC 32(c)(3)(D)). + eitc_child_count: 0 + # NMSA 7-2-18.34(J)(2) adopts the IRC 152(c) definition, which has no + # identification number requirement. + nm_ctc_qualifying_children: 1 + # AGI $20,000 is in the first 2025 row (PIT-RC Table 4): $637 per child. + nm_ctc: 637 + +- name: 2025 independent filer with a qualifying child who has a Social Security number gets the same credit + period: 2025 + input: + people: + parent: + age: 30 + employment_income: 20_000 + child: + age: 5 + ssn_card_type: CITIZEN + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NM + output: + eitc_child_count: 1 + nm_ctc_qualifying_children: 1 + nm_ctc: 637 + +- name: 2025 married couple filing separately with an ITIN child gets half the credit + period: 2025 + input: + people: + parent: + age: 30 + employment_income: 20_000 + child: + age: 5 + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [parent, child] + filing_status: SEPARATE + households: + household: + members: [parent, child] + state_code: NM + output: + # NMSA 7-2-18.34(G): each may claim "only one-half"; 637 / 2. + nm_ctc: 318.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.yaml new file mode 100644 index 00000000000..88f708e73a8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.yaml @@ -0,0 +1,308 @@ +- name: 2025 a child with an ITIN counts although the federal EITC does not count them + period: 2025 + input: + people: + parent: + age: 30 + child: + age: 5 + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NM + output: + eitc_child_count: 0 + # NMSA 7-2-18.34(J)(2): "qualifying child" as defined by IRC 152(c). + nm_ctc_qualifying_children: 1 + +- name: 2025 a child without a Social Security number card counts + period: 2025 + input: + people: + parent: + age: 30 + child: + age: 5 + ssn_card_type: NONE + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: NM + output: + eitc_child_count: 0 + # IRC 152(c) has no identification number requirement. + nm_ctc_qualifying_children: 1 + +- name: 2025 age test of IRC 152(c)(3) - under 19, a full-time student under 24, or permanently and totally disabled + period: 2025 + input: + people: + parent: + age: 50 + child_18: + age: 18 + is_tax_unit_dependent: true + child_19: + age: 19 + is_tax_unit_dependent: true + student_23: + age: 23 + is_full_time_student: true + is_tax_unit_dependent: true + student_24: + age: 24 + is_full_time_student: true + is_tax_unit_dependent: true + disabled_30: + age: 30 + is_permanently_and_totally_disabled: true + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child_18, child_19, student_23, student_24, disabled_30] + households: + household: + members: [parent, child_18, child_19, student_23, student_24, disabled_30] + state_code: NM + output: + # The 18-year-old, the 23-year-old student and the disabled 30-year-old. + nm_ctc_qualifying_children: 3 + +- name: 2025 a dependent parent is not a qualifying child (IRC 152(c)(2) relationship test) + period: 2025 + input: + people: + filer: + age: 40 + is_tax_unit_dependent: false + grandmother: + age: 70 + is_tax_unit_dependent: true + is_parent_of_filer_or_spouse: true + child: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [filer, grandmother, child] + households: + household: + members: [filer, grandmother, child] + state_code: NM + output: + nm_ctc_qualifying_children: 1 + +- name: 2025 children still count on a joint return with one claimable spouse (the claimable spouse is the head) + period: 2025 + input: + people: + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: true + is_tax_unit_spouse: false + person_b: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 2 + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [person_a, person_b, child] + filing_status: JOINT + households: + household: + members: [person_a, person_b, child] + state_code: NM + output: + # 7-2-18.34 counts IRC 152(c) qualifying children, not dependents, so + # the IRC 152(b)(1) rule that a claimable filer has no dependents does + # not remove the child. + nm_ctc_qualifying_children: 1 + +- name: 2025 children still count on a joint return with one claimable spouse (the claimable spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + person_a: + age: 20 + claimed_as_dependent_on_another_return: true + is_tax_unit_head: false + is_tax_unit_spouse: true + child: + age: 2 + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [person_b, person_a, child] + filing_status: JOINT + households: + household: + members: [person_b, person_a, child] + state_code: NM + output: + nm_ctc_qualifying_children: 1 + +- name: 2025 no New Mexico count outside New Mexico + period: 2025 + input: + people: + parent: + age: 30 + child: + age: 5 + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: TX + output: + nm_ctc_qualifying_children: 0 + +- name: 2025 a student sibling older than the filer is not a qualifying child (IRC 152(c)(3)(A)) + period: 2025 + input: + people: + filer: + age: 20 + employment_income: 20_000 + is_tax_unit_dependent: false + sibling: + age: 22 + is_full_time_student: true + ssn_card_type: OTHER_NON_CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [filer, sibling] + households: + household: + members: [filer, sibling] + state_code: NM + output: + # The child must be "younger than the taxpayer claiming such individual + # as a qualifying child". + nm_ctc_qualifying_children: 0 + nm_ctc: 0 + +- name: 2025 a student sibling the same age as the filer is not a qualifying child + period: 2025 + input: + people: + filer: + age: 20 + is_tax_unit_dependent: false + sibling: + age: 20 + is_full_time_student: true + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [filer, sibling] + households: + household: + members: [filer, sibling] + state_code: NM + output: + nm_ctc_qualifying_children: 0 + +- name: 2025 a permanently and totally disabled sibling older than the filer is a qualifying child (IRC 152(c)(3)(B)) + period: 2025 + input: + people: + filer: + age: 20 + is_tax_unit_dependent: false + sibling: + age: 30 + is_permanently_and_totally_disabled: true + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [filer, sibling] + households: + household: + members: [filer, sibling] + state_code: NM + output: + # 152(c)(3)(B): "the requirements of subparagraph (A) shall be treated as + # met". + nm_ctc_qualifying_children: 1 + +- name: 2025 on a joint return a child younger than either spouse qualifies (the older spouse is the head) + period: 2025 + input: + people: + person_a: + age: 25 + is_tax_unit_head: true + is_tax_unit_spouse: false + is_tax_unit_dependent: false + person_b: + age: 20 + is_tax_unit_head: false + is_tax_unit_spouse: true + is_tax_unit_dependent: false + sibling: + age: 22 + is_full_time_student: true + is_tax_unit_head: false + is_tax_unit_spouse: false + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person_a, person_b, sibling] + filing_status: JOINT + households: + household: + members: [person_a, person_b, sibling] + state_code: NM + output: + # Publication 596: "younger than you (or your spouse if filing jointly)". + nm_ctc_qualifying_children: 1 + +- name: 2025 on a joint return a child younger than either spouse qualifies (the older spouse is the spouse) + period: 2025 + input: + people: + person_b: + age: 20 + is_tax_unit_head: true + is_tax_unit_spouse: false + is_tax_unit_dependent: false + person_a: + age: 25 + is_tax_unit_head: false + is_tax_unit_spouse: true + is_tax_unit_dependent: false + sibling: + age: 22 + is_full_time_student: true + is_tax_unit_head: false + is_tax_unit_spouse: false + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person_b, person_a, sibling] + filing_status: JOINT + households: + household: + members: [person_b, person_a, sibling] + state_code: NM + output: + nm_ctc_qualifying_children: 1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml index d1c6c67950a..ab7a5ea77f6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml @@ -29,7 +29,7 @@ output: nm_medical_expense_exemption: 3_000 -- name: Eligible - halved for separate filers +- name: Eligible - not halved for separate filers period: 2023 input: state_code: NM @@ -37,7 +37,45 @@ other_medical_expenses: 28_000 filing_status: SEPARATE output: - nm_medical_expense_exemption: 1_500 + # Neither NMSA 7-2-5.9 nor the PIT-ADJ line 18 instructions halve the + # exemption for married filing separately. + nm_medical_expense_exemption: 3_000 + +- name: 2025 married filing separately gets the full exemption + period: 2025 + input: + state_code: NM + age: 70 + medicare_enrolled: false + other_medical_expenses: 30_000 + filing_status: SEPARATE + output: + # PIT-ADJ line 18: "enter $3,000 on line 18 to claim the exemption". + nm_medical_expense_exemption: 3_000 + +- name: 2025 expenses of exactly $28,000 qualify under the PIT-ADJ instructions + period: 2025 + input: + state_code: NM + age: 70 + medicare_enrolled: false + other_medical_expenses: 28_000 + filing_status: SINGLE + output: + # PIT-ADJ line 18: expenses "of $28,000 or more". NMSA 7-2-5.9(A) says + # expenses that "exceed" $28,000, which would give 0 here. + nm_medical_expense_exemption: 3_000 + +- name: 2025 expenses one dollar under $28,000 do not qualify + period: 2025 + input: + state_code: NM + age: 70 + medicare_enrolled: false + other_medical_expenses: 27_999 + filing_status: SINGLE + output: + nm_medical_expense_exemption: 0 - name: Output always $3,000 period: 2023 diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py index df105c86537..672fc48c5ac 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc.py @@ -17,10 +17,9 @@ def formula(tax_unit, period, parameters): # Legal code refers to "adjusted gross income", # Tax form does not specify if federal or state AGI agi = tax_unit("adjusted_gross_income", period) - # The law 7-2-18.34(J)(2) defines qualifying children as those from IRC 152(c). - # IRC 152(c) refers to the EITC qualifying children. - # https://www.law.cornell.edu/uscode/text/26/152#c - children = tax_unit("eitc_child_count", period) + # NMSA 7-2-18.34(J)(2) defines qualifying children by IRC 152(c), + # without the Social Security number requirement of the federal EITC. + children = tax_unit("nm_ctc_qualifying_children", period) p = parameters(period).gov.states.nm.tax.income.credits.ctc amount_per_child = p.amount.calc(agi, right=True) amount = amount_per_child * children diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.py new file mode 100644 index 00000000000..86d3b8ff90e --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_ctc_qualifying_children.py @@ -0,0 +1,56 @@ +from policyengine_us.model_api import * + + +class nm_ctc_qualifying_children(Variable): + value_type = int + entity = TaxUnit + label = "New Mexico child income tax credit qualifying children" + documentation = """ + Number of qualifying children for the New Mexico child income tax credit. + NMSA 7-2-18.34(J)(2) adopts the IRC 152(c) qualifying-child definition, + which has no taxpayer identification number requirement, so a child with + an ITIN (or no Social Security number) counts here although the federal + EITC (IRC 32(c)(3)(D)) does not count them. A child who is not disabled + must also be younger than the filer, or than either spouse on a joint + return (IRC 152(c)(3)(A)). + """ + definition_period = YEAR + reference = ( + # NMSA 7-2-18.34(J)(2) defines "qualifying child" by IRC 152(c). + "https://nmonesource.com/nmos/nmsa/en/item/4340/index.do#!fragment/zoupio-_Toc140503818/BQCwhgziBcwMYgK4DsDWszIQewE4BUBTADwBdoAvbRABwEtsBaAfX2zgEYAWABgFYeAZgAcHYQEoANMmylCEAIqJCuAJ7QA5BskRCYXAiUr1WnXoMgAynlIAhdQCUAogBknANQCCAOQDCTyVIwACNoUnZxcSA", + "https://www.law.cornell.edu/uscode/text/26/152#c", + "https://realfile.tax.newmexico.gov/2025pit-rc-ins.pdf#page=10", + # Publication 596, qualifying child age test: "younger than you (or + # your spouse if filing jointly)". PDF pages 10, 12. + "https://www.irs.gov/pub/irs-prior/p596--2025.pdf#page=10", + ) + defined_for = StateCode.NM + + def formula(tax_unit, period, parameters): + person = tax_unit.members + # NMSA 7-2-18.34(J)(2): "qualifying child" means "qualifying child" as + # defined by IRC 152(c), and also a minor child or stepchild who would + # qualify if public assistance toward their support were counted as + # the taxpayer's. is_eitc_qualifying_child applies the 152(c) tests + # the model records (relationship, and age or permanent and total + # disability) and leaves out the EITC identification requirement of + # IRC 32(c)(3)(D), which 152(c) does not contain. The model does not + # record the 152(c)(1)(D) own-support test, so every dependent who + # passes the other tests is treated as meeting it. + # A filer who can be claimed elsewhere is treated as having no + # dependents (IRC 152(b)(1)), but 7-2-18.34 counts 152(c) qualifying + # children rather than dependents, so the count does not drop to zero + # here; nm_ctc applies the claimant test of 7-2-18.34(A). + qualifying_child = person("is_eitc_qualifying_child", period) + # IRC 152(c)(3)(A) also requires the child to be "younger than the + # taxpayer claiming such individual as a qualifying child"; on a joint + # return, younger than either spouse (Publication 596). 152(c)(3)(B) + # treats the requirement as met for a permanently and totally + # disabled child. + age = person("age", period) + filer = person("is_tax_unit_head_or_spouse", period) + oldest_filer_age = tax_unit.max(where(filer, age, 0)) + younger_than_a_filer = age < tax_unit.project(oldest_filer_age) + disabled = person("is_permanently_and_totally_disabled", period) + meets_relative_age_test = younger_than_a_filer | disabled + return tax_unit.sum(qualifying_child & meets_relative_age_test) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py index dd02d6d3eb8..e0e0a081804 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py @@ -26,11 +26,14 @@ def formula(tax_unit, period, parameters): # dependent aged 65 or older does not qualify the return. filer = person("is_tax_unit_head_or_spouse", period) age_eligible = tax_unit.any(filer & (age >= p.age_eligibility)) + # NMSA 7-2-5.9(A) requires expenses that "exceed twenty-eight thousand + # dollars ($28,000)"; PIT-ADJ line 18 instructions allow expenses "of + # $28,000 or more". This keeps the instructions' reading, under which + # expenses of exactly $28,000 qualify, until the conflict is resolved. expense_eligible = medical_expense >= p.min_expenses eligible = age_eligible & expense_eligible - # Exemption is halved for married filing separately - filing_status = tax_unit("filing_status", period) - separate = filing_status == filing_status.possible_values.SEPARATE - denominator = where(separate, 2, 1) - numerator = eligible * p.amount - return numerator / denominator + # Neither 7-2-5.9 nor the PIT-ADJ line 18 instructions halve the + # exemption for married individuals filing separately. Sections that + # do, such as the 65+ medical care credit (7-2-18.13(B)) and the organ + # donation deduction (7-2-36(B), the next PIT-ADJ line), say so. + return eligible * p.amount