diff --git a/changelog.d/fix-2020-charitable-ald.fixed.md b/changelog.d/fix-2020-charitable-ald.fixed.md new file mode 100644 index 00000000000..13e1235cfb5 --- /dev/null +++ b/changelog.d/fix-2020-charitable-ald.fixed.md @@ -0,0 +1 @@ +Take the 2020 charitable deduction for non-itemizers (IRC 62(a)(22), Form 1040 line 10b) above the line, capped at $300 for every return as the statute reads, so it reduces adjusted gross income; stop adding the 2020 and 2021 non-itemizer amount to itemized deductions; include the non-itemizer cash charitable deduction in itemization comparisons; and add it back in states that did not adopt it. diff --git a/policyengine_us/parameters/gov/irs/ald/charitable_contributions/cap.yaml b/policyengine_us/parameters/gov/irs/ald/charitable_contributions/cap.yaml new file mode 100644 index 00000000000..14dbd30b59d --- /dev/null +++ b/policyengine_us/parameters/gov/irs/ald/charitable_contributions/cap.yaml @@ -0,0 +1,47 @@ +description: The IRS caps the above-the-line deduction for cash charitable contributions by individuals who do not itemize at this amount, based on filing status. +metadata: + propagate_metadata_to_children: true + unit: currency-USD + period: year + label: Above-the-line charitable deduction cap for non-itemizers + breakdown: + - filing_status + reference: + # CARES Act sec. 2204 added 26 U.S.C. 62(a)(22) and 62(f) for taxable + # years beginning in 2020: "the amount (not to exceed $300) of qualified + # charitable contributions made by an eligible individual". + - title: Pub. L. 116-136, sec. 2204 (CARES Act) + href: https://www.congress.gov/116/plaws/publ136/PLAW-116publ136.pdf#page=65 + # Pub. L. 116-260, div. EE, sec. 212(c) struck 62(a)(22) and 62(f) for + # taxable years beginning after 2020; sec. 212(a) replaced them with the + # below-the-line 26 U.S.C. 170(p). + - title: "Pub. L. 116-260, div. EE, sec. 212 (Consolidated Appropriations Act, 2021) #page 1888-1889" + href: https://www.congress.gov/116/plaws/publ260/PLAW-116publ260.pdf#page=1888 + # Source conflict: $300 statutory cap versus $150 for separate filers. + # Form 1040 line 10b allows $300 a return. The instructions' $150 for + # married filing separately has no basis in 62(a)(22) or 62(f), which + # cap the deduction at $300 for every eligible individual, so the + # statute governs (PolicyEngine statute-first rule, 2026-10-19). + - title: 2020 Instructions for Form 1040, line 10b + href: https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=30 + +SINGLE: + 2000-01-01: 0 + 2020-01-01: 300 + 2021-01-01: 0 +JOINT: + 2000-01-01: 0 + 2020-01-01: 300 + 2021-01-01: 0 +SEPARATE: + 2000-01-01: 0 + 2020-01-01: 300 + 2021-01-01: 0 +HEAD_OF_HOUSEHOLD: + 2000-01-01: 0 + 2020-01-01: 300 + 2021-01-01: 0 +SURVIVING_SPOUSE: + 2000-01-01: 0 + 2020-01-01: 300 + 2021-01-01: 0 diff --git a/policyengine_us/parameters/gov/irs/ald/deductions.yaml b/policyengine_us/parameters/gov/irs/ald/deductions.yaml index a2ecf812a21..d470d7041c9 100644 --- a/policyengine_us/parameters/gov/irs/ald/deductions.yaml +++ b/policyengine_us/parameters/gov/irs/ald/deductions.yaml @@ -33,6 +33,25 @@ values: - us_bonds_for_higher_ed - specified_possession_income - puerto_rico_income + # 26 U.S.C. 62(a)(22) (CARES Act sec. 2204) adds the charitable deduction + # for non-itemizers in 2020 only. + 2020-01-01: + - loss_ald + - self_employment_tax_ald + - student_loan_interest_ald + - early_withdrawal_penalty + - alimony_expense_ald + - educator_expense_ald + - capped_qualified_tuition_expenses_ald + - health_savings_account_ald + - self_employed_health_insurance_ald + - self_employed_pension_contribution_ald + - traditional_ira_deduction + - qualified_adoption_assistance_expense + - us_bonds_for_higher_ed + - specified_possession_income + - puerto_rico_income + - charitable_contributions_ald 2021-01-01: - loss_ald - self_employment_tax_ald diff --git a/policyengine_us/parameters/gov/irs/deductions/deductions_if_not_itemizing.yaml b/policyengine_us/parameters/gov/irs/deductions/deductions_if_not_itemizing.yaml index 454beae177b..fc58295ca71 100644 --- a/policyengine_us/parameters/gov/irs/deductions/deductions_if_not_itemizing.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/deductions_if_not_itemizing.yaml @@ -4,7 +4,13 @@ values: - standard_deduction - qualified_business_income_deduction - tuition_and_fees_deduction + # The 2020 charitable deduction for non-itemizers is above the line + # (charitable_contributions_ald). From 2021 it is taken from adjusted gross + # income under 26 U.S.C. 170(p) and 63(b)(4). 2020-01-01: + - standard_deduction + - qualified_business_income_deduction + 2021-01-01: - charitable_deduction_for_non_itemizers - standard_deduction - qualified_business_income_deduction diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/charity/non_itemizers_amount.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/charity/non_itemizers_amount.yaml index 7b100b2c0d6..e95802db011 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized/charity/non_itemizers_amount.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/charity/non_itemizers_amount.yaml @@ -7,11 +7,8 @@ metadata: breakdown: - filing_status reference: - - title: 2020 Publication 526 Charitable Contributions - href: https://www.irs.gov/pub/irs-prior/p526--2020.pdf#page=2 - title: 2021 Publication 526 Charitable Contributions href: https://www.irs.gov/pub/irs-prior/p526--2021.pdf#page=2 - # The legal code does not describe the 2020 deduction values - title: 26 U.S. Code § 170 - Charitable, etc., contributions and gifts - (p) href: https://www.law.cornell.edu/uscode/text/26/170 - title: Expanded tax benefits help individuals and businesses give to charity during 2021; deductions up to $600 available for cash donations by non-itemizers @@ -19,37 +16,30 @@ metadata: - title: H.R.1 - One Big Beautiful Bill Act href: https://www.congress.gov/bill/119th-congress/house-bill/1/text +# 26 U.S.C. 170(p) starts in 2021. The 2020 deduction for non-itemizers was +# above the line (26 U.S.C. 62(a)(22)); see gov.irs.ald.charitable_contributions. JOINT: 2018-01-01: 0 - 2020-01-01: 300 2021-01-01: 600 2022-01-01: 0 2026-01-01: 2_000 SINGLE: 2018-01-01: 0 - # CARES Act §2204: $300 cap for non-itemizing individuals in 2020 - # (IRS Pub. 526 (2020), p. 2). - 2020-01-01: 300 2021-01-01: 300 2022-01-01: 0 2026-01-01: 1_000 SEPARATE: 2018-01-01: 0 - # MFS 2020 is $150 per IRS guidance — a MFS couple shares the $300 - # joint cap ($150 each). - 2020-01-01: 150 2021-01-01: 300 2022-01-01: 0 2026-01-01: 1_000 HEAD_OF_HOUSEHOLD: 2018-01-01: 0 - 2020-01-01: 300 2021-01-01: 300 2022-01-01: 0 2026-01-01: 1_000 SURVIVING_SPOUSE: 2018-01-01: 0 - 2020-01-01: 300 2021-01-01: 300 2022-01-01: 0 2026-01-01: 1_000 diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml index 14d369f6999..55972586fa8 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized_deductions.yaml @@ -7,21 +7,6 @@ values: - medical_expense_deduction - casualty_loss_deduction - misc_deduction - 2020-01-01: - - charitable_deduction - - charitable_deduction_for_non_itemizers - - interest_deduction - - salt_deduction - - medical_expense_deduction - - casualty_loss_deduction - - misc_deduction - 2022-01-01: - - charitable_deduction - - interest_deduction - - salt_deduction - - medical_expense_deduction - - casualty_loss_deduction - - misc_deduction metadata: unit: list label: itemized deductions diff --git a/policyengine_us/parameters/gov/states/ma/tax/income/ald/disallowed.yaml b/policyengine_us/parameters/gov/states/ma/tax/income/ald/disallowed.yaml index 7016ba2a218..0016a94c8cd 100644 --- a/policyengine_us/parameters/gov/states/ma/tax/income/ald/disallowed.yaml +++ b/policyengine_us/parameters/gov/states/ma/tax/income/ald/disallowed.yaml @@ -11,11 +11,15 @@ values: - early_withdrawal_penalty # (h) - self_employment_tax_ald - alimony_income # is a US ALD only in 2018 through 2025 + # Massachusetts follows Code 62(a) as in effect on January 1, 2005, so + # it does not adopt 62(a)(22) (TIR 20-9, B(6)). + - charitable_contributions_ald 2026-01-01: - loss_ald # (c) - traditional_ira_deduction # (f) - early_withdrawal_penalty # (h) - self_employment_tax_ald + - charitable_contributions_ald metadata: unit: list period: year @@ -23,3 +27,5 @@ metadata: reference: - title: Mass. General Laws c.62 § 2(d)(1)(A) href: https://www.mass.gov/info-details/mass-general-laws-c62-ss-2 + - title: Massachusetts DOR TIR 20-9, Massachusetts tax implications of the CARES Act, B(6) + href: https://archives.lib.state.ma.us/server/api/core/bitstreams/84d51bb2-c47f-4aa5-908a-b534fa72cabe/content#page=6 diff --git a/policyengine_us/parameters/gov/states/mi/tax/income/household_resources_adjustments.yaml b/policyengine_us/parameters/gov/states/mi/tax/income/household_resources_adjustments.yaml index 94637b89d28..d0b0322d0ba 100644 --- a/policyengine_us/parameters/gov/states/mi/tax/income/household_resources_adjustments.yaml +++ b/policyengine_us/parameters/gov/states/mi/tax/income/household_resources_adjustments.yaml @@ -7,6 +7,20 @@ values: # items) and the exclusions from gross income under IRC 135, 137, 931 and # 933 (us_bonds_for_higher_ed, qualified_adoption_assistance_expense, # specified_possession_income and puerto_rico_income). + # In 2020 the charitable deduction for non-itemizers (U.S. Form 1040 line + # 10b) is a deduction from adjusted gross income "and therefore Total + # Household Resources as well" (Michigan Treasury FAQ). + 2020-01-01: + - educator_expense_ald # line 11, capped per eligible educator + - health_savings_account_ald # line 13 + - self_employment_tax_ald # line 15 + - self_employed_pension_contribution_ald # line 16 + - self_employed_health_insurance_ald # line 17 + - early_withdrawal_penalty # line 18 + - alimony_expense_ald # line 19a + - traditional_ira_deduction # line 20, the deductible amount + - student_loan_interest_ald # line 21 + - charitable_contributions_ald 2021-01-01: - educator_expense_ald # line 11, capped per eligible educator - health_savings_account_ald # line 13 @@ -22,6 +36,10 @@ metadata: period: year label: Michigan household resources adjustments reference: + - title: Michigan 2020 Individual Income Tax Instructions, MI-1040CR line 30 + href: https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2020/Book_MI-1040_instructions_only.pdf#page=28 + - title: Michigan Treasury FAQ, charitable contribution deductions from AGI and total household resources (2020 and 2021) + href: https://www.michigan.gov/taxes/questions/iit/accordion/covid19-cares/charitable-contribution-deduct556422 - title: Michigan 2024 Individual Income Tax Instructions, MI-1040CR line 30 href: https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2024/MI-1040-Instructions.pdf#page=33 - title: Michigan 2025 Individual Income Tax Instructions, MI-1040CR line 30 diff --git a/policyengine_us/parameters/gov/states/mn/tax/income/additions/sources.yaml b/policyengine_us/parameters/gov/states/mn/tax/income/additions/sources.yaml index e911bf74ac2..38dc559a470 100644 --- a/policyengine_us/parameters/gov/states/mn/tax/income/additions/sources.yaml +++ b/policyengine_us/parameters/gov/states/mn/tax/income/additions/sources.yaml @@ -1,5 +1,12 @@ description: Minnesota adds these variables to federal adjusted gross income when computing its taxable income. values: + # 2020 Schedule M1NC line 6 adds back the federal charitable deduction for + # non-itemizers (CARES Act sec. 2204), through Form M1M line 16: Laws 2023, + # ch. 1, sec. 22(c)(2), effective retroactively for 2020. + 2020-01-01: + - form_4972_lumpsum_distributions + - charitable_contributions_ald + - mn_other_additions 2021-01-01: - form_4972_lumpsum_distributions - mn_other_additions @@ -9,6 +16,10 @@ metadata: period: year label: Minnesota additions to federal AGI reference: + - title: 2020 Minnesota Schedule M1NC, Federal Adjustments + href: https://www.revenue.state.mn.us/sites/default/files/2024-02/m1nc_20.pdf#page=1 + - title: Laws of Minnesota 2023, chapter 1, section 22 (temporary additions and subtractions) + href: https://www.revisor.mn.gov/laws/2023/0/Session+Law/Chapter/1/ - title: 2021 Minnesota Schedule M1M, Income Additions and Subtractions, Page 3 href: https://www.taxformfinder.org/forms/2021/2021-minnesota-form-m1m.pdf#page=3 - title: 2022 Minnesota Schedule M1M, Income Additions and Subtractions, Page 3 diff --git a/policyengine_us/tests/code_health/test_source_conflicts.py b/policyengine_us/tests/code_health/test_source_conflicts.py index 25311fb01e9..901b7b37215 100644 --- a/policyengine_us/tests/code_health/test_source_conflicts.py +++ b/policyengine_us/tests/code_health/test_source_conflicts.py @@ -116,6 +116,7 @@ # or reword a comment that is not one, then lower or delete its count here. # A new file or a higher count fails. KNOWN_UNMARKED: dict[str, int] = { + "policyengine_us/parameters/gov/irs/ald/charitable_contributions/cap.yaml": 1, "policyengine_us/parameters/gov/irs/credits/residential_clean_energy/fuel_cell_cap_per_kw.yaml": 1, "policyengine_us/parameters/gov/irs/income/exemption/overtime/hce_salary_threshold.yaml": 1, "policyengine_us/parameters/gov/irs/income/exemption/overtime/salary_basis_threshold.yaml": 1, diff --git a/policyengine_us/tests/core/test_charitable_contributions_ald_invariants.py b/policyengine_us/tests/core/test_charitable_contributions_ald_invariants.py new file mode 100644 index 00000000000..d8f6d201e9d --- /dev/null +++ b/policyengine_us/tests/core/test_charitable_contributions_ald_invariants.py @@ -0,0 +1,245 @@ +"""Invariants for the 2020 above-the-line charitable deduction. + +26 U.S.C. 62(a)(22) (CARES Act sec. 2204) lets an individual who does not +itemize deduct up to $300 of cash charitable contributions in arriving at +adjusted gross income for taxable years beginning in 2020 only. The statute +caps it at $300 for every return; the 2020 Instructions for Form 1040, line +10b, also give $300 for a joint return, and their $150 for married filing +separately has no basis in the statute, which governs. Because adjusted gross +income then depends on the itemization choice, tax_unit_itemizes compares the +deductions of the branches that itemize and that do not. + +Invariants, for every household, written from the statute and the form rather +than read from the model's parameters: + +1. Bounds and eligibility: the deduction is min(the head's and spouse's cash + gifts, $300) for a unit that does not itemize and zero for one that does, + and zero in 2021. A dependent's gifts are on the dependent's own return. +2. Adjusted gross income is gross income less the above-the-line deductions, + which include the charitable deduction (2020 Form 1040 lines 9, 10c, 11). +3. No gift is deducted twice: itemized deductions are exactly the Schedule A + components, in 2020 and 2021. +4. Consistency of the choice: the income tax of the simulation equals the tax + of the branch for the choice it makes, which is the lower of the two + (differential: main simulation against the branch that recomputes it). +5. Decoupled states add it back (California; New York's recomputed federal + AGI, IT-201 line 19a) and Massachusetts Part B income leaves it in (TIR + 20-9, B(6)). +6. Without branching (as CI runs YAML tests), the choice still leaves the + lower taxable income of the two branches: in 2020 the comparison counts + what not itemizing takes off adjusted gross income. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_core.periods import instant +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation + +TOLERANCE = 0.01 # dollars + +# 26 U.S.C. 62(a)(22): "not to exceed $300", for every return. +CAP_2020 = 300 +SCHEDULE_A = [ + "charitable_deduction", + "interest_deduction", + "salt_deduction", + "medical_expense_deduction", + "casualty_loss_deduction", + "misc_deduction", +] +STATES = ["TX", "CA", "NY", "MA", "AZ", "MI", "KY"] + + +@st.composite +def tax_units(draw): + money = lambda high: st.one_of( # noqa: E731 + st.just(0.0), st.integers(1, high).map(float) + ) + return { + "filing": draw(st.sampled_from(["single", "joint", "separate", "hoh"])), + "state": draw(st.sampled_from(STATES)), + "wages": draw(money(250_000)), + "cash": draw(money(5_000)), + "noncash": draw(money(5_000)), + "mortgage": draw(money(30_000)), + "medical": draw(money(20_000)), + "child_cash": draw(money(1_000)), + } + + +def _seeded_units(n=60, seed=20261012): + rng = np.random.default_rng(seed) + + def some(high, p=0.6): + return float(round(rng.uniform(1, high))) if rng.random() < p else 0.0 + + return [ + { + "filing": str(rng.choice(["single", "joint", "separate", "hoh"])), + "state": str(rng.choice(STATES)), + "wages": some(250_000, 0.9), + "cash": some(5_000, 0.8), + "noncash": some(5_000, 0.3), + "mortgage": some(30_000, 0.5), + "medical": some(20_000, 0.3), + "child_cash": some(1_000, 0.3), + } + for _ in range(n) + ] + [ + # Single New Hampshire filers (no state or local tax deduction) whose + # Schedule A is the $12,400 standard deduction plus k, with $300 of + # gifts: itemizing pays only when k > 300. + { + "filing": "single", + "state": "NH", + "wages": 60_000.0, + "cash": 300.0, + "noncash": 0.0, + "mortgage": 12_400.0 - 300 + k, + "medical": 0.0, + "child_cash": 0.0, + } + for k in [-400, -200, -100, 0, 100, 299, 301, 400] + ] + + +def _situation(units, year): + people = {} + groups = {"tax_units": {}, "households": {}, "marital_units": {}} + for i, u in enumerate(units): + head = f"head_{i}" + people[head] = { + "age": {year: 45}, + "employment_income": {year: u["wages"]}, + "charitable_cash_donations": {year: u["cash"]}, + "charitable_non_cash_donations": {year: u["noncash"]}, + "deductible_mortgage_interest": {year: u["mortgage"]}, + "other_medical_expenses": {year: u["medical"]}, + } + members = [head] + if u["filing"] == "joint": + people[f"spouse_{i}"] = {"age": {year: 43}} + members.append(f"spouse_{i}") + if u["filing"] == "hoh": + people[f"child_{i}"] = { + "age": {year: 15}, + "charitable_cash_donations": {year: u["child_cash"]}, + } + members.append(f"child_{i}") + groups["marital_units"][f"couple_{i}"] = { + "members": [m for m in members if not m.startswith("child")] + } + if u["filing"] == "hoh": + groups["marital_units"][f"child_unit_{i}"] = {"members": [f"child_{i}"]} + tax_unit = {"members": members} + if u["filing"] == "separate": + tax_unit["filing_status"] = {year: "SEPARATE"} + groups["tax_units"][f"tax_unit_{i}"] = tax_unit + groups["households"][f"household_{i}"] = { + "members": members, + "state_code": {year: u["state"]}, + } + return {"people": people, **groups} + + +def _no_branching(parameters): + parameters.gov.simulation.branch_to_determine_itemization.update( + start=instant("2000-01-01"), stop=instant("2039-12-31"), value=False + ) + return parameters + + +class NoBranching(Reform): + def apply(self): + self.modify_parameters(_no_branching) + + +def _check(units, year, without_branching=False): + sim = Simulation(situation=_situation(units, year)) + + def calc(variable): + return np.asarray(sim.calculate(variable, year), dtype=float) + + filing = sim.calculate("filing_status", year).decode_to_str() + state = np.array([u["state"] for u in units]) + cash = np.array([u["cash"] for u in units]) + itemizes = calc("tax_unit_itemizes").astype(bool) + ald = calc("charitable_contributions_ald") + + # 1. Bounds and eligibility. + if year == 2020: + expected = np.where(itemizes, 0, np.minimum(cash, CAP_2020)) + else: + expected = np.zeros_like(cash) + np.testing.assert_allclose(ald, expected, atol=TOLERANCE) + + # 2. AGI: line 11 = line 9 - line 10c. Wages are the only income and the + # charitable deduction the only adjustment. + wages = np.array([u["wages"] for u in units]) + agi = calc("adjusted_gross_income") + np.testing.assert_allclose(calc("above_the_line_deductions"), ald, atol=TOLERANCE) + np.testing.assert_allclose(agi, wages - ald, atol=1) + + # 3. No gift deducted twice. + components = sum(calc(variable) for variable in SCHEDULE_A) + np.testing.assert_allclose( + calc("total_itemized_taxable_income_deductions"), components, atol=1 + ) + + # 4. The simulation's tax is the tax of the branch it chooses, the lower. + if_itemizing = calc("tax_liability_if_itemizing") + if_not = calc("tax_liability_if_not_itemizing") + income_tax = calc("income_tax") + np.testing.assert_allclose( + income_tax, np.where(itemizes, if_itemizing, if_not), atol=1 + ) + np.testing.assert_allclose(income_tax, np.minimum(if_itemizing, if_not), atol=1) + + # 5. Decoupled states. + in_state = lambda code: state == code # noqa: E731 + np.testing.assert_allclose( + calc("ca_additions")[in_state("CA")], ald[in_state("CA")] + ) + ny = in_state("NY") + np.testing.assert_allclose( + calc("ny_recomputed_federal_agi")[ny], (agi + ald)[ny], atol=1 + ) + # Massachusetts does not adopt 62(a)(22) (TIR 20-9, B(6)): Part B AGI is + # the wages. + ma = in_state("MA") + np.testing.assert_allclose(calc("ma_part_b_agi")[ma], wages[ma], atol=1) + + # 6. Without branching, the choice leaves the lower taxable income. + if without_branching: + plain = Simulation(situation=_situation(units, year), reform=NoBranching) + taxable_income = np.asarray(plain.calculate("taxable_income", year)) + branch_incomes = [ + np.asarray(plain.branches[name].calculate("taxable_income", year)) + for name in ("itemizing", "not_itemizing") + ] + assert (taxable_income <= np.minimum(*branch_incomes) + 1).all() + + +def test_seeded_population_2020(): + # The seeded population includes households at the itemization margin. + _check(_seeded_units(), 2020, without_branching=True) + + +def test_seeded_population_2021(): + _check(_seeded_units(), 2021) + + +@settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(st.lists(tax_units(), min_size=5, max_size=20)) +def test_random_households_2020(units): + _check(units, 2020) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.yaml new file mode 100644 index 00000000000..84e1d6dfdaa --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.yaml @@ -0,0 +1,118 @@ +# 26 U.S.C. 62(a)(22) and 62(f) (CARES Act sec. 2204), 2020 only: "the +# amount (not to exceed $300) of qualified charitable contributions made by +# an eligible individual". $300 for every return, as the 2020 Instructions +# for Form 1040, line 10b, give for single, head of household, qualifying +# widow(er) and joint filers. The instructions' $150 for married filing +# separately has no basis in the statute, which governs. + +- name: Case 1, 2020 non-itemizer below the cap deducts all cash gifts. + period: 2020 + input: + charitable_cash_donations: 200 + filing_status: SINGLE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 200 + +- name: Case 2, 2020 single non-itemizer exactly at the cap. + period: 2020 + input: + charitable_cash_donations: 300 + filing_status: SINGLE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 300 + +- name: Case 3, 2020 single non-itemizer above the cap. + period: 2020 + input: + charitable_cash_donations: 500 + filing_status: SINGLE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 300 + +- name: Case 4, 2020 joint return is capped at $300, not $600. + period: 2020 + input: + charitable_cash_donations: 700 + filing_status: JOINT + tax_unit_itemizes: false + output: + charitable_contributions_ald: 300 + +- name: Case 5, 2020 married filing separately is capped at $300, as the statute reads. + period: 2020 + input: + charitable_cash_donations: 400 + filing_status: SEPARATE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 300 + +- name: Case 6, 2020 head of household is capped at $300. + period: 2020 + input: + charitable_cash_donations: 400 + filing_status: HEAD_OF_HOUSEHOLD + tax_unit_itemizes: false + output: + charitable_contributions_ald: 300 + +- name: Case 7, 2020 qualifying widow(er) is capped at $300. + period: 2020 + input: + charitable_cash_donations: 400 + filing_status: SURVIVING_SPOUSE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 300 + +- name: Case 8, 2020 itemizer is not an eligible individual (62(f)(1)). + period: 2020 + input: + charitable_cash_donations: 500 + filing_status: SINGLE + tax_unit_itemizes: true + output: + charitable_contributions_ald: 0 + +- name: Case 9, noncash gifts do not qualify (62(f)(2)(A)). + period: 2020 + input: + charitable_cash_donations: 0 + charitable_non_cash_donations: 1_000 + filing_status: SINGLE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 0 + +- name: Case 10, no deduction in 2019, before 62(a)(22). + period: 2019 + input: + charitable_cash_donations: 500 + filing_status: SINGLE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 0 + +- name: Case 11, no above-the-line deduction in 2021; 170(p) takes it from AGI instead. + period: 2021 + input: + charitable_cash_donations: 500 + filing_status: SINGLE + tax_unit_itemizes: false + output: + charitable_contributions_ald: 0 + charitable_deduction_for_non_itemizers: 300 + +- name: Case 12, gifts to organizations that are not 170(b)(1)(A) organizations do not qualify (62(f)(2)(C)). + period: 2020 + input: + charitable_cash_donations: 500 + charitable_cash_donations_non_50_pct_orgs: 400 + filing_status: SINGLE + tax_unit_itemizes: false + output: + # 500 - 400 = 100 of cash to 170(b)(1)(A) organizations. + charitable_contributions_ald: 100 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/integration.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/integration.yaml new file mode 100644 index 00000000000..7d1bd0624eb --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/integration.yaml @@ -0,0 +1,393 @@ +# Worked from the 2020 Form 1040 (lines 9, 10a-10c, 11, 12 and 15) and its +# instructions: line 10b (file page 30; the cap is $300 for every return, as +# 26 U.S.C. 62(a)(22) reads, not the instructions' $150 for married filing +# separately), the 2020 Tax Rate Schedules (file +# page 111), the Standard Deduction Chart for People Born Before January 2, +# 1956 (file page 33), the Social Security Benefits Worksheet (file page 31, +# line 6 subtracts line 10b) and the Unemployment Compensation Exclusion +# Worksheet (file page 88, line 5 subtracts line 10c). +# https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf +# New Hampshire taxes no wages and has no sales tax, so there is no state and +# local tax deduction. + +- name: Case 1, 2020 single non-itemizer deducts $300 of $500 in cash gifts above the line. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + # Line 9: total income. + irs_gross_income: 40_000 + # Line 10b: min(500, 300); line 10c = 10a + 10b. + charitable_contributions_ald: 300 + above_the_line_deductions: 300 + # Line 11: 40,000 - 300. + adjusted_gross_income: 39_700 + tax_unit_itemizes: false + # Line 12: 2020 single standard deduction; nothing below the line for + # the charitable gifts. + standard_deduction: 12_400 + charitable_deduction_for_non_itemizers: 0 + taxable_income_deductions: 12_400 + # Line 15: 39,700 - 12,400. + taxable_income: 27_300 + # Schedule X: 987.50 + 12% x (27,300 - 9,875). + income_tax_main_rates: 3_078.5 + +- name: Case 2, 2020 joint return deducts $300, not $600. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 80_000 + charitable_cash_donations: 1_000 + person2: + age: 40 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NH + output: + charitable_contributions_ald: 300 + # Line 11: 80,000 - 300. + adjusted_gross_income: 79_700 + standard_deduction: 24_800 + # Line 15: 79,700 - 24,800. + taxable_income: 54_900 + # Schedule Y-1: 1,975 + 12% x (54,900 - 19,750). + income_tax_main_rates: 6_193 + +- name: Case 3, 2020 married filing separately deducts $300, as the statute reads. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 50_000 + charitable_cash_donations: 400 + tax_units: + tax_unit: + members: [person1] + filing_status: SEPARATE + households: + household: + members: [person1] + state_code: NH + output: + # 62(a)(22): not to exceed $300; the instructions' $150 has no basis in + # the statute. + charitable_contributions_ald: 300 + # Line 11: 50,000 - 300. + adjusted_gross_income: 49_700 + standard_deduction: 12_400 + # Line 15: 49,700 - 12,400. + taxable_income: 37_300 + # Schedule Y-2: 987.50 + 12% x (37,300 - 9,875). + income_tax_main_rates: 4_278.5 + +- name: Case 4, 2020 itemizer deducts the gifts on Schedule A only, once. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 100_000 + deductible_mortgage_interest: 20_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + tax_unit_itemizes: true + # Line 10b is for those who take the standard deduction. + charitable_contributions_ald: 0 + adjusted_gross_income: 100_000 + charitable_deduction: 500 + # Schedule A: 20,000 interest + 500 gifts. + total_itemized_taxable_income_deductions: 20_500 + # Line 15: 100,000 - 20,500. + taxable_income: 79_500 + +- name: Case 5, 2020 line 10b makes the standard deduction the better choice. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 60_000 + deductible_mortgage_interest: 12_300 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + # Itemizing: line 11 = 60,000, Schedule A = 12,300 + 300 = 12,600, line + # 15 = 47,400. Standard deduction: line 11 = 60,000 - 300 = 59,700, line + # 15 = 59,700 - 12,400 = 47,300, so taking the standard deduction leaves + # $100 less taxable income although Schedule A exceeds $12,400. + tax_unit_itemizes: false + charitable_contributions_ald: 300 + adjusted_gross_income: 59_700 + taxable_income: 47_300 + # Schedule X: 4,617.50 + 22% x (47,300 - 40,125). + income_tax_main_rates: 6_196 + +- name: Case 6, 2020 line 10b lowers the income that makes Social Security benefits taxable. + period: 2020 + input: + people: + person1: + age: 70 + social_security_retirement: 20_000 + taxable_pension_income: 20_000 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + charitable_contributions_ald: 300 + # Social Security Benefits Worksheet: line 3 = 20,000 (line 5b), line 6 + # = 300 (line 10b), line 7 = 10,000 + 20,000 - 300 = 29,700. + taxable_ss_magi: 19_700 + tax_unit_combined_income_for_social_security_taxability: 29_700 + # Line 9 = 29,700 - 25,000 = 4,700; line 12 = min(4,700, 9,000); line 13 + # = 2,350; line 14 = min(10,000, 2,350); line 15 = 0; line 18 = + # min(2,350, 17,000). + tax_unit_taxable_social_security: 2_350 + # Line 11: 20,000 + 2,350 - 300. + adjusted_gross_income: 22_050 + # Single, born before January 2, 1956: $14,050. + standard_deduction: 14_050 + # Line 15: 22,050 - 14,050. + taxable_income: 8_000 + income_tax_main_rates: 800 + +- name: Case 7, 2020 line 10c brings income under the unemployment compensation exclusion limit. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 150_200 + unemployment_compensation: 5_000 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + charitable_contributions_ald: 300 + # Unemployment Compensation Exclusion Worksheet: line 4 = 150,200, line + # 5 = 300 (line 10c), line 6 = 149,900, under $150,000, so line 10 + # excludes all 5,000. + taxable_uc_agi: 149_900 + tax_unit_taxable_unemployment_compensation: 0 + # Line 11: 150,200 + 0 - 300. + adjusted_gross_income: 149_900 + +- name: Case 8, 2021 itemizer deducts the gifts on Schedule A only, once. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 100_000 + deductible_mortgage_interest: 20_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + tax_unit_itemizes: true + adjusted_gross_income: 100_000 + # Schedule A: 20,000 interest + 500 gifts; the 170(p) amount is for + # those who take the standard deduction. + total_itemized_taxable_income_deductions: 20_500 + taxable_income: 79_500 + +- name: Case 9, 2021 non-itemizer deducts the gifts after AGI, on line 12b. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + charitable_contributions_ald: 0 + # 2021 line 11. + adjusted_gross_income: 40_000 + tax_unit_itemizes: false + # Line 12b: min(500, 300); line 12c = 12,550 + 300. + charitable_deduction_for_non_itemizers: 300 + taxable_income_deductions: 12_850 + # Line 15: 40,000 - 12,850. + taxable_income: 27_150 + +- name: Case 10, 2020 dependent's own gifts are not on the parent's return. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + person2: + age: 16 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NH + output: + # 62(a)(22) counts contributions "made by an eligible individual"; the + # child's gifts belong on the child's own return. + charitable_contributions_ald: 0 + adjusted_gross_income: 40_000 + +- name: Case 11, 2020 line 10b lowers the income that limits the tuition and fees deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 80_050 + charitable_cash_donations: 300 + qualified_tuition_expenses: 4_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + charitable_contributions_ald: 300 + # 2020 Pub. 970 Worksheet 6-1: line 1 = 80,050, line 2 includes line 10b + # (300), MAGI = 79,750, over $65,000 but not over $80,000 for a single + # filer, so the deduction is limited to $2,000 (Form 8917 line 5). + capped_qualified_tuition_expenses_ald: 2_000 + # Line 11: 80,050 - 300 - 2,000. + adjusted_gross_income: 77_750 + +- name: Case 12, 2020 choice without branching weighs line 10b with the standard deduction. + period: 2020 + input: + gov.simulation.branch_to_determine_itemization: false + people: + person1: + age: 40 + employment_income: 60_000 + deductible_mortgage_interest: 12_300 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + # Case 5 without branching (as CI runs YAML tests): Schedule A 12,600 is + # less than the 12,400 standard deduction plus the 300 that not + # itemizing takes off AGI. + tax_unit_itemizes: false + charitable_contributions_ald: 300 + adjusted_gross_income: 59_700 + taxable_income: 47_300 + +- name: Case 13, 2020 choice without branching counts wagering losses with Schedule A. + period: 2020 + input: + gov.simulation.branch_to_determine_itemization: false + people: + person1: + age: 40 + employment_income: 60_000 + gambling_winnings: 1_000 + gambling_losses: 1_000 + deductible_mortgage_interest: 12_300 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + # Itemizing takes 12,300 + 300 + 1,000 of wagering losses (Schedule A + # line 16) from income; not itemizing takes 12,400 + 300 (line 10b). + # 13,600 > 12,700, so the unit itemizes. + tax_unit_itemizes: true + wagering_losses_deduction: 1_000 + charitable_contributions_ald: 0 + +- name: Case 14, 2020 choice without branching counts the QBI deduction each choice allows. + period: 2020 + input: + gov.simulation.branch_to_determine_itemization: false + people: + person1: + age: 40 + employment_income: 126_000 + partnership_s_corp_income: 1_000_000 + sstb_self_employment_income: -950_000 + w2_wages_from_qualified_business: 500_000 + deductible_mortgage_interest: 12_300 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NH + output: + # The SSTB loss phases in with taxable income, so the QBI deduction is + # $10,380 when itemizing and $10,000 when not (Sol review r2). Itemizing + # leaves taxable income $153,020 against $153,300 without. + tax_unit_itemizes: true + taxable_income: 153_020 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.yaml index a14419484fa..2a7941e9543 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.yaml @@ -6,32 +6,31 @@ output: charitable_deduction_for_non_itemizers: 0 -- name: 2020 single filer more than limit +- name: 2020 single filer, whose deduction is above the line instead period: 2020 input: charitable_cash_donations: 400 filing_status: SINGLE - # CARES Act §2204 set the non-itemizer cap at $300 for 2020, not $150. - # Per IRS Pub. 526 (2020) p.2, MFS was limited to $150 (sharing the - # joint $300 cap), but SINGLE/HoH/SURVIVING_SPOUSE get the full $300. + # 170(p) starts in 2021. The 2020 deduction for non-itemizers is the + # above-the-line charitable_contributions_ald (26 U.S.C. 62(a)(22)). output: - charitable_deduction_for_non_itemizers: 300 + charitable_deduction_for_non_itemizers: 0 -- name: 2020 married filers more than limit +- name: 2020 married filers, whose deduction is above the line instead period: 2020 input: charitable_cash_donations: 700 filing_status: JOINT output: - charitable_deduction_for_non_itemizers: 300 + charitable_deduction_for_non_itemizers: 0 -- name: 2020 MFS filer capped at $150 (half of joint $300) +- name: 2020 MFS filer, whose deduction is above the line instead period: 2020 input: charitable_cash_donations: 400 filing_status: SEPARATE output: - charitable_deduction_for_non_itemizers: 150 + charitable_deduction_for_non_itemizers: 0 - name: 2021 single filer less than limit period: 2021 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.yaml index 90e8017e8c4..63799f86996 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.yaml @@ -55,6 +55,64 @@ output: tax_unit_itemizes: False +- name: 2021 without branching - compare itemized deductions with standard plus cash gifts + period: 2021 + input: + gov.simulation.branch_to_determine_itemization: false + # Each triple is below, equal to, and above the 2021 Form 1040 line 12c + # total: standard deduction plus the cash gift allowed by 26 U.S.C. 170(p). + # Singles have full, partial, then zero gifts; joint filers repeat these. + filing_status: [SINGLE, SINGLE, SINGLE, SINGLE, SINGLE, SINGLE, SINGLE, SINGLE, SINGLE, JOINT, JOINT, JOINT, JOINT, JOINT, JOINT, JOINT, JOINT, JOINT] + standard_deduction: [12_550, 12_550, 12_550, 12_550, 12_550, 12_550, 12_550, 12_550, 12_550, 25_100, 25_100, 25_100, 25_100, 25_100, 25_100, 25_100, 25_100, 25_100] + charitable_cash_donations: [400, 400, 400, 150, 150, 150, 0, 0, 0, 700, 700, 700, 250, 250, 250, 0, 0, 0] + itemized_taxable_income_deductions: [12_849, 12_850, 12_851, 12_699, 12_700, 12_701, 12_549, 12_550, 12_551, 25_699, 25_700, 25_701, 25_349, 25_350, 25_351, 25_099, 25_100, 25_101] + output: + # Section 170(p): $300 per single return and $600 per joint return, + # limited to the eligible cash actually given. + charitable_deduction_for_non_itemizers: [300, 300, 300, 150, 150, 150, 0, 0, 0, 600, 600, 600, 250, 250, 250, 0, 0, 0] + tax_unit_itemizes: [false, false, true, false, false, true, false, false, true, false, false, true, false, false, true, false, false, true] + +- name: 2021 equal federal tax - deduction comparison includes the non-itemizer cash deduction + period: 2021 + input: + gov.simulation.branch_to_determine_itemization: true + filing_status: SINGLE + standard_deduction: 12_550 + charitable_cash_donations: 300 + itemized_taxable_income_deductions: 12_699 + tax_liability_if_itemizing: 0 + tax_liability_if_not_itemizing: 0 + output: + # 2021 Form 1040 line 12c: 12_550 + 300 under section 170(p) = 12_850. + tax_unit_itemizes: false + +- name: 2026 without branching - the cash deduction makes not itemizing preferable + period: 2026 + input: + gov.simulation.branch_to_determine_itemization: false + people: + head: + age: 45 + employment_income: 60_000 + charitable_cash_donations: 1_000 + deductible_mortgage_interest: 15_800 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: NH + output: + # Section 170(b)(1)(I): 1_000 - 0.005 * 60_000 = 700 itemized gifts. + charitable_deduction: 700 + itemized_taxable_income_deductions: 16_500 + standard_deduction: 16_100 + # Section 170(p): the non-itemizer instead deducts the full 1_000. + charitable_deduction_for_non_itemizers: 1_000 + tax_unit_itemizes: false + taxable_income: 42_900 + - name: 2026 - the itemization choice uses the section 68 limitation net of the QBI deduction period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.yaml index 8663b817733..b2701520f8d 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.yaml @@ -33,3 +33,27 @@ adjusted_gross_income: 30_001 output: nyc_cdcc_eligible: false + +- name: 2020 NYC child care credit tests recomputed federal AGI + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 30_100 + charitable_cash_donations: 300 + person2: + age: 3 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + in_nyc: true + output: + # Federal AGI 29,800 is under $30,000; recomputed federal AGI (IT-216 + # line 9, IT-201 line 19a) is 30,100, over it. + adjusted_gross_income: 29_800 + nyc_cdcc_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml index 43eeabb49b8..badd48577b1 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml @@ -349,3 +349,25 @@ output: # $30 x 2. nyc_household_credit: 60 + +- name: 2020 NYC household credit uses recomputed federal AGI + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 12_600 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NY + in_nyc: true + output: + # Federal AGI 12,300 is under the $12,500 single limit, but recomputed + # federal AGI (IT-201 line 19a) is 12,600, over it. + adjusted_gross_income: 12_300 + nyc_household_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/nyc_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/nyc_eitc.yaml index 10c2d656008..5b84c2e9e0a 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/nyc_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/nyc_eitc.yaml @@ -67,3 +67,28 @@ # 0.18 # 3. Allowable NYC EIC (multiply line 1 by line 2) nyc_eitc: 18 + +- name: 2020 NYC EITC matches the federal credit recomputed with line 19a + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 40_800 + taxable_interest_income: 1_000 + charitable_cash_donations: 300 + person2: + age: 10 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + in_nyc: true + output: + # Recomputed federal AGI 41,800 is over the $41,756 one-child limit. + ny_recomputed_federal_agi: 41_800 + nyc_eitc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.yaml index f0dff0043a9..590b2877469 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.yaml @@ -108,3 +108,24 @@ # distribution and income equals AGI. adjusted_gross_income: 300_000 nyc_school_credit_income: 300_000 + +- name: 2020 NYC school credit income uses recomputed federal AGI + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 250_100 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NY + in_nyc: true + output: + # Federal AGI 249,800; recomputed federal AGI (line 19a) 250,100. + adjusted_gross_income: 249_800 + nyc_school_credit_income: 250_100 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.yaml index bb735ea8f16..330df92d435 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.yaml @@ -117,3 +117,46 @@ # charitable after credit = max($1,200 - $400, 0) = $800 # rate (0) * 800 + min($800, $1,000 single cap) = $800 az_increased_standard_deduction_for_charitable_contributions: 800 + +- name: test for 2020 federal non-itemizer, whose line 10b deduction is subtracted + period: 2020 + input: + state_code: AZ + charitable_deduction: 500 + charitable_contributions_ald: 300 + az_charitable_contributions_credit_potential: 0 + output: + # 2020 worksheet: line 4C 500, line 5C 300, line 6C 0; 25% of 200. + az_increased_standard_deduction_for_charitable_contributions: 50 + +- name: test for 2020 federal deduction and credit exceeding contributions + period: 2020 + input: + state_code: AZ + charitable_deduction: 500 + charitable_contributions_ald: 300 + az_charitable_contributions_credit_potential: 400 + output: + # max(500 - 300 - 400, 0) = 0 + az_increased_standard_deduction_for_charitable_contributions: 0 + +- name: test for 2020 household that gives $500 in cash and takes the federal standard deduction + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: AZ + output: + charitable_contributions_ald: 300 + charitable_deduction: 500 + # 25% of (500 - 300) + az_increased_standard_deduction_for_charitable_contributions: 50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.yaml index a544bb87454..562db883780 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.yaml @@ -13,3 +13,25 @@ adjusted_gross_income: 150_000 output: is_ca_cvrp_normal_rebate_eligible: true + +- name: The 2020 charitable deduction for non-itemizers does not lower CVRP income. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 150_200 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CA + output: + # Form 1040 line 11 is 150,200 - 300 = 149,900, but the CVRP manual + # counts income before adjustments (Form 1040 lines 1-7, Schedule 1 + # lines 1-8): 150,200, over the $150,000 single cap. + adjusted_gross_income: 149_900 + is_ca_cvrp_normal_rebate_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_additions.yaml new file mode 100644 index 00000000000..1262441653f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_additions.yaml @@ -0,0 +1,61 @@ +# 2020 Schedule CA (540) instructions, Part I, Section C, line 22, column B: +# the federal Form 1040 line 10b charitable deduction is added back. +# https://www.ftb.ca.gov/forms/2020/2020-540-booklet.pdf#page=48 + +- name: Case 1, 2020 federal non-itemizer adds back the line 10b deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CA + output: + # Federal AGI 40,000 - 300 = 39,700; California adds the 300 back. + adjusted_gross_income: 39_700 + ca_additions: 300 + ca_agi: 40_000 + +- name: Case 2, 2020 federal itemizer has no line 10b deduction to add back. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 100_000 + charitable_cash_donations: 500 + deductible_mortgage_interest: 20_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CA + output: + ca_additions: 0 + +- name: Case 3, 2021 deduction for non-itemizers is below the line, so nothing is added back. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CA + output: + ca_additions: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/add_backs/in_add_backs.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/add_backs/in_add_backs.yaml new file mode 100644 index 00000000000..b419fc8fd4a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/add_backs/in_add_backs.yaml @@ -0,0 +1,39 @@ +# IC 6-3-1-3.5(a)(26) adds back the 2020 IRC 62(a)(22) charitable deduction +# (Information Bulletin 119, code 120). +# https://www.in.gov/dor/files/reference/ib119.pdf#page=2 + +- name: Case 1, 2020 federal non-itemizer adds back the line 10b deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: IN + output: + in_add_backs: 300 + +- name: Case 2, 2021 deduction for non-itemizers is below the line, so nothing is added back. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: IN + output: + in_add_backs: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/ky_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/ky_agi.yaml new file mode 100644 index 00000000000..e2b8e09398d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ky/tax/income/ky_agi.yaml @@ -0,0 +1,67 @@ +# 2020 Form 740 instructions, Schedule M line 5: contributions claimed on +# federal Form 1040 line 10b are added back (in ky_agi, by each filer's part). +# https://revenue.ky.gov/Forms/740%20Instructions%20Packet%205.7.21.pdf#page=27 + +- name: Case 1, 2020 single federal non-itemizer adds back the line 10b deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: KY + output: + adjusted_gross_income_person: 39_700 + charitable_contributions_ald_person: 300 + ky_agi: 40_000 + +- name: Case 2, 2020 joint federal non-itemizers add back each spouse's own part of the deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 50_000 + charitable_cash_donations: 1_000 + person2: + age: 40 + employment_income: 30_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: KY + output: + # The $300 deduction is person1's: only person1 gave. Federal person AGI + # deducts it from person1 (charitable_contributions_ald_person). + charitable_contributions_ald_person: [300, 0] + adjusted_gross_income_person: [49_700, 30_000] + ky_agi: [50_000, 30_000] + +- name: Case 3, 2021 deduction for non-itemizers is below the line, so nothing is added back. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: KY + output: + charitable_contributions_ald_person: 0 + ky_agi: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_part_b_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_part_b_agi.yaml index e350f4cb091..293904a7c0c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_part_b_agi.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ma/tax/income/adjusted_gross_income/ma_part_b_agi.yaml @@ -33,3 +33,25 @@ # insurance deduction is left out too: it is on the child's own return. ma_part_b_gross_income: 40_000 ma_part_b_agi: 40_000 + +- name: The 2020 federal charitable deduction for non-itemizers does not reduce Part B AGI + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MA + output: + # Massachusetts follows Code 62(a) as in effect on January 1, 2005, so + # it does not adopt 62(a)(22) (TIR 20-9, B(6)). + adjusted_gross_income: 39_700 + ma_part_b_agi: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_household_resources.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_household_resources.yaml index 9e9345ba1a7..83f3615cf3a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_household_resources.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_household_resources.yaml @@ -1098,3 +1098,46 @@ output: # Line 33: 10,000 - 1,000 = 9,000. mi_household_resources: 9_000 + +- name: The 2020 charitable deduction for non-itemizers comes off household resources + # Michigan Treasury FAQ: for 2020 the charitable cash contribution "is a + # deduction from adjusted gross income and therefore Total Household + # Resources as well". U.S. Form 1040 line 10b: min(500, 300). + period: 2020 + input: + people: + head: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MI + output: + charitable_contributions_ald: 300 + mi_household_resources: 39_700 + +- name: The 2021 charitable deduction for non-itemizers is not taken from household resources + # The 2021 deduction is taken from AGI, so it is not a deduction from + # total household resources (Michigan Treasury FAQ). + period: 2021 + input: + people: + head: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [head] + households: + household: + members: [head] + state_code: MI + output: + charitable_deduction_for_non_itemizers: 300 + mi_household_resources: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/additions/mn_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/additions/mn_additions.yaml new file mode 100644 index 00000000000..1ceb76062cb --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/additions/mn_additions.yaml @@ -0,0 +1,59 @@ +# 2020 Schedule M1NC line 6 adds back the federal charitable deduction for +# non-itemizers, through Form M1M line 16. +# https://www.revenue.state.mn.us/sites/default/files/2024-02/m1nc_20.pdf#page=1 + +- name: Case 1, 2020 federal non-itemizer adds back the line 10b deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_additions: 300 + +- name: Case 2, 2021 deduction for non-itemizers is below the line, so nothing is added back. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_additions: 0 + +- name: Case 3, 2020 charitable addback combines with other Minnesota additions. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + mn_other_additions: 1_700 + households: + household: + members: [person1] + state_code: MN + output: + charitable_contributions_ald: 300 + mn_additions: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml index 0b04373bb34..1a70179489b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml @@ -55,3 +55,27 @@ output: mn_military_pension_subtraction: 50_000 mn_amt_taxable_income: 200_000 - 50_000 + +- name: MN AMT taxable income adds back the 2020 federal charitable deduction for non-itemizers + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Federal AGI (M1MT line 1) = 40,000 - 300 (Form 1040 line 10b). Line 4 + # includes Schedule M1NC line 6, the 300 added back (2020 M1MT + # instructions, file page 2). No itemizing, no subtractions. + adjusted_gross_income: 39_700 + charitable_contributions_ald: 300 + mn_itemizing: false + mn_amt_taxable_income: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml index 17cb6090594..e90fead9244 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_social_security_subtraction.yaml @@ -153,3 +153,35 @@ # AGI equals the $86,410 threshold, so the simplified subtraction is not # reduced and the full $20,000 of taxable benefits is subtracted. mn_social_security_subtraction: 20_000 + +- name: MN social security subtraction 2020 worksheet step 8 leaves out Form 1040 line 10b + absolute_error_margin: 0.01 + period: 2020 + input: + # 2020 Worksheet for line 39 constants (2020 M1M instructions, file + # page 8), as the parameters start in 2021. + gov.states.mn.tax.income.subtractions.social_security.income_amount.SINGLE: 62_090 + gov.states.mn.tax.income.subtractions.social_security.alternative_amount.SINGLE: 4_090 + people: + person1: + age: 67 + employment_income: 65_000 + social_security_retirement: 10_000 + charitable_cash_donations: 300 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + charitable_contributions_ald: 300 + # Federal: provisional income 65,000 - 300 + 5,000 = 69,700, so 85% of + # benefits, 8,500, are taxable. + tax_unit_taxable_social_security: 8_500 + # Steps 1-3: 73,500 - 8,500 = 65,000; step 5: 5,000; step 7: 70,000; + # step 8: Schedule 1 lines 10-19 and 22 only, 0 (not line 10b); + # step 9: 70,000; step 11: 70,000 - 62,090 = 7,910; step 12: 1,582; + # step 14: 4,090 - 1,582 = 2,508; step 18: min(2,508, 8,500). + mn_social_security_subtraction: 2_508 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_additions.yaml b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_additions.yaml new file mode 100644 index 00000000000..5ea3394f324 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nc/tax/income/nc_additions.yaml @@ -0,0 +1,59 @@ +# 2020 D-400 Schedule S, Part A, line 16: above-the-line qualified charitable +# contribution deduction. +# https://www.ncdor.gov/documents/files/2020-d-400-schedule-s-web/open#page=2 + +- name: Case 1, 2020 federal non-itemizer adds back the line 10b deduction. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NC + output: + nc_additions: 300 + +- name: Case 2, 2021 deduction for non-itemizers is below the line, so nothing is added back. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NC + output: + nc_additions: 0 + +- name: Case 3, 2020 charitable addback combines with other North Carolina additions. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + nc_other_additions: 700 + households: + household: + members: [person1] + state_code: NC + output: + charitable_contributions_ald: 300 + nc_additions: 1_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/adjusted_gross_income/ny_recomputed_federal_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/adjusted_gross_income/ny_recomputed_federal_agi.yaml new file mode 100644 index 00000000000..40355240a03 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/adjusted_gross_income/ny_recomputed_federal_agi.yaml @@ -0,0 +1,72 @@ +# 2020 Form IT-201 instructions, line 19a worksheet (file page 16): recomputed +# federal AGI is federal AGI plus the Form IT-558 addition adjustments. IT-558 +# addition A-003 (instructions, file page 2) adds back the IRC 62(a)(22) +# charitable deduction, which New York did not adopt for 2020. New York AGI +# starts from line 19a (IT-201 instructions, file page 17), and Form IT-196 +# lines 2 and 26 take the medical and miscellaneous floors from it. +# https://www.tax.ny.gov/pdf/2020/inc/it201i_2020.pdf#page=16 + +- name: Case 1, 2020 federal non-itemizer's line 10b deduction is added back on line 19a. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NY + output: + # Federal AGI (IT-201 line 19) 40,000 - 300; line 19a adds the 300 back. + adjusted_gross_income: 39_700 + ny_recomputed_federal_agi: 40_000 + ny_additions: 0 + ny_agi: 40_000 + +- name: Case 2, 2020 IT-196 floors use recomputed federal AGI. + period: 2020 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 300 + other_medical_expenses: 6_000 + tax_units: + tax_unit: + members: [person1] + total_misc_deductions: 10_000 + households: + household: + members: [person1] + state_code: NY + output: + ny_recomputed_federal_agi: 40_000 + # IT-196 line 3: 10% of line 19a = 4,000; 6,000 - 4,000. + ny_medical_expense_deduction: 2_000 + # IT-196 line 26-27: 2% of line 19a = 800; 10,000 - 800. + ny_misc_deduction: 9_200 + +- name: Case 3, 2021 recomputed federal AGI is federal AGI. + period: 2021 + input: + people: + person1: + age: 40 + employment_income: 40_000 + charitable_cash_donations: 500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NY + output: + adjusted_gross_income: 40_000 + ny_recomputed_federal_agi: 40_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_ctc_pre_2024.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_ctc_pre_2024.yaml index d083f389a8d..03b750ec757 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_ctc_pre_2024.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_ctc_pre_2024.yaml @@ -97,3 +97,126 @@ output: ny_ctc: 330 ny_ctc_pre_2024: 330 + +- name: 2020 Empire State child credit recomputes the federal credit with line 19a + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 110_100 + charitable_cash_donations: 300 + person2: + age: 40 + person3: + age: 10 + tax_units: + tax_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NY + output: + # Federal AGI 110,100 - 300 = 109,800; IT-201 line 19a adds the 300 back. + adjusted_gross_income: 109_800 + ny_recomputed_federal_agi: 110_100 + # IT-213 Worksheet A (2020 instructions, file page 2) with line 19a: + # 110,100 is over the $110,000 joint threshold; the excess rounds up to + # $1,000, so 5% = $50 off the $1,000 credit; 33% x 950 = 313.50. + ny_ctc_pre_2024: 313.5 + +- name: 2020 Empire State child credit adds only the deduction to federal AGI, recomputing nothing else + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + employment_income: 62_690 + social_security_retirement: 30_000 + charitable_cash_donations: 300 + person2: + age: 67 + social_security_retirement: 30_000 + person3: + age: 10 + tax_units: + tax_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: NY + output: + # Line 19a = federal AGI as reported (with the taxable Social Security + # figured after line 10b) plus the 300: 109,521.50 + 300 = 109,821.50, + # under the $110,000 joint threshold, so 33% of the full $1,000 (Sol + # review r3). + adjusted_gross_income: 109_521.5 + ny_recomputed_federal_agi: 109_821.5 + ny_ctc_pre_2024: 330 + +- name: 2020 Empire State child credit keeps the federal tax as reported + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + taxable_pension_income: 23_030 + charitable_cash_donations: 300 + person2: + age: 10 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + output: + # IT-213 Worksheet A lines 7-8 take Form 1040 line 18 as reported: the + # federal tax is $408 (AGI 22,730, head of household), which limits the + # nonrefundable $1,000 credit; there are no earnings for the refundable + # part. 33% x 408 = 134.64 (Sol review r3). + income_tax_before_credits: 408 + ny_ctc_pre_2024: 134.64 + +- name: 2020 Empire State child credit carries the federal EITC as reported + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 67 + employment_income: 3_000 + taxable_pension_income: 52_925 + charitable_cash_donations: 300 + person2: + age: 67 + person3: + age: 10 + person4: + age: 10 + person5: + age: 10 + person6: + age: 10 + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5, person6] + households: + household: + members: [person1, person2, person3, person4, person5, person6] + state_code: NY + output: + adjusted_gross_income: 55_625 + ny_recomputed_federal_agi: 55_925 + income_tax_before_credits: 2_992 + # IT-213 Worksheet C line 7 carries federal Schedule 8812 line 13 as + # reported: the federal EITC as reported exceeds the $229.50 of payroll + # taxes, so the refundable part is zero, and the credit is 33% of the + # $2,992 of tax the nonrefundable part is limited to (Sol review r4). + ny_ctc_pre_2024: 987.36 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_eitc.yaml index 8ff2589fd60..56bdd57f9fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_eitc.yaml @@ -199,3 +199,30 @@ # 543 - 7.65% x (12,000 - 8,880) = 304.32; 30% = 91.30; less the $45 # household credit (table 1, 7,000-20,000) = 46.30 ny_eitc: 46.30 + +- name: 2020 NY EITC matches the federal credit recomputed with line 19a + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + employment_income: 40_800 + taxable_interest_income: 1_000 + charitable_cash_donations: 300 + person2: + age: 10 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + output: + # Federal AGI 41,800 - 300 = 41,500, under the $41,756 one-child limit; + # recomputed federal AGI (line 19a) is 41,800, over it (2020 IT-215 + # instructions, file page 2: recompute the federal credit with line 19a). + adjusted_gross_income: 41_500 + ny_recomputed_federal_agi: 41_800 + ny_eitc: 0 diff --git a/policyengine_us/tools/general.py b/policyengine_us/tools/general.py index d2bd4f5fd5f..2f8254e327c 100644 --- a/policyengine_us/tools/general.py +++ b/policyengine_us/tools/general.py @@ -185,6 +185,22 @@ def filer_share(person, period, amounts): return where(total > 0, proportional, equal) +def get_itemization_branch(tax_unit, period, itemizes: bool): + """ + The branch of ``tax_unit``'s simulation that fixes ``tax_unit_itemizes``. + + It is named ``itemizing`` or ``not_itemizing``. Every caller asks for the + same inputs, so they share one branch: the choice itself and the tax + liabilities it compares. + """ + return get_override_branch( + tax_unit.simulation, + "itemizing" if itemizes else "not_itemizing", + period, + {"tax_unit_itemizes": np.full(tax_unit.count, itemizes, dtype=bool)}, + ) + + def sum_contained_tax_units(var, population, period): tax_unit = population.members.tax_unit.reference_entity values = tax_unit(var, period) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py index c634698f7e5..ac0389d9366 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/capped_qualified_tuition_expenses_ald.py @@ -15,6 +15,8 @@ class capped_qualified_tuition_expenses_ald(Variable): ) reference = ( "https://www.irs.gov/pub/irs-pdf/f8917.pdf#page=2", + # 2020 Pub. 970, Worksheet 6-1, MAGI for the Tuition and Fees Deduction. + "https://www.irs.gov/pub/irs-prior/p970--2020.pdf#page=46", # Law was repealed starting tax year 2021. "https://irc.bloombergtax.com/public/uscode/doc/irc/section_222", ) @@ -53,6 +55,10 @@ def formula(tax_unit, period, parameters): for source in safe_sources: magi += not_dependent * max_(0, add(person, period, [source])) magi = tax_unit.sum(magi) + # 2020 Pub. 970 Worksheet 6-1 line 2 subtracts Form 1040 line 10b, + # the charitable deduction for non-itemizers, with the Schedule 1 + # adjustments (which this simplified MAGI leaves out). + magi = magi - tax_unit("charitable_contributions_ald", period) p = parameters(period).gov.irs.deductions.tuition_and_fees joint = filing_status == filing_status.possible_values.JOINT cap = where(joint, p.joint.calc(magi), p.non_joint.calc(magi)) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.py new file mode 100644 index 00000000000..dc24903def4 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.py @@ -0,0 +1,52 @@ +from policyengine_us.model_api import * + + +class charitable_contributions_ald(Variable): + value_type = float + entity = TaxUnit + label = "Above-the-line charitable deduction for non-itemizers" + unit = USD + documentation = ( + "Above-the-line deduction for cash charitable contributions by a tax " + "unit that does not itemize (26 U.S.C. 62(a)(22), 2020 Form 1040 line " + "10b), which reduces adjusted gross income. It applies to taxable " + "years beginning in 2020 only; the 2021 and later deductions for " + "non-itemizers (26 U.S.C. 170(p)) are taken from adjusted gross " + "income in charitable_deduction_for_non_itemizers. It counts the " + "head's and spouse's cash gifts to organizations described in " + "section 170(b)(1)(A); a dependent's gifts are on the dependent's " + "own return. The inputs do not separate gifts to supporting " + "organizations and donor advised funds, which 62(f)(2) excludes, so " + "they are counted. The $300 cap applies to every return, including " + "married filing separately: the 2020 Form 1040 instructions' $150 " + "for separate returns has no basis in the statute." + ) + definition_period = YEAR + reference = ( + # CARES Act sec. 2204: 26 U.S.C. 62(a)(22) and 62(f). + "https://www.congress.gov/116/plaws/publ136/PLAW-116publ136.pdf#page=65", + # 2020 Instructions for Form 1040, line 10b. + "https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=30", + ) + + def formula(tax_unit, period, parameters): + # Only in years that list it among above-the-line deductions, where + # tax_unit_itemizes takes the itemization choice from branches. + # State additions that read it then never reach the choice in other + # years. + if ( + "charitable_contributions_ald" + not in parameters(period).gov.irs.ald.deductions + ): + return 0 + p = parameters(period).gov.irs.ald.charitable_contributions + # 62(f)(1): an eligible individual is one who does not elect to + # itemize deductions. + itemizes = tax_unit("tax_unit_itemizes", period) + # 62(f)(2): contributions made in cash to an organization described + # in section 170(b)(1)(A), by the head or spouse. + cash_donations = tax_unit_non_dep_add( + tax_unit, period, ["resolved_charitable_cash_donations_50_pct"] + ) + filing_status = tax_unit("filing_status", period) + return ~itemizes * min_(cash_donations, p.cap[filing_status]) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald_person.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald_person.py new file mode 100644 index 00000000000..a925a24f78a --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald_person.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class charitable_contributions_ald_person(Variable): + value_type = float + entity = Person + label = "Above-the-line charitable deduction for non-itemizers for each person" + unit = USD + documentation = ( + "Each head's or spouse's part of the tax unit's 2020 above-the-line " + "charitable deduction (charitable_contributions_ald), in proportion " + "to their own cash gifts to organizations described in section " + "170(b)(1)(A), or equally when neither gave. 62(a)(22) counts the " + "contributions made by the eligible individual. A tax unit " + "dependent's part is zero." + ) + definition_period = YEAR + reference = ( + # CARES Act sec. 2204: 26 U.S.C. 62(a)(22) and 62(f). + "https://www.congress.gov/116/plaws/publ136/PLAW-116publ136.pdf#page=65", + ) + + def formula(person, period, parameters): + amount = person.tax_unit("charitable_contributions_ald", period) + own = person("resolved_charitable_cash_donations_50_pct", period) + return amount * filer_share(person, period, own) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.py index a7fa661a339..a97dabd8e5d 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/charitable_deduction_for_non_itemizers.py @@ -6,9 +6,17 @@ class charitable_deduction_for_non_itemizers(Variable): entity = TaxUnit label = "Charitable deduction for non-itemizers" unit = USD - documentation = "Charitable deduction amount for non-itemizers." + documentation = ( + "Charitable deduction for non-itemizers under 26 U.S.C. 170(p), taken " + "from adjusted gross income (2021 Form 1040 line 12b, and from 2026). " + "The 2020 deduction for non-itemizers is above the line: " + "charitable_contributions_ald." + ) definition_period = YEAR - reference = "https://www.irs.gov/newsroom/expanded-tax-benefits-help-individuals-and-businesses-give-to-charity-during-2021-deductions-up-to-600-available-for-cash-donations-by-non-itemizers" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/170#p", + "https://www.irs.gov/newsroom/expanded-tax-benefits-help-individuals-and-businesses-give-to-charity-during-2021-deductions-up-to-600-available-for-cash-donations-by-non-itemizers", + ) def formula(tax_unit, period, parameters): p = parameters(period).gov.irs.deductions.itemized.charity diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py index bbfb5c097ba..532a205d3a4 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_itemizing.py @@ -9,11 +9,5 @@ class tax_liability_if_itemizing(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): - simulation = tax_unit.simulation - itemized_branch = get_override_branch( - simulation, - "itemizing", - period, - {"tax_unit_itemizes": np.ones((tax_unit.count,), dtype=bool)}, - ) - return itemized_branch.calculate("income_tax", period) + branch = get_itemization_branch(tax_unit, period, itemizes=True) + return branch.calculate("income_tax", period) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py index 07279c5639b..1853e36cfd4 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_liability_if_not_itemizing.py @@ -10,11 +10,5 @@ class tax_liability_if_not_itemizing(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): - simulation = tax_unit.simulation - non_itemized_branch = get_override_branch( - simulation, - "not_itemizing", - period, - {"tax_unit_itemizes": np.zeros((tax_unit.count,), dtype=bool)}, - ) - return non_itemized_branch.calculate("income_tax", period) + branch = get_itemization_branch(tax_unit, period, itemizes=False) + return branch.calculate("income_tax", period) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py index ab7736c1835..30e413249e2 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tax_unit_itemizes.py @@ -10,9 +10,42 @@ class tax_unit_itemizes(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): - standard_deduction = tax_unit("standard_deduction", period) - limitation = parameters(period).gov.irs.deductions.itemized.limitation - if limitation.applies and limitation.obbb.applies: + p = parameters(period).gov.irs + limitation = p.deductions.itemized.limitation + if "charitable_contributions_ald" in p.ald.deductions: + # In 2020 a unit that does not itemize deducts charitable + # contributions above the line (26 U.S.C. 62(a)(22)), so adjusted + # gross income depends on the choice, and with it the itemized + # deductions it limits (the medical floor, the charitable + # ceiling) and any standard deduction that phases out with it. + # Each comes from the branch that makes that choice (the ones + # that also give the tax liabilities compared below). + itemizing = get_itemization_branch(tax_unit, period, itemizes=True) + not_itemizing = get_itemization_branch(tax_unit, period, itemizes=False) + # The deductions compared are everything each choice takes from + # gross income: every deduction from adjusted gross income on + # that side (with the qualified business income deduction and, + # for an itemizer, wagering losses), and, for not itemizing, the + # lower adjusted gross income (line 10b and what follows from it, + # such as taxable Social Security). + agi_reduction = itemizing.calculate( + "adjusted_gross_income", period + ) - not_itemizing.calculate("adjusted_gross_income", period) + itemized_deductions = itemizing.calculate( + "taxable_income_deductions_if_itemizing", period + ) + standard_deduction = ( + not_itemizing.calculate( + "taxable_income_deductions_if_not_itemizing", period + ) + + agi_reduction + ) + elif limitation.applies and limitation.obbb.applies: + standard_deduction = add( + tax_unit, + period, + ["standard_deduction", "charitable_deduction_for_non_itemizers"], + ) # The itemized deductions the unit would claim if it itemized. # The P.L. 119-21 section 68 limitation nets them of the # qualified business income deduction, which depends on the @@ -20,16 +53,15 @@ def formula(tax_unit, period, parameters): # (the one that also gives tax_liability_if_itemizing). # Calculated before branching, so the branch inherits it. tax_unit("total_itemized_taxable_income_deductions", period) - itemizing_branch = get_override_branch( - tax_unit.simulation, - "itemizing", + itemized_deductions = get_itemization_branch( + tax_unit, period, itemizes=True + ).calculate("itemized_taxable_income_deductions", period) + else: + standard_deduction = add( + tax_unit, period, - {"tax_unit_itemizes": np.ones((tax_unit.count,), dtype=bool)}, - ) - itemized_deductions = itemizing_branch.calculate( - "itemized_taxable_income_deductions", period + ["standard_deduction", "charitable_deduction_for_non_itemizers"], ) - else: itemized_deductions = tax_unit("itemized_taxable_income_deductions", period) if parameters(period).gov.simulation.branch_to_determine_itemization: # determine federal itemization behavior by comparing tax liability diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_applicable_percentage.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_applicable_percentage.py index 2bec01f1e12..be2606ee535 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_applicable_percentage.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_applicable_percentage.py @@ -17,7 +17,7 @@ def formula(tax_unit, period, parameters): # § 11-1706 Credits against tax (e). # First get federal AGI. - income = tax_unit("adjusted_gross_income", period) + income = tax_unit("ny_recomputed_federal_agi", period) # Then get the CDCC part of the parameter tree. p = parameters(period).gov.local.ny.nyc.tax.income.credits.cdcc diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.py index 0724bfa2e38..b028a199b7f 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/cdcc/nyc_cdcc_eligible.py @@ -9,11 +9,11 @@ class nyc_cdcc_eligible(Variable): defined_for = "in_nyc" def formula(tax_unit, period, parameters): - # Eligibility is based on having a federal AGI below $30k - # and being an NYC full-time resident. + # Eligibility is based on having a recomputed federal AGI (IT-201 + # line 19a) below $30k and being an NYC full-time resident. # First get their income. - income = tax_unit("adjusted_gross_income", period) + income = tax_unit("ny_recomputed_federal_agi", period) # Then get the CDCC part of the parameter tree. p = parameters(period).gov.local.ny.nyc.tax.income.credits.cdcc diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py index c919d1626d0..3f470e086f5 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py @@ -10,9 +10,9 @@ class nyc_household_credit(Variable): defined_for = "in_nyc" def formula(tax_unit, period, parameters): - # First get their federal AGI. - # Technically based on recomputed AGI, which deviates slightly from federal AGI. - federal_agi = tax_unit("adjusted_gross_income", period) + # Recomputed federal AGI (IT-201 line 19a), which differs from + # federal AGI in 2020 by the charitable deduction for non-itemizers. + federal_agi = tax_unit("ny_recomputed_federal_agi", period) # Then get their filing status. filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit.py index 96bf0cd4f4b..7af2af6eb63 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit.py @@ -22,7 +22,7 @@ def formula(tax_unit, period, parameters): # proportion to federal adjusted gross income above the threshold and # reaches zero at the phase-out width. Line 5 rounds the fraction to # the fourth decimal place. - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) threshold = tax_unit( "nyc_income_tax_elimination_credit_income_threshold", period ) diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit_eligible.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit_eligible.py index ba473a6818c..995dfeb3dbb 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit_eligible.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/income_tax_elimination/nyc_income_tax_elimination_credit_eligible.py @@ -28,7 +28,7 @@ def formula(tax_unit, period, parameters): # § 1310(h)(1)(B) and (h)(2); line C: federal adjusted gross income # (§ 1310(h)(3)(B)) is no more than the phase-out width above the # threshold. - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) threshold = tax_unit( "nyc_income_tax_elimination_credit_income_threshold", period ) diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/nyc_eitc.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/nyc_eitc.py index 774f7369abc..99a817c8382 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/nyc_eitc.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/nyc_eitc.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.ny.tax.income.adjusted_gross_income.ny_recomputed_federal_agi import ( + get_ny_recomputed_branch, +) class nyc_eitc(Variable): @@ -12,7 +15,13 @@ class nyc_eitc(Variable): def formula(tax_unit, period, parameters): # First get their NYAGI and EITC. ny_agi = tax_unit("ny_agi", period) - federal_eitc = tax_unit("eitc", period) + # The federal credit recomputed with recomputed federal AGI (IT-201 + # line 19a; IT-215 instructions) when it differs from federal AGI. + branch = get_ny_recomputed_branch(tax_unit, period) + if branch is None: + federal_eitc = tax_unit("eitc", period) + else: + federal_eitc = branch.calculate("eitc", period) # Then get the EITC part of the parameter tree. p = parameters(period).gov.local.ny.nyc.tax.income.credits.eitc diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.py index 71e1de010c6..7e2a4fc06cf 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/school/nyc_school_credit_income.py @@ -20,13 +20,12 @@ def formula(tax_unit, period, parameters): # The credit uses income as defined in RPTL § 425(4)(b)(ii): federal # adjusted gross income reduced by distributions from individual # retirement accounts and annuities, to the extent included in - # federal adjusted gross income (IT-201 line 19 minus line 9; the - # 2022 instructions used recomputed federal AGI, line 19a, which is - # not modeled separately). + # federal adjusted gross income (IT-201 line 19 minus line 9; line 19a, + # recomputed federal AGI, in the 2020 and 2022 instructions). # Roth conversions are not identified as IRA-sourced, and individual # retirement annuity distributions recorded as pension income are not # identified separately, so both stay in income. - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) p = parameters(period).gov.local.ny.nyc.tax.income.credits.school person = tax_unit.members # Federal gross income excludes dependents' income, so only the diff --git a/policyengine_us/variables/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.py b/policyengine_us/variables/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.py index 2855d2b092e..0817fa10030 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.py +++ b/policyengine_us/variables/gov/states/az/tax/income/deductions/standard/az_increased_standard_deduction_for_charitable_contributions.py @@ -6,7 +6,12 @@ class az_increased_standard_deduction_for_charitable_contributions(Variable): entity = TaxUnit label = "Arizona increased standard deduction for charitable contributions" unit = USD - reference = "https://www.azleg.gov/viewdocument/?docName=https://www.azleg.gov/ars/43/01041.htm" + reference = ( + "https://www.azleg.gov/viewdocument/?docName=https://www.azleg.gov/ars/43/01041.htm", + # 2020 Form 140 booklet, Standard Deduction Increase for Charitable + # Contributions worksheet, Note 1 and line 5C. + "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2020_140BOOKLET.pdf#page=25", + ) definition_period = YEAR defined_for = StateCode.AZ @@ -16,8 +21,16 @@ def formula(tax_unit, period, parameters): charitable_contributions_credit = tax_unit( "az_charitable_contributions_credit_potential", period ) + # 2020 worksheet line 5C: contributions already deducted on the + # federal return by a filer who did not itemize (line 10b). + federal_non_itemizer_deduction = tax_unit( + "charitable_contributions_ald", period + ) charitable_deduction_after_credit = max_( - charitable_deduction - charitable_contributions_credit, 0 + charitable_deduction + - federal_non_itemizer_deduction + - charitable_contributions_credit, + 0, ) # Through TY2025, the add-on is a share of charitable contributions # (the rate). From TY2026 (HB 4168), the rate is zero and the add-on diff --git a/policyengine_us/variables/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.py b/policyengine_us/variables/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.py index e3be7b19789..ba63fa86196 100644 --- a/policyengine_us/variables/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.py +++ b/policyengine_us/variables/gov/states/ca/calepa/carb/cvrp/is_ca_cvrp_normal_rebate_eligible.py @@ -9,12 +9,22 @@ class is_ca_cvrp_normal_rebate_eligible(Variable): documentation = ( "Eligible for California Clean Vehicle Rebate Project (CVRP) normal rebate" ) - reference = "https://cleanvehiclerebate.org/en/eligibility-guidelines" + reference = ( + "https://cleanvehiclerebate.org/en/eligibility-guidelines", + # CVRP Implementation Manual: gross annual income is Form 1040 lines + # 1-7 and Schedule 1 lines 1-8, before adjustments. + "http://web.archive.org/web/20220305190415/https://cleanvehiclerebate.org/sites/default/files/docs/nav/transportation/cvrp/documents/CVRP-Implementation-Manual.pdf#page=13", + ) defined_for = StateCode.CA def formula(person, period, parameters): - # AGI must be less than the threshold. - agi = person.tax_unit("adjusted_gross_income", period) + # AGI must be less than the threshold. The program counts income + # before adjustments, so the 2020 charitable deduction for + # non-itemizers (Form 1040 line 10b) does not lower it. + tax_unit = person.tax_unit + agi = tax_unit("adjusted_gross_income", period) + tax_unit( + "charitable_contributions_ald", period + ) filing_status = person.tax_unit("filing_status", period) caps = parameters(period).gov.states.ca.calepa.carb.cvrp.income_cap return agi <= caps[filing_status] diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py index 4a3d173fefa..8a0bee91b3e 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py @@ -7,10 +7,16 @@ class ca_additions(Variable): label = "CA AGI additions to federal AGI" unit = USD definition_period = YEAR - reference = "https://www.ftb.ca.gov/forms/2021/2021-540.pdf" + reference = ( + "https://www.ftb.ca.gov/forms/2021/2021-540.pdf", + # 2020 Schedule CA (540) instructions, Part I, Section C, line 22, + # column B: the federal Form 1040 line 10b charitable deduction. + "https://www.ftb.ca.gov/forms/2020/2020-540-booklet.pdf#page=48", + ) defined_for = StateCode.CA adds = [ "ca_hsa_addition", "ca_educator_expense_addition", "ca_other_additions", + "charitable_contributions_ald", ] diff --git a/policyengine_us/variables/gov/states/in/tax/income/add_backs/in_add_backs.py b/policyengine_us/variables/gov/states/in/tax/income/add_backs/in_add_backs.py index 0c5500e464c..ca650e09e63 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/add_backs/in_add_backs.py +++ b/policyengine_us/variables/gov/states/in/tax/income/add_backs/in_add_backs.py @@ -7,10 +7,16 @@ class in_add_backs(Variable): label = "Indiana add-backs" unit = USD definition_period = YEAR - reference = "http://iga.in.gov/legislative/laws/2021/ic/titles/006#6-3-1-3.5" + reference = ( + "http://iga.in.gov/legislative/laws/2021/ic/titles/006#6-3-1-3.5", + # Information Bulletin 119: IC 6-3-1-3.5(a)(26) adds back the 2020 + # IRC 62(a)(22) charitable deduction (code 120). + "https://www.in.gov/dor/files/reference/ib119.pdf#page=2", + ) defined_for = StateCode.IN adds = [ + "charitable_contributions_ald", "in_bonus_depreciation_add_back", "in_nol_add_back", "in_oos_municipal_obligation_interest_add_back", diff --git a/policyengine_us/variables/gov/states/ky/tax/income/ky_agi.py b/policyengine_us/variables/gov/states/ky/tax/income/ky_agi.py index baf0791882a..00dfde92c1f 100644 --- a/policyengine_us/variables/gov/states/ky/tax/income/ky_agi.py +++ b/policyengine_us/variables/gov/states/ky/tax/income/ky_agi.py @@ -8,12 +8,18 @@ class ky_agi(Variable): unit = USD definition_period = YEAR reference = ( - "https://revenue.ky.gov/Forms/740%20Packet%20Instructions%205-9-23.pdf#page=11" + "https://revenue.ky.gov/Forms/740%20Packet%20Instructions%205-9-23.pdf#page=11", + # 2020 Form 740 instructions, Schedule M line 5: contributions + # claimed on federal Form 1040 line 10b are added back. + "https://revenue.ky.gov/Forms/740%20Instructions%20Packet%205.7.21.pdf#page=27", ) defined_for = StateCode.KY adds = [ "ky_additions", "ky_federal_agi", + # Schedule M line 5 (2020): the federal charitable deduction for + # non-itemizers, in the part ky_federal_agi deducts from each filer. + "charitable_contributions_ald_person", ] subtracts = ["ky_subtractions"] diff --git a/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py b/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py index eac3dca4bfc..14b5f968ce9 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py @@ -12,11 +12,20 @@ class mn_amt_taxable_income(Variable): "https://www.revenue.state.mn.us/sites/default/files/2023-01/m1mt_22.pdf", # 2024 Schedule M1MT, line 12 "https://www.revenue.state.mn.us/sites/default/files/2025-07/m1mt-24.pdf#page=1", + # 2020 Schedule M1MT instructions, line 4: includes Schedule M1NC + # line 6, the federal charitable deduction for non-itemizers. + "https://www.revenue.state.mn.us/sites/default/files/2023-02/m1mt_20.pdf#page=2", + # Laws 2023, ch. 1, sec. 22(c)(2) and (d)(2): the addition applies + # to alternative minimum taxable income. + "https://www.revisor.mn.gov/laws/2023/0/Session+Law/Chapter/1/", ) defined_for = StateCode.MN def formula(tax_unit, period, parameters): fagi = tax_unit("adjusted_gross_income", period) + # Line 4 adds back the 2020 federal charitable deduction for + # non-itemizers (Schedule M1NC line 6). + charitable_addition = tax_unit("charitable_contributions_ald", period) itemizing = tax_unit("mn_itemizing", period) SOME_DEDUCTIONS = [ "charitable_deduction", @@ -33,4 +42,4 @@ def formula(tax_unit, period, parameters): "mn_military_pension_subtraction", ] amt_subs = add(tax_unit, period, AMT_SUBTRACTIONS) - return fagi - some_itm_deds - amt_subs + return fagi + charitable_addition - some_itm_deds - amt_subs diff --git a/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py b/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py index 74d3c37ea4f..e6739183eee 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_social_security_subtraction.py @@ -10,6 +10,9 @@ class mn_social_security_subtraction(Variable): reference = ( "https://www.taxformfinder.org/forms/2021/2021-minnesota-form-m1m.pdf", "https://www.revenue.state.mn.us/sites/default/files/2024-01/m1m-22.pdf", + # 2020 Schedule M1M instructions, line 12 worksheet, step 8: federal + # Schedule 1 adjustments only, not Form 1040 line 10b. + "https://www.revenue.state.mn.us/sites/default/files/2024-01/m1m_20.pdf#page=8", ) defined_for = StateCode.MN @@ -47,7 +50,10 @@ def formula(tax_unit, period, parameters): sum_income = adj_income + oasdi_amount + tax_exempt_int us_ald = tax_unit("above_the_line_deductions", period) student_loan_int = add(tax_unit, period, ["student_loan_interest"]) - mn_ald = max_(0, us_ald - student_loan_int) + # Step 8 counts the Schedule 1 adjustments, so not the 2020 + # charitable deduction for non-itemizers (Form 1040 line 10b). + charitable_ald = tax_unit("charitable_contributions_ald", period) + mn_ald = max_(0, us_ald - student_loan_int - charitable_ald) income = max_(0, sum_income - mn_ald) net_income = max_(0, income - income_amount) alt_sub_amt = max_(0, alt_amount - (net_income * p.net_income_fraction)) diff --git a/policyengine_us/variables/gov/states/nc/tax/income/nc_additions.py b/policyengine_us/variables/gov/states/nc/tax/income/nc_additions.py index a29bb042fbb..e81d1724624 100644 --- a/policyengine_us/variables/gov/states/nc/tax/income/nc_additions.py +++ b/policyengine_us/variables/gov/states/nc/tax/income/nc_additions.py @@ -7,6 +7,11 @@ class nc_additions(Variable): label = "North Carolina additions to the adjusted gross income" unit = USD definition_period = YEAR + reference = ( + # 2020 D-400 Schedule S, Part A, line 16: above-the-line qualified + # charitable contribution deduction. + "https://www.ncdor.gov/documents/files/2020-d-400-schedule-s-web/open#page=2", + ) defined_for = StateCode.NC - adds = ["nc_other_additions"] + adds = ["nc_other_additions", "charitable_contributions_ald"] diff --git a/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_agi.py b/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_agi.py index e8ca05d62ae..ef552b070d2 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_agi.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_agi.py @@ -10,5 +10,6 @@ class ny_agi(Variable): reference = "https://www.law.cornell.edu/regulations/new-york/20-NYCRR-132.1" defined_for = StateCode.NY - adds = ["adjusted_gross_income", "ny_additions"] + # IT-201 line 19a, recomputed federal AGI, plus the New York additions. + adds = ["ny_recomputed_federal_agi", "ny_additions"] subtracts = ["ny_agi_subtractions"] diff --git a/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_disability_income_exclusion.py b/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_disability_income_exclusion.py index fbad7a5fead..ee398ae9f00 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_disability_income_exclusion.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_disability_income_exclusion.py @@ -40,7 +40,7 @@ def formula(person, period, parameters): # Lines 1-10: each person's pay at most $100 a week, reduced dollar # for dollar by federal AGI above $15,000. excludable = tax_unit.sum(min_(disability_pay, p.cap)) - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) exclusion = max_(0, excludable - max_(0, agi - p.reduction_threshold)) # Line 11: spouses share it in proportion to their disability pay. total_pay = tax_unit.sum(disability_pay) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_recomputed_federal_agi.py b/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_recomputed_federal_agi.py new file mode 100644 index 00000000000..c48ea7adf07 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/tax/income/adjusted_gross_income/ny_recomputed_federal_agi.py @@ -0,0 +1,67 @@ +from policyengine_us.model_api import * + + +def get_ny_recomputed_branch(tax_unit, period, keep_reported_tax_and_eitc=False): + """ + The branch whose federal adjusted gross income is New York's recomputed + federal AGI (Form IT-201 line 19a), or None when the two are equal for + every tax unit. + + New York matches federal credits computed with line 19a (the IT-213 and + IT-215 recomputations). Line 19a is federal AGI as reported plus the Form + IT-558 additions, of which the model has one, A-003, the 2020 federal + charitable deduction for non-itemizers. Nothing else on the federal + return is recomputed, so the branch sets adjusted gross income to line + 19a and keeps each unit's federal itemization choice. The deduction is + zero in the branch, which already counts it in adjusted gross income. + + With ``keep_reported_tax_and_eitc``, for the Empire State child credit, + the branch also keeps income tax before credits (Form 1040 line 18, + which IT-213 Worksheet A takes as reported) and the federal earned income + credit (which Schedule 8812 lines 12-13, carried by IT-213 Worksheet C + line 7, take as reported). The New York and New York City EITC branch + does not read the tax: New York City income tax enters the federal + deduction for state and local taxes. + """ + deduction = tax_unit("charitable_contributions_ald", period) + if not deduction.any(): + return None + overrides = { + "adjusted_gross_income": tax_unit("ny_recomputed_federal_agi", period), + "charitable_contributions_ald": np.zeros(tax_unit.count), + "tax_unit_itemizes": tax_unit("tax_unit_itemizes", period), + } + name = "ny_recomputed_federal_agi" + if keep_reported_tax_and_eitc: + overrides["income_tax_before_credits"] = tax_unit( + "income_tax_before_credits", period + ) + overrides["eitc"] = tax_unit("eitc", period) + name = "ny_recomputed_federal_agi_for_child_credit" + return get_override_branch(tax_unit.simulation, name, period, overrides) + + +class ny_recomputed_federal_agi(Variable): + value_type = float + entity = TaxUnit + label = "New York recomputed federal adjusted gross income" + unit = USD + documentation = ( + "Federal adjusted gross income as reported, plus the Form IT-558 " + "addition adjustments and less its subtraction adjustments (Form " + "IT-201 line 19a). New York AGI, the credits and the itemized " + "deduction floors and limitation start from it. The adjustment " + "modeled is A-003: the 2020 federal charitable deduction for " + "non-itemizers (IRC 62(a)(22)), which New York did not adopt." + ) + definition_period = YEAR + reference = ( + # 2020 IT-201 instructions, line 19a worksheet. + "https://www.tax.ny.gov/pdf/2020/inc/it201i_2020.pdf#page=16", + # 2020 IT-558 instructions, addition A-003. + "https://www.tax.ny.gov/pdf/2020/inc/it558i_2020.pdf#page=2", + ) + # Not limited to New York: its readers are, and New York City residents + # are New York residents. + + adds = ["adjusted_gross_income", "charitable_contributions_ald"] diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_federal_rate.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_federal_rate.py index 8b109c5074e..d230a05aac8 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_federal_rate.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/cdcc/ny_cdcc_federal_rate.py @@ -20,6 +20,6 @@ def formula(tax_unit, period, parameters): # AGI for all years. New York does not use the federal Form 2441 # rate, so the 2021 ARPA rates do not apply. p = parameters(period).gov.states.ny.tax.income.credits.cdcc.federal_rate - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) steps = np.ceil(max_(0, agi - p.start) / p.increment) return max_(p.min, p.max - steps * p.step) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_additional_ctc.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_additional_ctc.py index 3055b122085..9e3a1cd425e 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_additional_ctc.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_additional_ctc.py @@ -11,7 +11,7 @@ class ny_additional_ctc(Variable): defined_for = StateCode.NY def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) p = parameters(period).gov.states.ny.tax.income.credits.ctc.additional match = p.amount.calc(agi) base_ctc = tax_unit("ny_ctc", period) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_post_2024_phase_out.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_post_2024_phase_out.py index a77307116da..bd3bbbb2040 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_post_2024_phase_out.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_post_2024_phase_out.py @@ -13,7 +13,7 @@ class ny_ctc_post_2024_phase_out(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ny.tax.income.credits.ctc - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) filing_status = tax_unit("filing_status", period) base_credit = tax_unit("ny_ctc_post_2024_base", period) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_pre_2024.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_pre_2024.py index 24c4fd4914c..b032548edef 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_pre_2024.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ctc/ny_ctc_pre_2024.py @@ -3,6 +3,9 @@ CREDIT_DEPENDENT_VARIABLES, get_pre_tcja_ctc_tbs, ) +from policyengine_us.variables.gov.states.ny.tax.income.adjusted_gross_income.ny_recomputed_federal_agi import ( + get_ny_recomputed_branch, +) class ny_ctc_pre_2024(Variable): @@ -14,14 +17,18 @@ class ny_ctc_pre_2024(Variable): ) unit = USD definition_period = YEAR - reference = "https://www.nysenate.gov/legislation/laws/TAX/606" # (c-1) + reference = ( + "https://www.nysenate.gov/legislation/laws/TAX/606", # (c-1) + # 2020 IT-213 instructions: the credit uses recomputed federal AGI. + "https://www.tax.ny.gov/pdf/2020/inc/it213i_2020.pdf#page=2", + ) defined_for = "ny_ctc_pre_2024_eligible" def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ny.tax.income.credits.ctc person = tax_unit.members age = person("age", period) - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) # Original CTC rules (pre-2025 and post-2027) if not p.pre_tcja: @@ -31,7 +38,13 @@ def formula(tax_unit, period, parameters): else: # Initialize pre-TCJA CTC branch with cached pinned parameters # (one clone per process; see tools/pinned_tbs.py, issue #8114). - simulation = tax_unit.simulation + # IT-213 Worksheet A computes the federal credit with recomputed + # federal AGI (IT-201 line 19a), so the branch starts from the + # one where federal AGI is line 19a when they differ. + recomputed = get_ny_recomputed_branch( + tax_unit, period, keep_reported_tax_and_eitc=True + ) + simulation = tax_unit.simulation if recomputed is None else recomputed pre_tcja_ctc = get_branch_for_period(simulation, "pre_tcja_ctc", period) pre_tcja_ctc.tax_benefit_system = get_pre_tcja_ctc_tbs( simulation.tax_benefit_system diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_eitc.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_eitc.py index 20c38b23cdb..c2abee94cf2 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_eitc.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_eitc.py @@ -1,5 +1,8 @@ from policyengine_us.model_api import * from policyengine_us.tools.pinned_tbs import get_pre_arpa_eitc_tbs +from policyengine_us.variables.gov.states.ny.tax.income.adjusted_gross_income.ny_recomputed_federal_agi import ( + get_ny_recomputed_branch, +) class ny_eitc(Variable): @@ -11,6 +14,9 @@ class ny_eitc(Variable): reference = ( "https://www.nysenate.gov/legislation/laws/TAX/606", # (d) "https://www.tax.ny.gov/pdf/2021/inc/it215i_2021.pdf", + # 2020 IT-215 instructions: recompute the federal credit with line + # 19a when lines 19 and 19a differ. + "https://www.tax.ny.gov/pdf/2020/inc/it215i_2020.pdf#page=2", ) defined_for = StateCode.NY @@ -32,7 +38,13 @@ def formula(tax_unit, period, parameters): branch.delete_arrays(variable) federal_eitc = branch.tax_unit("eitc", period) else: - federal_eitc = tax_unit("eitc", period) + # IT-215: the federal credit recomputed with recomputed federal + # AGI (IT-201 line 19a) when it differs from federal AGI. + branch = get_ny_recomputed_branch(tax_unit, period) + if branch is None: + federal_eitc = tax_unit("eitc", period) + else: + federal_eitc = branch.calculate("eitc", period) tentative_nys_eitc = federal_eitc * p.eitc.match income_tax_before_credits = tax_unit("ny_income_tax_before_credits", period) household_credit = tax_unit("ny_household_credit", period) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py index b71524d37c8..d6e79046274 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py @@ -18,7 +18,7 @@ def formula(tax_unit, period, parameters): agi = add( tax_unit, period, - ["adjusted_gross_income", "spouse_separate_adjusted_gross_income"], + ["ny_recomputed_federal_agi", "spouse_separate_adjusted_gross_income"], ) # Single filers: based only on AGI. single = filing_status == filing_statuses.SINGLE diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_gross_income.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_gross_income.py index 9137289a93e..6bae9a74f09 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_gross_income.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_gross_income.py @@ -28,7 +28,8 @@ def formula(tax_unit, period, parameters): # Form IT-214, Step 3 "Determine household gross income" (line 16 = # sum of lines 9 through 15). Line 9 is federal AGI; the remaining # lines add income that is not already in AGI, "even if not taxable." - agi = tax_unit("adjusted_gross_income", period) # line 9 + # Line 9: recomputed federal AGI (IT-201 line 19a; IT-214 instructions). + agi = tax_unit("ny_recomputed_federal_agi", period) person = tax_unit.members # Line 11: Social Security payments not included on line 9 (i.e. the # portion of benefits not taxed federally). Line 11 instructions: diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_real_property_tax_credit.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_real_property_tax_credit.py index ed140e803bf..43a138072ee 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_real_property_tax_credit.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_real_property_tax_credit.py @@ -42,7 +42,7 @@ def formula(tax_unit, period, parameters): if rptc.uses_household_gross_income: income = tax_unit("ny_household_gross_income", period) else: - income = tax_unit("adjusted_gross_income", period) + income = tax_unit("ny_recomputed_federal_agi", period) # IT-214: "Your federal adjusted gross income cannot be an amount less # than zero. If the amount is less than zero, enter 0." income = max_(income, 0) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py index 9e160b62047..db906ce1a2f 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out.py @@ -79,7 +79,7 @@ def formula(tax_unit, period, parameters): # The defined_for guard already restricts this to filers whose federal # AGI exceeds the applicable amount (worksheet line 7), so the excess is # positive; max_ guards the vectorized boundary. - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) filing_status = tax_unit("filing_status", period) applicable_amount = p.start[filing_status] agi_excess = max_(agi - applicable_amount, 0) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out_applies.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out_applies.py index 414bda3c7e6..237ca558263 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out_applies.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/phase_out/ny_itemized_deductions_phase_out_applies.py @@ -19,7 +19,7 @@ def formula(tax_unit, period, parameters): # the pre-TCJA federal 26 U.S.C. § 68(b) applicable amount, which NY # continues to inflation-adjust. p = parameters(period).gov.states.ny.tax.income.deductions.itemized.phase_out - agi = tax_unit("adjusted_gross_income", period) + agi = tax_unit("ny_recomputed_federal_agi", period) filing_status = tax_unit("filing_status", period) applicable_amount = p.start[filing_status] return agi > applicable_amount diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py index b179c6f0fab..21af7553b5f 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py @@ -24,6 +24,7 @@ def formula(tax_unit, period, parameters): # (not limited to federally declared disasters like federal post-2017) loss = add(tax_unit, period, ["casualty_loss"]) p = parameters(period).gov.irs.deductions.itemized.casualty - positive_agi = tax_unit("positive_agi", period) + # IT-196 line 17: 10% of recomputed federal AGI (IT-201 line 19a). + positive_agi = max_(0, tax_unit("ny_recomputed_federal_agi", period)) # Apply the 10% AGI floor (pre-TCJA rule) return max_(0, loss - positive_agi * p.floor) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_medical_expense_deduction.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_medical_expense_deduction.py index eb74491e9f4..c465812b652 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_medical_expense_deduction.py @@ -18,9 +18,9 @@ class ny_medical_expense_deduction(Variable): medical and dental expenses only above 10% of federal AGI; NY does not follow the federal 7.5% floor in place since 2017 (Public Law 115-97 § 11027 for 2017-2018, later law after that). Form IT-196 lines 1-4 apply - this floor to federal AGI (Form IT-201 or IT-203 line 19; line 19a, - recomputed federal AGI, in 2020-2022, which PolicyEngine does not - distinguish from federal AGI). Through 2017, NY took the federal + this floor to recomputed federal AGI (Form IT-201 or IT-203 line 19a, + ny_recomputed_federal_agi), which differs from federal AGI in 2020 by the + federal charitable deduction for non-itemizers. Through 2017, NY took the federal Schedule A amount, so the floor parameter follows the federal floor then. """ @@ -29,5 +29,7 @@ def formula(tax_unit, period, parameters): # above 10% of AGI, not the federal 7.5% floor in place since 2017 p = parameters(period).gov.states.ny.tax.income.deductions.itemized.medical expenses = tax_unit("itemized_medical_expenses", period) - medical_floor = p.floor * tax_unit("positive_agi", period) + # IT-196 line 2: recomputed federal AGI (IT-201 line 19a). + agi = max_(0, tax_unit("ny_recomputed_federal_agi", period)) + medical_floor = p.floor * agi return max_(0, expenses - medical_floor) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_misc_deduction.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_misc_deduction.py index b365a3da8de..8ac01079e92 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_misc_deduction.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_misc_deduction.py @@ -24,5 +24,7 @@ def formula(tax_unit, period, parameters): # with the 2% AGI floor (not suspended like federal post-2017) p = parameters(period).gov.irs.deductions.itemized.misc expenses = tax_unit("total_misc_deductions", period) - misc_floor = p.floor * tax_unit("positive_agi", period) + # IT-196 line 26: recomputed federal AGI (IT-201 line 19a). + agi = max_(0, tax_unit("ny_recomputed_federal_agi", period)) + misc_floor = p.floor * agi return max_(0, expenses - misc_floor) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index cea0e7bcead..11b3c3b2a87 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -5659,6 +5659,31 @@ candidates: state: unreviewed note: 'The statute-first rule requires revising the deduction-only ACTC bar; verify that interpretation separately from EITC, whose statutory wording differs. Code not yet on main: elects_section_911_exclusion.' +- id: us:charitable-non-itemizer-2020-separate-cap + program: income-tax + years: + from: 2020 + to: 2020 + type: tax + question: Is the 2020 above-the-line charitable deduction capped at $300 or $150 for a married separate filer? + sources: + - kind: statute + citation: Pub. L. 116-136, sec. 2204; 26 U.S.C. 62(a)(22), 62(f) + url: https://www.congress.gov/116/plaws/publ136/PLAW-116publ136.pdf#page=65 + - kind: instructions + citation: 2020 Instructions for Form 1040, line 10b + url: https://www.irs.gov/pub/irs-prior/i1040gi--2020.pdf#page=30 + readings: + statute: The cap is $300 for every eligible individual, with no separate-filer reduction. + other: The line 10b instructions limit married separate filers to $150. + pe_us: + follows: statute + code: + - policyengine_us/parameters/gov/irs/ald/charitable_contributions/cap.yaml + - policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/charitable_contributions/charitable_contributions_ald.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10186 + state: ruled - id: us:claim-of-right-same-class-aggregation program: income-tax years: