diff --git a/changelog.d/fix-tax-unit-itemizes-tied-fallback.fixed.md b/changelog.d/fix-tax-unit-itemizes-tied-fallback.fixed.md new file mode 100644 index 00000000000..3bd393f0003 --- /dev/null +++ b/changelog.d/fix-tax-unit-itemizes-tied-fallback.fixed.md @@ -0,0 +1 @@ +Fix tax_unit_itemizes tied-federal-tax fallback to compare federal standard against federal itemized deductions, restoring TaxAct/TAXSIM behavior and resolving downstream CO/VT/LA state-tax discrepancies. 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 45dbcaea4e5..f9b7e43d800 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 @@ -20,23 +20,23 @@ output: tax_unit_itemizes: True -- name: When tax liability is equal, itemization is based on state deductions +- name: When tax liability is equal, itemization is based on federal deduction comparison period: 2024 input: gov.simulation.branch_to_determine_itemization: true - state_itemized_deductions: 14_000 - state_standard_deduction: 11_000 + itemized_taxable_income_deductions: 14_000 + standard_deduction: 11_000 tax_liability_if_itemizing: 10_000 tax_liability_if_not_itemizing: 10_000 output: tax_unit_itemizes: True -- name: When tax liability is equal, itemization is based on state deductions, unit itemizes +- name: When tax liability is equal and itemized below standard, unit does not itemize period: 2024 input: gov.simulation.branch_to_determine_itemization: true - state_itemized_deductions: 14_000 - state_standard_deduction: 16_000 + itemized_taxable_income_deductions: 14_000 + standard_deduction: 16_000 tax_liability_if_itemizing: 10_000 tax_liability_if_not_itemizing: 10_000 output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/integration.yaml index e9937fda756..0bc76cb6dc3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/integration.yaml @@ -337,3 +337,49 @@ # Child age 14: FATC $2,455 (age 6-16 category) # At $10k income (below thresholds), full credit amounts co_family_affordability_credit: [0, 3_273, 2_455, 2_455] + +# Locks in the fix for #8210 / taxsim #870. Pre-fix the tied-federal-tax +# fallback in tax_unit_itemizes compared CO's federal standard deduction +# against $0 of CO-specific itemized deductions, returning False. The household +# was treated as taking the standard deduction, giving co_taxable_income $17,742 +# and co_income_tax +$780. Post-fix: itemizes, co_income_tax matches TaxAct +# (-$4,397) within ~$65, residual is a separate CO add-back issue. +- name: 2025 CO joint with property tax + mortgage itemizes when federal tax tied (taxsim #870) + absolute_error_margin: 1 + period: 2025 + input: + people: + person1: + age: 40 + employment_income: 0 + real_estate_taxes: 30_000 + deductible_mortgage_interest: 20_000 + is_tax_unit_head: true + person2: + age: 40 + employment_income: 20_000 + taxable_interest_income: 29_241.51 + is_tax_unit_spouse: true + child1: + age: 11 + is_tax_unit_dependent: true + child2: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, child1, child2] + filing_status: JOINT + tax_unit_childcare_expenses: 3_000 + premium_tax_credit: 0 + spm_units: + spm_unit: + members: [person1, person2, child1, child2] + households: + household: + members: [person1, person2, child1, child2] + state_code: CO + output: + tax_unit_itemizes: true + co_taxable_income: 1_474 + co_income_tax: -4_332 diff --git a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/taxable_income/deductions/me_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/taxable_income/deductions/me_deductions.yaml index 120a1891c7e..4e903bd9abd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/taxable_income/deductions/me_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/me/tax/income/taxable_income/deductions/me_deductions.yaml @@ -29,11 +29,13 @@ me_deduction_phaseout_percentage: 0.40 me_standard_deduction: 10_000 me_itemized_deductions_pre_phaseout: 15_000 + # Tied-federal-tax fallback uses the federal standard vs. itemized + # comparison; provide consistent federal-level inputs to itemize. + standard_deduction: 10_000 + itemized_taxable_income_deductions: 15_000 output: tax_liability_if_itemizing: 0 tax_liability_if_not_itemizing: 0 - state_standard_deduction: 10_000 - state_itemized_deductions: 15_000 me_deductions: 9_000 - name: ME single 2025 100K self-employment (TaxAct verified, taxsim issue 790) diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/integration.yaml index 92254d5097e..6c7db0526ed 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/integration.yaml @@ -59,3 +59,48 @@ # Only $5,000 flat exclusion applies since gains are from financial instruments # VT AGI: 140418 - 5000 = 135418 vt_agi: 135_418 + +# Locks in the fix for #8210 / taxsim #869. Pre-fix the tied-federal-tax +# fallback in tax_unit_itemizes returned False; vt_cdcc was $497 (computed +# against standard-deduction federal tax). Post-fix: itemizes, federal tax is +# $0 so capped CDCC and vt_cdcc both round to $0, matching TaxAct's vt_cdcc +# of $0 and vt_income_tax of -$3,045. +- name: 2025 VT joint with property tax + mortgage itemizes when federal tax tied (taxsim #869) + absolute_error_margin: 2 + period: 2025 + input: + people: + person1: + age: 40 + employment_income: 15_096.83 + real_estate_taxes: 30_000 + deductible_mortgage_interest: 20_000 + is_tax_unit_head: true + person2: + age: 40 + employment_income: 20_000 + social_security_retirement: 8_978.28 + is_tax_unit_spouse: true + child1: + age: 11 + is_tax_unit_dependent: true + child2: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, child1, child2] + filing_status: JOINT + tax_unit_childcare_expenses: 3_000 + premium_tax_credit: 0 + spm_units: + spm_unit: + members: [person1, person2, child1, child2] + households: + household: + members: [person1, person2, child1, child2] + state_code: VT + output: + tax_unit_itemizes: true + vt_cdcc: 0 + vt_income_tax: -3_045 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 1f9f914019c..2c36139155e 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,27 +10,29 @@ class tax_unit_itemizes(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): + standard_deduction = tax_unit("standard_deduction", period) + 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 tax_liability_if_itemizing = tax_unit("tax_liability_if_itemizing", period) tax_liability_if_not_itemizing = tax_unit( "tax_liability_if_not_itemizing", period ) - state_standard_deduction = tax_unit("state_standard_deduction", period) - state_itemized_deductions = tax_unit("state_itemized_deductions", period) # Use a small tolerance for floating-point comparison due to floating point imprecision TOLERANCE = 0.01 federal_tax_equal = ( np.abs(tax_liability_if_itemizing - tax_liability_if_not_itemizing) <= TOLERANCE ) + # When federal tax liabilities tie (typical at low/moderate income where + # refundable credits absorb the tax), fall back to the federal-level + # deduction comparison. This matches the non-branching path below and + # the IRS / TaxAct / TAXSIM-comparison default of itemizing whenever + # itemized deductions exceed the standard deduction. return where( federal_tax_equal, - state_standard_deduction < state_itemized_deductions, + itemized_deductions > standard_deduction, tax_liability_if_itemizing < tax_liability_if_not_itemizing, ) - else: - standard_deduction = tax_unit("standard_deduction", period) - itemized_deductions = tax_unit("itemized_taxable_income_deductions", period) - # determine federal itemization behavior by comparing deductions - return itemized_deductions > standard_deduction + # determine federal itemization behavior by comparing deductions + return itemized_deductions > standard_deduction