diff --git a/changelog.d/amti-section-68-and-tax-refunds.fixed.md b/changelog.d/amti-section-68-and-tax-refunds.fixed.md new file mode 100644 index 00000000000..d0828c01912 --- /dev/null +++ b/changelog.d/amti-section-68-and-tax-refunds.fixed.md @@ -0,0 +1 @@ +Leave state and local tax refunds and the section 68 itemized deduction reduction out of federal alternative minimum taxable income, including the married filing separately increase, and in Colorado alternative minimum taxable income leave out the state income tax addback and the refund subtraction and let a federal exemption larger than AMTI enter line 1 as a negative amount. diff --git a/policyengine_us/tests/core/test_amt_income_refund_and_section_68_invariants.py b/policyengine_us/tests/core/test_amt_income_refund_and_section_68_invariants.py new file mode 100644 index 00000000000..a50310fd1f8 --- /dev/null +++ b/policyengine_us/tests/core/test_amt_income_refund_and_section_68_invariants.py @@ -0,0 +1,118 @@ +"""Alternative minimum taxable income leaves out tax refunds and section 68. + +26 U.S.C. 56(b)(1)(C): no recovery of a state or local tax is in alternative +minimum taxable income (Form 6251 line 2b). 56(b)(1)(E): section 68 does not +apply. Each Hypothesis example evaluates a batch of tax units, and their twins +without the refund, in one vectorized simulation in a state with no income +tax. With the itemization choice fixed, the households have no deduction that +depends on AGI once the AMT add-backs are made: no medical expenses and no +charitable gifts. From 2026 the 0.5% floor of 26 U.S.C. 170(b)(1)(I) ties the +charitable deduction to AGI, so a refund rightly raises AMTI by 0.5% of it. + +Two properties, for 2017 (the old section 68), 2025 (no section 68) and 2026 +(the P.L. 119-21 section 68), single, joint and married filing separately, +itemizing or not: + +1. Refund invariance: adding a filer's state tax refund leaves AMTI unchanged. + The refund enters AGI and comes back out, and any change it causes in the + section 68 reduction or the capped SALT deduction cancels, because AMTI + reverses the first and adds back the second. For a separate filer, the + 55(d)(2) increase is figured on AMTI without the refund, so it holds too. +2. Statutory identity: AMTI before that separate filer increase is AGI, less + the refund, less the itemized deductions the AMT allows. Those are all + itemized deductions before section 68 except taxes and miscellaneous + deductions (56(b)(1)(A)), and none for a filer who takes the standard + deduction (56(b)(1)(D)). This checks the formula's route through taxable + income against the statute's definition. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +# The model computes in float32: allow about two units in the last place. +TOLERANCE = dict(atol=0.01, rtol=2.5e-7) + + +@st.composite +def tax_unit(draw): + return { + "status": draw(st.sampled_from(["SINGLE", "JOINT", "SEPARATE"])), + "itemizes": draw(st.booleans()), + "employment_income": draw(st.integers(20_000, 3_000_000)), + "salt_refund_income": draw(st.integers(1, 20_000)), + "real_estate_taxes": draw(st.integers(0, 40_000)), + "home_mortgage_interest": draw(st.integers(0, 40_000)), + } + + +def _simulate(units, year): + situation = {"people": {}, "tax_units": {}, "households": {}} + for twin in (0, 1): + for i, unit in enumerate(units): + head = f"head_{twin}_{i}" + members = [head] + situation["people"][head] = { + "age": {year: 45}, + "employment_income": {year: unit["employment_income"]}, + "salt_refund_income": { + year: unit["salt_refund_income"] if twin == 0 else 0 + }, + "real_estate_taxes": {year: unit["real_estate_taxes"]}, + "home_mortgage_interest": {year: unit["home_mortgage_interest"]}, + } + if unit["status"] == "JOINT": + spouse = f"spouse_{twin}_{i}" + situation["people"][spouse] = {"age": {year: 45}} + members.append(spouse) + situation["tax_units"][f"unit_{twin}_{i}"] = { + "members": members, + "filing_status": {year: unit["status"]}, + "tax_unit_itemizes": {year: unit["itemizes"]}, + } + situation["households"][f"household_{twin}_{i}"] = { + "members": members, + "state_code": {year: "TX"}, + } + return Simulation(situation=situation) + + +@pytest.mark.parametrize("year", [2017, 2025, 2026]) +@settings( + max_examples=4, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(units=st.lists(tax_unit(), min_size=8, max_size=8)) +def test_amt_income_leaves_out_refunds_and_section_68(year, units): + simulation = _simulate(units, year) + + def calc(variable): + return np.asarray(simulation.calculate(variable, year), dtype=float) + + n = len(units) + amti = calc("amt_income") + + # 1. Refund invariance: each unit matches its twin without the refund. + np.testing.assert_allclose(amti[:n], amti[n:], **TOLERANCE) + + # 2. Statutory identity, before the separate filer increase: AGI less the + # refund less the AMT's itemized deductions (before section 68, without + # taxes and miscellaneous ones). + refunds = np.array([unit["salt_refund_income"] for unit in units] + [0] * n) + allowed_itemized = np.where( + calc("tax_unit_itemizes").astype(bool), + calc("total_itemized_taxable_income_deductions") + - calc("salt_deduction") + - calc("misc_deduction"), + 0, + ) + np.testing.assert_allclose( + calc("amt_income_before_separate_addition"), + calc("adjusted_gross_income") - refunds - allowed_itemized, + **TOLERANCE, + ) diff --git a/policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.yaml b/policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.yaml index 6543111b8d2..61bb8b7b561 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.yaml @@ -161,3 +161,194 @@ # The senior deduction is not in line 14 and is not added back, so # line 4 is AGI for a filer who takes the standard deduction. amt_income: 100_000 + +# 26 U.S.C. 56(b)(1)(C); 2025 Form 6251 line 2b: refunds of state and local +# taxes included in income are subtracted. +- name: Single itemizer subtracts a state income tax refund, 2025 + absolute_error_margin: 0.01 + period: 2025 + input: + tax_unit_itemizes: true + taxable_income: 100_000 + salt_deduction: 8_000 + salt_refund_income: 2_000 + filing_status: SINGLE + output: + # 100,000 + 8,000 - 2,000 + amt_income: 106_000 + +# 26 U.S.C. 56(b)(1)(E): section 68 does not apply, so the itemized deduction +# reduction in taxable income comes back out. +- name: Single itemizer reverses the section 68 reduction, 2026 + absolute_error_margin: 0.01 + period: 2026 + input: + tax_unit_itemizes: true + taxable_income: 1_000_000 + salt_deduction: 10_000 + itemized_taxable_income_deductions_reduction: 5_000 + filing_status: SINGLE + output: + # 1,000,000 + 10,000 - 5,000 + amt_income: 1_005_000 + +- name: Non-itemizer has no section 68 reduction to reverse, 2026 + absolute_error_margin: 0.01 + period: 2026 + input: + tax_unit_itemizes: false + taxable_income: 50_000 + standard_deduction: 1_000 + itemized_taxable_income_deductions_reduction: 5_000 + filing_status: SINGLE + output: + amt_income: 51_000 + +# 2017 Form 6251 line 6 enters line 9 of the Schedule A Itemized Deductions +# Worksheet (the section 68 reduction) as a negative amount. 26 U.S.C. +# 56(b)(1)(F) then; (E) since P.L. 116-94. A 4,155 reduction is 3% of AGI over +# 261,500, so AGI is at least 400,000 and the personal exemption has phased +# out (it does above 384,000 for a single filer). +- name: Single itemizer reverses the 2017 overall limitation on itemized deductions + absolute_error_margin: 0.01 + period: 2017 + input: + tax_unit_itemizes: true + taxable_income: 300_000 + exemptions: 0 + salt_deduction: 20_000 + misc_deduction: 0 + itemized_taxable_income_deductions_reduction: 4_155 + filing_status: SINGLE + output: + # 300,000 + 20,000 - 4,155 + amt_income: 315_845 + +- name: A dependent's state tax refund is not subtracted from the filer's AMTI, 2025 + absolute_error_margin: 0.01 + period: 2025 + input: + people: + person1: + age: 45 + salt_refund_income: 1_000 + person2: + age: 17 + is_tax_unit_dependent: true + salt_refund_income: 500 + tax_units: + tax_unit: + members: [person1, person2] + tax_unit_itemizes: true + taxable_income: 100_000 + salt_deduction: 8_000 + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [person1, person2] + output: + # 100,000 + 8,000 - 1,000 + amt_income: 107_000 + +# End to end from wages, with the refund in AGI. 26 U.S.C. 56(b)(1)(C); 2025 +# Form 6251 line 2b. Sales taxes are set to zero so the SALT deduction is the +# real estate taxes, under the 2025 cap. +- name: Single itemizer's refund enters AGI and comes back out of AMTI, 2025 + absolute_error_margin: 0.01 + period: 2025 + input: + people: + person1: + age: 45 + employment_income: 100_000 + salt_refund_income: 2_000 + real_estate_taxes: 8_000 + home_mortgage_interest: 20_000 + tax_units: + tax_unit: + members: [person1] + tax_unit_itemizes: true + state_sales_tax: 0 + local_sales_tax: 0 + households: + household: + members: [person1] + state_code: TX + output: + # AGI 102,000; itemized deductions 8,000 + 20,000 = 28,000. + taxable_income: 74_000 + # Line 1b 74,000 + line 2a 8,000 - line 2b 2,000 = 80,000, which is AGI + # less the refund less the AMT's itemized deductions (102,000 - 2,000 - + # 20,000). + amt_income: 80_000 + +# End to end from wages, with the model's own 2026 section 68 reduction. +# 26 U.S.C. 56(b)(1)(E): section 68 does not apply. +- name: Single itemizer's section 68 reduction stays out of AMTI, 2026 + absolute_error_margin: 0.01 + period: 2026 + input: + people: + person1: + age: 45 + employment_income: 1_000_000 + real_estate_taxes: 10_000 + home_mortgage_interest: 30_000 + tax_units: + tax_unit: + members: [person1] + tax_unit_itemizes: true + state_sales_tax: 0 + local_sales_tax: 0 + households: + household: + members: [person1] + state_code: TX + output: + # 26 U.S.C. 164(b)(7): the 2026 SALT cap of 40,400 phases down by 30% of + # MAGI over 505,000 to its 10,000 floor at this income. + salt_deduction: 10_000 + total_itemized_taxable_income_deductions: 40_000 + # Section 68 as amended by P.L. 119-21: 2/37 of the lesser of the itemized + # deductions (40,000) and taxable income over the 37% bracket threshold + # (1,000,000 - 640,600 = 359,400): 80,000 / 37. + itemized_taxable_income_deductions_reduction: 2_162.16 + # 1,000,000 - (40,000 - 2,162.16) + taxable_income: 962_162.16 + # 962,162.16 + 10,000 SALT - 2,162.16 = 970,000, which is AGI less the + # AMT's itemized deductions (1,000,000 - 30,000). + amt_income: 970_000 + +# 26 U.S.C. 55(d)(2): a married individual filing separately adds the lesser +# of 25% of AMTI "determined without regard to this sentence" over the amount +# at which the exemption is zero, and the exemption. 2025 Form 6251 +# instructions, line 4: 25% of line 4 over 900,350. Line 4 here is already net +# of the refund (line 2b). +- name: Separate filer's additional amount is figured after the refund, 2025 + absolute_error_margin: 0.01 + period: 2025 + input: + people: + person1: + age: 45 + employment_income: 1_000_000 + salt_refund_income: 20_000 + real_estate_taxes: 10_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SEPARATE + tax_unit_itemizes: true + state_sales_tax: 0 + local_sales_tax: 0 + households: + household: + members: [person1] + state_code: TX + output: + # AGI 1,020,000, less the 20,000 refund; the only itemized deduction is + # the SALT deduction, which line 2a adds back. + amt_income_before_separate_addition: 1_000_000 + # min(68,500 exemption, 25% x (1,000,000 - 900,350)) + amt_separate_addition: 24_912.5 + amt_income: 1_024_912.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.yaml index 35a377bc3f2..4e284da7d07 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.yaml @@ -13,6 +13,10 @@ - name: CO AMTI with no federal AMT income period: 2024 input: + # Form 6251 line 4 equals the line 5 exemption, so line 6 and DR 0104AMT + # line 1 are zero. A line 4 below line 5 would enter as a negative amount. + amt_income: 85_700 + amt_exemption: 85_700 amt_income_less_exemptions: 0 co_additions: 10_000 co_subtractions: 5_000 @@ -31,3 +35,62 @@ output: # max(0, 0 + 0 - 50,000) = 0 co_alternative_minimum_taxable_income: 0 + +# DR 0104AMT (2025): line 2 sums DR 0104 lines 3 through 7 and 9, leaving out +# the state income tax addback on line 2; line 4 sums the DR 0104AD +# subtractions excluding the state income tax refund on line 1. +- name: CO AMTI leaves out the state income tax addback and the refund subtraction + period: 2025 + input: + amt_income_less_exemptions: 200_000 + co_additions: 10_000 + co_state_addback: 6_000 + co_subtractions: 5_000 + salt_refund_income: 1_500 + state_code: CO + output: + # 200,000 + (10,000 - 6,000) - (5,000 - 1,500) + co_alternative_minimum_taxable_income: 200_500 + +# DR 0104AMT line 4 takes the DR 0104AD subtractions without the refund; it +# cannot go below zero, so the refund never adds to Colorado AMTI. +- name: CO AMTI line 4 without the refund does not go below zero + period: 2025 + input: + amt_income_less_exemptions: 200_000 + co_additions: 0 + co_state_addback: 0 + co_subtractions: 500 + salt_refund_income: 1_500 + state_code: CO + output: + # 200,000 + 0 - max(0, 500 - 1,500) + co_alternative_minimum_taxable_income: 200_000 + +# DR 0104AMT line 1: "If line 5 on form 6251 is larger than line 4 on form +# 6251, subtract line 5 from line 4 and enter the resulting negative amount +# here." C.R.S. 39-22-105(2)(a) has no floor before the modifications. +- name: A negative DR 0104AMT line 1 offsets Colorado additions + period: 2025 + input: + people: + person1: + age: 40 + salt_refund_income: 15_000 + tax_units: + tax_unit: + members: [person1] + # Non-Colorado municipal bond interest (DR 0104 line 9). + co_other_additions: 15_000 + households: + household: + members: [person1] + state_code: CO + output: + # Federal: AGI 15,000; line 1b 15,000 - 15,750 = -750; line 2a 15,750; + # line 2b -15,000; line 4 = 0. Line 5 exemption 88,100. + amt_income: 0 + # Line 1: 0 - 88,100. Line 2: 15,000. Line 4: 15,000 - 15,000 refund. + # Line 5: -88,100 + 15,000 - 0 < 0. + co_alternative_minimum_taxable_income: 0 + co_amt: 0 diff --git a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.py b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.py index 5c488f3054b..267c7b0f3c4 100644 --- a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.py +++ b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income.py @@ -7,41 +7,16 @@ class amt_income(Variable): definition_period = YEAR label = "AMT taxable income" unit = USD + documentation = ( + "Alternative minimum taxable income, Form 6251 line 4: taxable income " + "with the section 56 adjustments, plus the increase for a married " + "individual filing a separate return." + ) reference = ( - "https://www.law.cornell.edu/uscode/text/26/56", - # Form 6251 line 1 keeps a negative amount when taxable income is zero - "https://www.irs.gov/pub/irs-prior/f6251--2024.pdf#page=1", + "https://www.law.cornell.edu/uscode/text/26/55#b_2", + "https://www.law.cornell.edu/uscode/text/26/55#d_2", "https://www.irs.gov/pub/irs-prior/f6251--2025.pdf#page=1", - "https://www.irs.gov/pub/irs-prior/i6251--2025.pdf#page=2", + # Line 4, married filing separately + "https://www.irs.gov/pub/irs-prior/i6251--2025.pdf#page=9", ) - - def formula(tax_unit, period, parameters): - p = parameters(period).gov.irs.income.amt - # Form 6251 line 1: Form 1040 line 15 if more than zero. If line 15 - # is zero, line 11 minus line 14, entered as a negative amount if - # less than zero. Before 2018 the personal exemptions were not - # subtracted, which taxable income plus exemptions also gives. - taxable_income = tax_unit("taxable_income", period) - exemptions = tax_unit("exemptions", period) - agi = tax_unit("adjusted_gross_income", period) - deductions = tax_unit("taxable_income_deductions", period) - # From 2018 both branches equal agi - deductions; the taxable income - # branch only differs before 2018, when exemptions were subtracted. - line_1 = where( - taxable_income > 0, - taxable_income + exemptions, - agi - deductions, - ) - # 2025 line 1a: deductions in line 14 that are not taken for the AMT. - deductions_add_back = ( - add(tax_unit, period, p.deductions_add_back) if p.deductions_add_back else 0 - ) - return ( - line_1 - + deductions_add_back - + add( - tax_unit, - period, - ["amt_excluded_deductions", "amt_separate_addition"], - ) - ) + adds = ["amt_income_before_separate_addition", "amt_separate_addition"] diff --git a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income_before_separate_addition.py b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income_before_separate_addition.py new file mode 100644 index 00000000000..f3ca98bff46 --- /dev/null +++ b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_income_before_separate_addition.py @@ -0,0 +1,73 @@ +from policyengine_us.model_api import * + + +class amt_income_before_separate_addition(Variable): + value_type = float + entity = TaxUnit + definition_period = YEAR + label = "AMT taxable income before the married filing separately addition" + unit = USD + documentation = ( + "Alternative minimum taxable income determined without the increase " + "26 U.S.C. 55(d)(2) makes for a married individual filing a separate " + "return, which is figured on this amount (Form 6251 line 4 before the " + "additional amount)." + ) + reference = ( + "https://www.law.cornell.edu/uscode/text/26/55#d_2", + "https://www.law.cornell.edu/uscode/text/26/56", + # Form 6251 line 1 keeps a negative amount when taxable income is zero + "https://www.irs.gov/pub/irs-prior/f6251--2024.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/f6251--2025.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/i6251--2025.pdf#page=2", + # 2017 Form 6251 lines 6 and 7, and their instructions. + "https://www.irs.gov/pub/irs-prior/f6251--2017.pdf#page=1", + # PDF pages 2-3 + "https://www.irs.gov/pub/irs-prior/i6251--2017.pdf#page=2", + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.income.amt + # Form 6251 line 1: Form 1040 line 15 if more than zero. If line 15 + # is zero, line 11 minus line 14, entered as a negative amount if + # less than zero. Before 2018 the personal exemptions were not + # subtracted, which taxable income plus exemptions also gives. + taxable_income = tax_unit("taxable_income", period) + exemptions = tax_unit("exemptions", period) + agi = tax_unit("adjusted_gross_income", period) + deductions = tax_unit("taxable_income_deductions", period) + # From 2018 both branches equal agi - deductions; the taxable income + # branch only differs before 2018, when exemptions were subtracted. + line_1 = where( + taxable_income > 0, + taxable_income + exemptions, + agi - deductions, + ) + # 2025 line 1a: deductions in line 14 that are not taken for the AMT. + deductions_add_back = ( + add(tax_unit, period, p.deductions_add_back) if p.deductions_add_back else 0 + ) + # 2025 line 2b (2017 line 7): refunds of state and local taxes in + # income are not AMTI (26 U.S.C. 56(b)(1)(C), formerly (D)). Subtract + # what irs_gross_income put in AGI: each head's or spouse's refund, + # floored at zero. Dependents' refunds are on their own returns. + person = tax_unit.members + filer = ~person("is_tax_unit_dependent", period) + tax_refunds = tax_unit.sum( + filer * max_(0, person("salt_refund_income", period)) + ) + # Section 68 does not apply to AMTI (26 U.S.C. 56(b)(1)(E), formerly + # (F)), so the itemized deduction reduction in line 1 comes back out + # (2017 line 6). + section_68_reduction = where( + tax_unit("tax_unit_itemizes", period), + tax_unit("itemized_taxable_income_deductions_reduction", period), + 0, + ) + return ( + line_1 + + deductions_add_back + + tax_unit("amt_excluded_deductions", period) + - tax_refunds + - section_68_reduction + ) diff --git a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_separate_addition.py b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_separate_addition.py index 6349705b8bf..d6c147fca1c 100644 --- a/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_separate_addition.py +++ b/policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/income/amt_separate_addition.py @@ -7,12 +7,18 @@ class amt_separate_addition(Variable): definition_period = YEAR label = "AMT taxable income separate addition" unit = USD - reference = "https://www.law.cornell.edu/uscode/text/26/55#b_2" + reference = ( + "https://www.law.cornell.edu/uscode/text/26/55#d_2", + # Line 4, married filing separately + "https://www.irs.gov/pub/irs-prior/i6251--2025.pdf#page=9", + ) def formula(tax_unit, period, parameters): - taxable_income = tax_unit("taxable_income", period) - excluded_deductions = tax_unit("amt_excluded_deductions", period) - amt_inc = taxable_income + excluded_deductions + # 26 U.S.C. 55(d)(2) figures the increase on alternative minimum + # taxable income "determined without regard to this sentence", so + # with every other AMT adjustment, such as the refund and section 68 + # lines. + amt_inc = tax_unit("amt_income_before_separate_addition", period) p = parameters(period).gov.irs.income.amt.exemption filing_status = tax_unit("filing_status", period) separate = filing_status == filing_status.possible_values.SEPARATE diff --git a/policyengine_us/variables/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.py b/policyengine_us/variables/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.py index 99a985d0093..ff20642f7be 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.py +++ b/policyengine_us/variables/gov/states/co/tax/income/amt/co_alternative_minimum_taxable_income.py @@ -11,20 +11,44 @@ class co_alternative_minimum_taxable_income(Variable): reference = [ "https://tax.colorado.gov/DR0104AMT", "https://law.justia.com/codes/colorado/2022/title-39/article-22/part-1/section-39-22-105/", + # C.R.S. 39-22-104(3)(d)(I), 39-22-104(4)(e) and 39-22-105(2)(a), + # Colorado Revised Statutes 2025. PDF pages 380, 386, 401 + "https://olls.info/crs/crs2025-title-39.pdf#page=380", + # 2025 DR 0104AMT instructions and computation schedule + # PDF pages 1, 3 + "https://tax.colorado.gov/sites/tax/files/documents/DR0104AMT_2025.pdf#page=1", + # 2025 DR 0104 lines 2 through 9 + "https://tax.colorado.gov/sites/tax/files/documents/DR0104_2025.pdf#page=3", ] def formula(tax_unit, period, parameters): - # DR 0104AMT calculation: - # Line 1: Federal Form 6251 line 6 (federal AMTI less exemption) - federal_amt_income_less_exemptions = tax_unit( - "amt_income_less_exemptions", period + # C.R.S. 39-22-105(2)(a) applies the 39-22-104 modifications to + # federal alternative minimum taxable income less the exemption. + # Measured against that base, the state income tax addback of + # 39-22-104(3)(d) ("an amount equal to the deduction claimed") is + # zero, since 26 U.S.C. 56(b)(1)(A)(ii) allows no such deduction, and + # the refund subtraction of 39-22-104(4)(e), whose stated purpose is + # "to avoid re-taxing" the refund, has nothing to offset, since 26 + # U.S.C. 56(b)(1)(C) already leaves the refund out. DR 0104AMT lines + # 2 and 4 leave out both. + # Line 1: Form 6251 line 6, or, when the exemption (line 5) exceeds + # alternative minimum taxable income (line 4) and line 6 is zero, + # line 4 less line 5 as a negative amount. + line_6 = tax_unit("amt_income_less_exemptions", period) + shortfall = min_( + 0, tax_unit("amt_income", period) - tax_unit("amt_exemption", period) ) - # Line 2: Colorado additions (from DR 0104, lines 3-8) - co_additions = tax_unit("co_additions", period) - # Line 4: Colorado subtractions (excluding state income tax refund) - co_subtractions = tax_unit("co_subtractions", period) - # Line 5: Colorado AMTI - return max_( + line_1 = where(line_6 > 0, line_6, shortfall) + # Line 2: Colorado additions from DR 0104, lines 3 through 7 and 9, + # leaving out the state income tax addback on DR 0104 line 2. + line_2 = tax_unit("co_additions", period) - tax_unit("co_state_addback", period) + # Line 4: the DR 0104AD subtractions excluding the state income tax + # refund (DR 0104AD line 1). co_subtractions is floored at zero, and + # so is this line, so the refund can never act as an addition. + line_4 = max_( 0, - federal_amt_income_less_exemptions + co_additions - co_subtractions, + tax_unit("co_subtractions", period) + - add(tax_unit, period, ["salt_refund_income"]), ) + # Line 5: Colorado alternative minimum taxable income. + return max_(0, line_1 + line_2 - line_4)