From 6d3cb8fe2ee2666254ba8923a607754decef4809 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 7 May 2026 10:52:46 -0400 Subject: [PATCH 1/9] Fix federal-state tax conformity rules --- .../states/al/tax/income/agi/deductions.yaml | 6 ++ .../hi/tax/income/additions/additions.yaml | 9 +++ .../tax/income/subtractions/subtractions.yaml | 2 +- .../credits/earned_income/decoupled.yaml | 2 +- .../tax/income/adjustments/adjustments.yaml | 4 ++ .../oh/tax/income/deductions/deductions.yaml | 2 +- .../gov/states/al/tax/income/al_agi.yaml | 10 +++ .../co_federal_deduction_addback.yaml | 28 ++++++-- .../co/tax/income/credits/eitc/co_eitc.yaml | 51 ++++++++++++++ .../gov/states/dc/tax/income/dc_eitc.yaml | 37 +++++++--- .../income/dc_eitc_with_qualifying_child.yaml | 47 ++++++++++++- .../dc_eitc_without_qualifying_children.yaml | 25 +++++++ .../states/ga/tax/income/ga_deductions.yaml | 19 +++-- .../hi_student_loan_interest_adjustment.yaml | 17 +++++ .../id/tax/income/id_itemized_deductions.yaml | 12 ++++ .../states/il/tax/income/credits/il_ctc.yaml | 31 ++++++-- .../states/il/tax/income/credits/il_eitc.yaml | 64 +++++++++++++++-- .../ms/tax/income/ms_agi_adjustments.yaml | 10 +++ .../income/oh_educator_expense_deduction.yaml | 23 ++++++ .../states/pa/tax/income/credits/pa_cdcc.yaml | 15 +++- .../tax/income/state_itemized_deductions.yaml | 19 ++++- .../tax/income/state_standard_deduction.yaml | 13 +++- .../states/ut/tax/income/credits/ut_eitc.yaml | 11 +++ ...ederal_deductions_for_taxpayer_credit.yaml | 16 +++++ .../wa_working_families_tax_credit.yaml | 30 ++++++++ .../demographic/tax_unit/filing_status.yaml | 23 ++++++ .../co_federal_deduction_addback.py | 6 +- .../co/tax/income/credits/eitc/co_eitc.py | 53 +++++++++++++- .../dc/tax/income/credits/eitc/dc_eitc.py | 6 +- .../eitc/dc_eitc_with_qualifying_child.py | 20 +++++- .../eitc/dc_eitc_without_qualifying_child.py | 26 ++++++- .../ga/tax/income/deductions/ga_deductions.py | 7 +- .../ga_itemized_deductions_adjustment.py | 11 +++ .../gov/states/hi/tax/income/hi_additions.py | 2 + .../hi_student_loan_interest_adjustment.py | 37 ++++++++++ .../deductions/id_itemized_deductions.py | 3 +- .../states/il/tax/income/credits/il_ctc.py | 5 +- .../states/il/tax/income/credits/il_eitc.py | 24 ++++++- .../oh_educator_expense_deduction_person.py | 11 +++ .../states/pa/tax/income/credits/pa_cdcc.py | 5 +- .../states/tax/income/credits/eitc_helpers.py | 70 +++++++++++++++++++ .../tax/income/state_itemized_deductions.py | 9 +-- .../tax/income/state_standard_deduction.py | 2 - ..._federal_deductions_for_taxpayer_credit.py | 27 ++----- .../tax/income/credits/ut_eitc_potential.py | 3 +- .../credits/wa_working_families_tax_credit.py | 59 +++++++++++++++- ...ilies_tax_credit_age_expansion_eligible.py | 38 +++++----- .../tax_unit/head_of_household_eligible.py | 13 ++-- 48 files changed, 841 insertions(+), 122 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/ms_agi_adjustments.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/oh_educator_expense_deduction.yaml create mode 100644 policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py create mode 100644 policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py create mode 100644 policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py create mode 100644 policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py diff --git a/policyengine_us/parameters/gov/states/al/tax/income/agi/deductions.yaml b/policyengine_us/parameters/gov/states/al/tax/income/agi/deductions.yaml index efda2cf0743..90fd39e10a5 100644 --- a/policyengine_us/parameters/gov/states/al/tax/income/agi/deductions.yaml +++ b/policyengine_us/parameters/gov/states/al/tax/income/agi/deductions.yaml @@ -4,13 +4,19 @@ values: # IRA distributions prior to 1982 are not deductible. - traditional_ira_contributions # Line 1 - alimony_expense # Line 4 + - early_withdrawal_penalty # Line 5 - self_employed_health_insurance_ald # Line 7 + - health_savings_account_ald # Line 8 + - self_employed_pension_contribution_ald # Line 9 - us_govt_interest 2023-01-01: # IRA distributions prior to 1982 are not deductible. - traditional_ira_contributions # Line 1 - alimony_expense # Line 4 + - early_withdrawal_penalty # Line 5 - self_employed_health_insurance_ald # Line 7 + - health_savings_account_ald # Line 8 + - self_employed_pension_contribution_ald # Line 9 - us_govt_interest - al_retirement_exemption #Schedule RS Line 10 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml new file mode 100644 index 00000000000..ad98c27767a --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml @@ -0,0 +1,9 @@ +description: Hawaii additions to federal adjusted gross income. +values: + 2021-01-01: + - hi_student_loan_interest_addition + +metadata: + unit: list + period: year + label: Hawaii additions to federal adjusted gross income diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml index f8444c3f216..9c24560e8ab 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml @@ -5,7 +5,7 @@ values: - taxable_social_security # Line 14 - hi_military_pay_exclusion # Line 15 - us_govt_interest # Line 18 - - student_loan_interest_ald # Line 18 + - hi_student_loan_interest_subtraction # Line 18 metadata: diff --git a/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/decoupled.yaml b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/decoupled.yaml index 0318dc8cb5d..68088df3611 100644 --- a/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/decoupled.yaml +++ b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/decoupled.yaml @@ -14,5 +14,5 @@ metadata: values: 2010-01-01: false 2011-01-01: true - 2023-01-01: false + 2023-01-01: true diff --git a/policyengine_us/parameters/gov/states/ms/tax/income/adjustments/adjustments.yaml b/policyengine_us/parameters/gov/states/ms/tax/income/adjustments/adjustments.yaml index 98d4dd7708a..4cb971917de 100644 --- a/policyengine_us/parameters/gov/states/ms/tax/income/adjustments/adjustments.yaml +++ b/policyengine_us/parameters/gov/states/ms/tax/income/adjustments/adjustments.yaml @@ -5,6 +5,10 @@ values: - ms_self_employment_adjustment # Line 61 - ms_national_guard_or_reserve_pay_adjustment # Line 55 - alimony_expense # Line 53 + - self_employed_pension_contribution_ald # Line 51 + - early_withdrawal_penalty # Line 52 + - self_employed_health_insurance_ald # Line 56 + - health_savings_account_ald # Line 57 - ms_retirement_income_exemption - us_govt_interest_person - ms_529_deduction diff --git a/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml b/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml index 5f91de234dc..67bc1c98eba 100644 --- a/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml +++ b/policyengine_us/parameters/gov/states/oh/tax/income/deductions/deductions.yaml @@ -6,7 +6,7 @@ values: - oh_uniformed_services_retirement_income_deduction - oh_529_plan_deduction_person - pell_grant - - educator_expense + - oh_educator_expense_deduction_person - disability_benefits - oh_unreimbursed_medical_care_expense_deduction_person - us_govt_interest_person diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml new file mode 100644 index 00000000000..ace5bd1f75a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/al_agi.yaml @@ -0,0 +1,10 @@ +- name: Alabama AGI includes modeled federal deductions the state still allows + period: 2025 + input: + state_code: AL + irs_employment_income: 1_000 + early_withdrawal_penalty: 50 + health_savings_account_ald: 150 + self_employed_pension_contribution_ald: 100 + output: + al_agi: 700 diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/additions/federal_deduction/co_federal_deduction_addback.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/additions/federal_deduction/co_federal_deduction_addback.yaml index 27001fe8fd6..59e452d3cd5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/additions/federal_deduction/co_federal_deduction_addback.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/additions/federal_deduction/co_federal_deduction_addback.yaml @@ -264,7 +264,8 @@ state_code: CO co_federal_deduction_addback_required: true filing_status: SINGLE - taxable_income_deductions: 22_000 + tax_unit_itemizes: false + standard_deduction: 22_000 output: co_federal_deduction_addback: 10_000 @@ -274,8 +275,8 @@ state_code: CO co_federal_deduction_addback_required: true filing_status: JOINT - taxable_income_deductions: 26_000 - standard_deduction: 0 + tax_unit_itemizes: false + standard_deduction: 26_000 output: co_federal_deduction_addback: 10_000 @@ -285,7 +286,8 @@ state_code: CO co_federal_deduction_addback_required: true filing_status: SEPARATE - taxable_income_deductions: 22_000 + tax_unit_itemizes: false + standard_deduction: 22_000 output: co_federal_deduction_addback: 10_000 @@ -295,7 +297,8 @@ state_code: CO co_federal_deduction_addback_required: true filing_status: SURVIVING_SPOUSE - taxable_income_deductions: 22_000 + tax_unit_itemizes: false + standard_deduction: 22_000 output: co_federal_deduction_addback: 10_000 @@ -305,6 +308,19 @@ state_code: CO co_federal_deduction_addback_required: true filing_status: HEAD_OF_HOUSEHOLD - taxable_income_deductions: 32_000 + tax_unit_itemizes: false + standard_deduction: 32_000 output: co_federal_deduction_addback: 20_000 + +- name: In 2023, Colorado addback ignores unrelated taxable-income deductions like QBI. + period: 2023 + input: + state_code: CO + co_federal_deduction_addback_required: true + filing_status: SINGLE + tax_unit_itemizes: false + standard_deduction: 13_850 + taxable_income_deductions: 20_000 + output: + co_federal_deduction_addback: 1_850 diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml index 14476fc8548..af3285014a3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml @@ -74,3 +74,54 @@ # Federal EITC for 1 child at $20k income (head of household) is approximately $4,328 # CO EITC = 50% * federal EITC = $2,164 co_eitc: 2_164 + +- name: Colorado EITC includes ITIN qualifying children + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + employment_income: 10_000 + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: CO + output: + co_eitc: 1_700 + +- name: Colorado EITC includes certain childless filers under 25 + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 19 + employment_income: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + co_eitc: 324.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml index 97b5d12b2ef..a31701f41a6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc.yaml @@ -1,19 +1,40 @@ - name: DC EITC without qualifying children period: 2023 input: - eitc_child_count: 0 - dc_eitc_with_qualifying_child: 1 - dc_eitc_without_qualifying_child: 2 - state_code: DC + people: + head: + age: 30 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + dc_eitc_with_qualifying_child: 1 + dc_eitc_without_qualifying_child: 2 + households: + household: + members: [head] + state_code: DC output: dc_eitc: 2 - name: DC EITC with qualifying children period: 2023 input: - eitc_child_count: 2 - dc_eitc_with_qualifying_child: 1 - dc_eitc_without_qualifying_child: 2 - state_code: DC + people: + head: + age: 30 + is_tax_unit_head: true + child: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + dc_eitc_with_qualifying_child: 1 + dc_eitc_without_qualifying_child: 2 + households: + household: + members: [head, child] + state_code: DC output: dc_eitc: 1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_with_qualifying_child.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_with_qualifying_child.yaml index e7c544f1704..74e43ba0a60 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_with_qualifying_child.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_with_qualifying_child.yaml @@ -9,7 +9,50 @@ - name: 70% match period: 2023 input: - state_code: DC - eitc: 100 + people: + head: + age: 30 + is_tax_unit_head: true + child: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + filer_adjusted_earnings: 294.11764705882354 + adjusted_gross_income: 294.11764705882354 + households: + household: + members: [head, child] + state_code: DC output: dc_eitc_with_qualifying_child: 70 + +- name: DC EITC with qualifying child includes ITIN child in 2025 + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + employment_income: 10_000 + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: DC + output: + dc_eitc_with_qualifying_child: 3_400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml index b93634e87ec..166fe58cc58 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml @@ -41,3 +41,28 @@ state_code: DC output: dc_eitc_without_qualifying_child: 823.60 # 1502 - (27743 - 19743) * 0.0848 + +- name: DC childless EITC allows ITIN filers + absolute_error_margin: 0.01 + period: 2025 + input: + people: + head: + age: 30 + employment_income: 8_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: DC + output: + dc_eitc_without_qualifying_child: 612 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_deductions.yaml index 6cf4f9495cb..877f35b9965 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_deductions.yaml @@ -3,9 +3,8 @@ input: tax_unit_itemizes: true ga_standard_deduction: 8_000 - medical_expense_deduction: 9_000 + itemized_taxable_income_deductions: 9_000 state_code: GA - state_sales_tax: 0 output: ga_deductions: 9_000 @@ -24,10 +23,18 @@ input: tax_unit_itemizes: true ga_standard_deduction: 10_000 - charitable_deduction: 2_000 - interest_deduction: 3_000 - casualty_loss_deduction: 8_000 + itemized_taxable_income_deductions: 13_000 state_code: GA - state_sales_tax: 0 output: ga_deductions: 13_000 + +- name: Georgia itemized deductions subtract Georgia-specific adjustments + period: 2023 + input: + tax_unit_itemizes: true + ga_standard_deduction: 10_000 + itemized_taxable_income_deductions: 18_000 + ga_itemized_deductions_adjustment: 3_000 + state_code: GA + output: + ga_deductions: 15_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml new file mode 100644 index 00000000000..7ad2c9f1a36 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml @@ -0,0 +1,17 @@ +- name: Hawaii can add back student loan interest when the Hawaii allowance is smaller than federal + period: 2025 + input: + state_code: HI + adjusted_gross_income: 1_000 + hi_student_loan_interest_adjustment: -100 + output: + hi_agi: 1_100 + +- name: Hawaii can subtract more student loan interest when the Hawaii allowance is larger than federal + period: 2025 + input: + state_code: HI + adjusted_gross_income: 1_000 + hi_student_loan_interest_adjustment: 75 + output: + hi_agi: 925 diff --git a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml index d820b852a51..2538ff305c2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml @@ -3,6 +3,7 @@ input: id_salt_deduction: 1_000 itemized_taxable_income_deductions: 1_600 + foreign_tax_credit: 0 state_code: ID output: id_itemized_deductions: 600 @@ -12,6 +13,17 @@ input: id_salt_deduction: 2_000 itemized_taxable_income_deductions: 1_600 + foreign_tax_credit: 0 state_code: ID output: id_itemized_deductions: 0 + +- name: Idaho adds back the federal foreign tax credit to itemized deductions + period: 2025 + input: + id_salt_deduction: 2_000 + itemized_taxable_income_deductions: 1_600 + foreign_tax_credit: 700 + state_code: ID + output: + id_itemized_deductions: 300 diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml index 3033b134318..8e4b1adfbf6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml @@ -6,7 +6,7 @@ age: 30 dependent: age: 1 - ctc_qualifying_child: true + is_tax_unit_dependent: true households: household: members: [head, dependent] @@ -26,7 +26,7 @@ age: 30 dependent: age: 12 - ctc_qualifying_child: true + is_tax_unit_dependent: true households: household: members: [head, dependent] @@ -46,13 +46,13 @@ age: 30 dependent: age: 12 - ctc_qualifying_child: true + is_tax_unit_dependent: true dependent2: age: 10 - ctc_qualifying_child: true + is_tax_unit_dependent: true dependent3: age: 9 - ctc_qualifying_child: true + is_tax_unit_dependent: true households: household: members: [head, dependent, dependent2, dependent3] @@ -63,3 +63,24 @@ il_eitc: 100 output: il_ctc: 20 + +- name: Illinois child credit follows dependency and age, not federal CTC child rules + period: 2024 + input: + people: + head: + age: 30 + dependent: + age: 11 + is_tax_unit_dependent: true + ctc_qualifying_child: false + households: + household: + members: [head, dependent] + state_code: IL + tax_units: + tax_unit: + members: [head, dependent] + il_eitc: 100 + output: + il_ctc: 20 diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml index e34a35bc618..8ec41264958 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml @@ -2,8 +2,22 @@ period: 2021 absolute_error_margin: 0.0001 input: - eitc: 1_000 - state_code: IL + people: + head: + age: 30 + is_tax_unit_head: true + child: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filer_adjusted_earnings: 2_941.176470588235 + adjusted_gross_income: 2_941.176470588235 + households: + household: + members: [head, child] + state_code: IL output: il_eitc: 180 @@ -20,8 +34,22 @@ period: 2023 absolute_error_margin: 0.0001 input: - eitc: 1_000 - state_code: IL + people: + head: + age: 30 + is_tax_unit_head: true + child: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filer_adjusted_earnings: 2_941.176470588235 + adjusted_gross_income: 2_941.176470588235 + households: + household: + members: [head, child] + state_code: IL output: il_eitc: 200 @@ -33,3 +61,31 @@ state_code: IL output: il_eitc: 0 + +- name: 2025 Illinois EITC includes ITIN qualifying children and younger filers + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + employment_income: 10_000 + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: IL + output: + il_eitc: 680 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/ms_agi_adjustments.yaml b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/ms_agi_adjustments.yaml new file mode 100644 index 00000000000..37294ec1b31 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/ms_agi_adjustments.yaml @@ -0,0 +1,10 @@ +- name: Mississippi includes modeled federal AGI adjustments it still allows + period: 2025 + input: + state_code: MS + self_employed_pension_contribution_ald: 100 + early_withdrawal_penalty: 50 + self_employed_health_insurance_ald: 200 + health_savings_account_ald: 150 + output: + ms_agi_adjustments: 500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/oh_educator_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/oh_educator_expense_deduction.yaml new file mode 100644 index 00000000000..5ec651de6ac --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/oh_educator_expense_deduction.yaml @@ -0,0 +1,23 @@ +- name: Ohio educator deduction uses the Ohio-specific deduction amount, not the raw federal expense + period: 2025 + input: + people: + person: + educator_expense: 300 + oh_educator_expense_deduction_person: 50 + taxable_social_security: 0 + oh_uniformed_services_retirement_income_deduction: 0 + oh_529_plan_deduction_person: 0 + pell_grant: 0 + disability_benefits: 0 + oh_unreimbursed_medical_care_expense_deduction_person: 0 + us_govt_interest_person: 0 + households: + household: + members: [person] + state_code: OH + tax_units: + tax_unit: + members: [person] + output: + oh_deductions: 50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml index 5b8dc978e46..b12de20ba89 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml @@ -2,7 +2,7 @@ period: 2022 input: state_code: PA - cdcc_potential: 0 + cdcc: 0 output: pa_cdcc: 0 @@ -10,7 +10,7 @@ period: 2022 input: state_code: PA - cdcc_potential: 1_000 + cdcc: 1_000 output: pa_cdcc: 300 @@ -18,6 +18,15 @@ period: 2023 input: state_code: PA - cdcc_potential: 1_000 + cdcc: 1_000 output: pa_cdcc: 1_000 + +- name: No Pennsylvania credit when only the federal potential credit is positive + period: 2023 + input: + state_code: PA + cdcc: 0 + cdcc_potential: 1_000 + output: + pa_cdcc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml index ecb37149441..27225b9cd81 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml @@ -1,4 +1,4 @@ -- name: Federal itemized deductions adopted for Connecticut +- name: Connecticut does not adopt federal itemized deductions period: 2023 input: state_code: CT @@ -9,8 +9,7 @@ interest_deduction: 8_000 salt_deduction: 10_000 output: - # State should use sum of federal itemized deduction components - state_itemized_deductions: 23_000 + state_itemized_deductions: 0 - name: Federal itemized deductions adopted for Georgia period: 2023 @@ -53,6 +52,20 @@ # State should use sum of federal itemized deduction components state_itemized_deductions: 27_500 +- name: Georgia uses the claimed federal itemized deduction amount + period: 2023 + input: + state_code: GA + filing_status: JOINT + age_head: 40 + age_spouse: 38 + charitable_deduction: 10_000 + interest_deduction: 15_000 + salt_deduction: 10_000 + itemized_taxable_income_deductions: 31_000 + output: + state_itemized_deductions: 31_000 + - name: New Mexico uses federal itemized deductions through its own implementation period: 2023 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_standard_deduction.yaml index 39f33682c43..9c1b1c3d302 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_standard_deduction.yaml @@ -1,4 +1,4 @@ -- name: Federal standard deduction adopted for Connecticut +- name: Connecticut does not adopt the federal standard deduction period: 2023 input: state_code: CT @@ -6,7 +6,7 @@ age_head: 30 output: standard_deduction: 13_850 - state_standard_deduction: 13_850 + state_standard_deduction: 0 - name: Federal standard deduction adopted for Utah married couple period: 2023 @@ -19,6 +19,15 @@ standard_deduction: 27_700 state_standard_deduction: 27_700 +- name: South Carolina does not use the federal standard deduction + period: 2026 + input: + state_code: SC + filing_status: SINGLE + age_head: 30 + output: + state_standard_deduction: 0 + - name: State-specific deduction for Georgia period: 2023 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_eitc.yaml index 09b9185c72c..b2f45916d79 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_eitc.yaml @@ -2,6 +2,7 @@ period: 2022 input: eitc: 1000 + employment_income: 1000 state_code: UT ut_income_tax_before_non_refundable_credits: 1000000 output: @@ -14,3 +15,13 @@ ut_income_tax_before_non_refundable_credits: 1000000 output: ut_eitc: 0 + +- name: Utah EITC is capped by wages shown on the W-2 + period: 2025 + input: + eitc: 1000 + employment_income: 150 + state_code: UT + ut_income_tax_before_non_refundable_credits: 1000000 + output: + ut_eitc: 150 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_federal_deductions_for_taxpayer_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_federal_deductions_for_taxpayer_credit.yaml index da3ae1b2f6d..0b633991a8b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_federal_deductions_for_taxpayer_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ut/tax/income/credits/ut_federal_deductions_for_taxpayer_credit.yaml @@ -24,6 +24,8 @@ medical_expense_deduction: 500 casualty_loss_deduction: 500 real_estate_taxes: 6_000 + itemized_taxable_income_deductions: 7_000 + state_and_local_sales_or_income_tax: 0 output: ut_federal_deductions_for_taxpayer_credit: 7_000 @@ -38,6 +40,8 @@ medical_expense_deduction: 500 casualty_loss_deduction: 500 real_estate_taxes: 12_000 + itemized_taxable_income_deductions: 12_000 + state_and_local_sales_or_income_tax: 0 output: ut_federal_deductions_for_taxpayer_credit: 12_000 @@ -55,3 +59,15 @@ output: ut_federal_deductions_for_taxpayer_credit: 5_000 +- name: "Case 5: Utah subtracts only state income or sales tax from claimed itemized deductions" + period: 2023 + input: + filing_status: JOINT + state_code: UT + tax_unit_itemizes: true + itemized_taxable_income_deductions: 15_000 + state_and_local_sales_or_income_tax: 9_000 + standard_deduction: 0 + output: + ut_federal_deductions_for_taxpayer_credit: 6_000 + diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml index b648f5315d1..67898d42295 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml @@ -190,3 +190,33 @@ output: # Either head or spouse meeting age 18 is sufficient. wa_working_families_tax_credit: 367 + +- name: Case 14, ITIN filer with qualifying child can receive WFTC without a federal EITC amount. + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 30 + employment_income: 10_000 + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: WA + output: + wa_working_families_tax_credit: 660 diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml index a676f7d4a30..47e1eef3036 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/filing_status.yaml @@ -97,3 +97,26 @@ output: # High-income adult child fails qualifying relative income test filing_status: SINGLE + +- name: Separated filer with only a qualifying relative is not HOH. + period: 2024 + input: + people: + parent: + age: 45 + employment_income: 50_000 + is_separated: true + adult_child: + age: 19 + is_full_time_student: false + is_tax_unit_dependent: true + is_tax_unit_spouse: false + employment_income: 3_000 + tax_units: + tax_unit: + members: [parent, adult_child] + households: + household: + members: [parent, adult_child] + output: + filing_status: SEPARATE diff --git a/policyengine_us/variables/gov/states/co/tax/income/additions/federal_deductions/co_federal_deduction_addback.py b/policyengine_us/variables/gov/states/co/tax/income/additions/federal_deductions/co_federal_deduction_addback.py index a30f006df47..a44811d843c 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/additions/federal_deductions/co_federal_deduction_addback.py +++ b/policyengine_us/variables/gov/states/co/tax/income/additions/federal_deductions/co_federal_deduction_addback.py @@ -22,7 +22,11 @@ def formula(tax_unit, period, parameters): if p.itemized_only: deductions = tax_unit("itemized_taxable_income_deductions", period) else: - deductions = tax_unit("taxable_income_deductions", period) + deductions = where( + tax_unit("tax_unit_itemizes", period), + tax_unit("itemized_taxable_income_deductions", period), + tax_unit("standard_deduction", period), + ) filing_status = tax_unit("filing_status", period) exemption = p.exemption[filing_status] return max_(deductions - exemption, 0) diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py b/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py index c707b20c552..670ee2b21b8 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_like_amount, +) class co_eitc(Variable): @@ -12,5 +15,53 @@ class co_eitc(Variable): def formula(tax_unit, period, parameters): federal_eitc = tax_unit("eitc", period) + person = tax_unit.members + age = person("age", period) + has_tin = person("has_tin", period) + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + federal_identification = person( + "meets_eitc_identification_requirements", period + ) + filer_has_federal_identification = ( + tax_unit.sum(is_head_or_spouse & ~federal_identification) == 0 + ) + qualifying_child_with_tin = ( + person("is_qualifying_child_dependent", period) & has_tin + ) + child_count_with_tin = tax_unit.sum(qualifying_child_with_tin) + itin_eitc = calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count_with_tin, + child_count_with_tin > 0, + filer_has_tin, + ) + + specified_student = person("is_full_time_college_student", period) | person( + "is_part_time_college_student", period + ) + specified_student |= person("technical_institution_student", period) + homeless_or_foster = person("was_in_foster_care", period) | person.household( + "is_homeless", period + ) + under_25_age_eligible = ( + ((age >= 19) & (age < 24) & ~specified_student) + | (age == 24) + | ((age >= 18) & (age < 24) & homeless_or_foster) + ) + under_25_demographic_eligible = tax_unit.any( + is_head_or_spouse & under_25_age_eligible + ) + under_25_eitc = calculate_eitc_like_amount( + tax_unit, + period, + parameters, + 0, + under_25_demographic_eligible, + filer_has_federal_identification, + ) p = parameters(period).gov.states.co.tax.income.credits - return federal_eitc * p.eitc.match + state_eitc = max_(federal_eitc, max_(itin_eitc, under_25_eitc)) + return state_eitc * p.eitc.match diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py index 192b5501a08..315e6de9aa1 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py @@ -13,8 +13,12 @@ class dc_eitc(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): + person = tax_unit.members + dc_qualifying_child = person("is_qualifying_child_dependent", period) & person( + "has_tin", period + ) return where( - tax_unit("eitc_child_count", period) > 0, + tax_unit.sum(dc_qualifying_child) > 0, tax_unit("dc_eitc_with_qualifying_child", period), tax_unit("dc_eitc_without_qualifying_child", period), ) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py index 1b53d7d6588..5cf2d58f89f 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_like_amount, +) class dc_eitc_with_qualifying_child(Variable): @@ -13,6 +16,19 @@ class dc_eitc_with_qualifying_child(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): - federal_eitc = tax_unit("eitc", period) + person = tax_unit.members + has_tin = person("has_tin", period) + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + qualifying_child = person("is_qualifying_child_dependent", period) & has_tin + child_count = tax_unit.sum(qualifying_child) + filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + federal_like_eitc = calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + child_count > 0, + filer_has_tin, + ) p = parameters(period).gov.states.dc.tax.income.credits - return federal_eitc * p.eitc.with_children.match + return federal_like_eitc * p.eitc.with_children.match diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py index 392ef615ebc..5bab5dc8193 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py @@ -1,4 +1,8 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + eitc_filing_requirement_met, + eitc_filing_status_eligible, +) class dc_eitc_without_qualifying_child(Variable): @@ -13,8 +17,26 @@ class dc_eitc_without_qualifying_child(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): - # calculate US EITC amount before phase-out - us_eligible = tax_unit("eitc_eligible", period) + person = tax_unit.members + age = person("age", period) + has_tin = person("has_tin", period) + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + min_age = parameters.gov.irs.credits.eitc.eligibility.age.min(period) + max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) + age_eligible = is_head_or_spouse & (age >= min_age) & (age <= max_age) + no_qualifying_child = tax_unit.sum( + person("is_qualifying_child_dependent", period) & has_tin + ) == 0 + us_eligible = ( + filer_has_tin + & no_qualifying_child + & tax_unit.any(age_eligible) + & tax_unit("eitc_investment_income_eligible", period) + & eitc_filing_status_eligible(tax_unit, period, parameters) + & eitc_filing_requirement_met(tax_unit, period) + & tax_unit("takes_up_eitc", period) + ) us_eitc = us_eligible * tax_unit("eitc_phased_in", period) # phase out us_eitc for income above DC phase-out start threshold earnings = tax_unit("tax_unit_earned_income", period) diff --git a/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_deductions.py b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_deductions.py index 59e0adad3de..2e8e382a280 100644 --- a/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_deductions.py +++ b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_deductions.py @@ -25,9 +25,6 @@ class ga_deductions(Variable): def formula(tax_unit, period, parameters): itemizes = tax_unit("tax_unit_itemizes", period) sd = tax_unit("ga_standard_deduction", period) - # 48-7-27(a)(1) states: - # "Either the sum of all itemized nonbusiness deductions used in computing - # such taxpayer’s federal taxable income or..." - p = parameters(period).gov.irs.deductions - itemized = add(tax_unit, period, p.itemized_deductions) + itemized = tax_unit("itemized_taxable_income_deductions", period) + itemized -= tax_unit("ga_itemized_deductions_adjustment", period) return where(itemizes, itemized, sd) diff --git a/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py new file mode 100644 index 00000000000..8d253b52449 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class ga_itemized_deductions_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "Georgia itemized deductions adjustment" + unit = USD + definition_period = YEAR + default_value = 0 + defined_for = StateCode.GA diff --git a/policyengine_us/variables/gov/states/hi/tax/income/hi_additions.py b/policyengine_us/variables/gov/states/hi/tax/income/hi_additions.py index 1a7a33bbec7..3c9013ec7ca 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/hi_additions.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/hi_additions.py @@ -9,3 +9,5 @@ class hi_additions(Variable): defined_for = StateCode.HI unit = USD definition_period = YEAR + + adds = "gov.states.hi.tax.income.additions.additions" diff --git a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py new file mode 100644 index 00000000000..7f5b5324d7c --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py @@ -0,0 +1,37 @@ +from policyengine_us.model_api import * + + +class hi_student_loan_interest_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii student loan interest adjustment relative to federal AGI" + unit = USD + definition_period = YEAR + default_value = 0 + defined_for = StateCode.HI + + +class hi_student_loan_interest_subtraction(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii student loan interest subtraction" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + adjustment = tax_unit("hi_student_loan_interest_adjustment", period) + return max_(adjustment, 0) + + +class hi_student_loan_interest_addition(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii student loan interest addition" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + adjustment = tax_unit("hi_student_loan_interest_adjustment", period) + return max_(0, -adjustment) diff --git a/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py b/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py index d717b115b4a..625e9e072da 100644 --- a/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py @@ -18,4 +18,5 @@ def formula(tax_unit, period, parameters): # by the amount of the SALT deduction id_salt_ded = tax_unit("id_salt_deduction", period) itemized_ded = tax_unit("itemized_taxable_income_deductions", period) - return max_(itemized_ded - id_salt_ded, 0) + foreign_tax_credit = tax_unit("foreign_tax_credit", period) + return max_(itemized_ded - id_salt_ded + foreign_tax_credit, 0) diff --git a/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py b/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py index 03104d26b75..fcaf7fa9e76 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py +++ b/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py @@ -14,9 +14,8 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.il.tax.income.credits.ctc person = tax_unit.members age = person("age", period) - age_eligible_child = age < p.age_limit - federal_ctc_eligible_child = person("ctc_qualifying_child", period) - eligible_child = age_eligible_child & federal_ctc_eligible_child + dependent = person("is_tax_unit_dependent", period) + eligible_child = dependent & (age < p.age_limit) eligible_child_present = tax_unit.any(eligible_child) state_eitc = tax_unit("il_eitc", period) return eligible_child_present * state_eitc * p.rate diff --git a/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py b/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py index 21dc19e29a0..c8c1855b081 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py +++ b/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_like_amount, +) class il_eitc(Variable): @@ -11,6 +14,23 @@ class il_eitc(Variable): defined_for = StateCode.IL def formula(tax_unit, period, parameters): - federal_eitc = tax_unit("eitc", period) + person = tax_unit.members + age = person("age", period) + has_tin = person("has_tin", period) + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + qualifying_child = person("is_qualifying_child_dependent", period) & has_tin + child_count = tax_unit.sum(qualifying_child) + filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + demographic_eligible = (child_count > 0) | tax_unit.any( + is_head_or_spouse & (age >= 18) + ) + state_eitc = calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + demographic_eligible, + filer_has_tin, + ) match = parameters(period).gov.states.il.tax.income.credits.eitc.match - return federal_eitc * match + return state_eitc * match diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py new file mode 100644 index 00000000000..33d281ba7cb --- /dev/null +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class oh_educator_expense_deduction_person(Variable): + value_type = float + entity = Person + label = "Ohio educator expense deduction" + unit = USD + definition_period = YEAR + default_value = 0 + defined_for = StateCode.OH diff --git a/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py b/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py index 41ce25c2f33..e9196d559da 100644 --- a/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py +++ b/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py @@ -11,9 +11,8 @@ class pa_cdcc(Variable): defined_for = StateCode.PA def formula(tax_unit, period, parameters): - # Get the federal CDCC value - # Pennsylvania matches the potential federal credit - cdcc = tax_unit("cdcc_potential", period) + # Pennsylvania matches the federal CDCC actually claimed. + cdcc = tax_unit("cdcc", period) # Access the parameter path p = parameters(period).gov.states.pa.tax.income.credits.cdcc # Multiply the federal CDCC value by the Pennsylvania CDCC rate diff --git a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py b/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py new file mode 100644 index 00000000000..4a3e8904107 --- /dev/null +++ b/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py @@ -0,0 +1,70 @@ +"""Shared helpers for state EITC formulas that partially track federal rules.""" + +from policyengine_us.model_api import * + + +def eitc_filing_requirement_met(tax_unit, period): + """Mirror the federal EITC filing condition used in the baseline formula.""" + + is_required = tax_unit("tax_unit_is_required_to_file", period) + files_voluntarily = tax_unit("would_file_taxes_voluntarily", period) + would_file_for_credits = tax_unit( + "would_file_if_eligible_for_refundable_credit", period + ) + return is_required | files_voluntarily | would_file_for_credits + + +def eitc_filing_status_eligible( + tax_unit, period, parameters, separate_filer_eligible=None +): + """Apply the federal EITC separate-filer rule unless a state overrides it.""" + + if separate_filer_eligible is None: + separate_filer_eligible = ( + parameters.gov.irs.credits.eitc.eligibility.separate_filer(period) + ) + if separate_filer_eligible: + return True + filing_status = tax_unit("filing_status", period) + return filing_status != filing_status.possible_values.SEPARATE + + +def calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + demographic_eligible, + filer_identification_eligible, + separate_filer_eligible=None, +): + """Calculate a federal-style EITC amount using state-specific child/ID rules.""" + + eitc = parameters(period).gov.irs.credits.eitc + earnings = tax_unit("filer_adjusted_earnings", period) + agi = tax_unit("adjusted_gross_income", period) + maximum = eitc.max.calc(child_count) + phase_in_rate = eitc.phase_in_rate.calc(child_count) + phased_in = min_(maximum, earnings * phase_in_rate) + phase_out_start = eitc.phase_out.start.calc(child_count) + phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc.phase_out.joint_bonus.calc( + child_count + ) + phase_out_rate = eitc.phase_out.rate.calc(child_count) + reduction = max_(0, max_(earnings, agi) - phase_out_start) * phase_out_rate + limitation = max_(0, maximum - reduction) + investment_income_eligible = tax_unit("eitc_investment_income_eligible", period) + filing_status_eligible = eitc_filing_status_eligible( + tax_unit, period, parameters, separate_filer_eligible + ) + is_filer = eitc_filing_requirement_met(tax_unit, period) + takes_up_eitc = tax_unit("takes_up_eitc", period) + return ( + min_(phased_in, limitation) + * demographic_eligible + * filer_identification_eligible + * investment_income_eligible + * filing_status_eligible + * is_filer + * takes_up_eitc + ) diff --git a/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py b/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py index 7c7c584b279..1e1b4ce0ad2 100644 --- a/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py @@ -13,10 +13,8 @@ def formula(tax_unit, period, parameters): # States that adopt the federal itemized deductions # Based on comments in state_itemized_deductions.yaml FEDERAL_ITEMIZED_DEDUCTION_STATES = [ - "CT", # Connecticut "GA", # Georgia "ND", # North Dakota - "SC", # South Carolina "UT", # Utah ] @@ -61,12 +59,7 @@ def formula(tax_unit, period, parameters): # Check if the state adopts federal itemized deductions uses_federal = np.isin(state_code, FEDERAL_ITEMIZED_DEDUCTION_STATES) - # Get federal itemized deductions (sum of all federal itemized deduction components) - federal_itemized = add( - tax_unit, - period, - parameters(period).gov.irs.deductions.itemized_deductions, - ) + federal_itemized = tax_unit("itemized_taxable_income_deductions", period) # Return federal itemized deductions for states that adopt them, otherwise state-specific return where(uses_federal, federal_itemized, state_specific_base) diff --git a/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py b/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py index 32cdf244fd0..51fd2021cb3 100644 --- a/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py +++ b/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py @@ -12,12 +12,10 @@ def formula(tax_unit, period, parameters): # States that adopt the federal standard deduction # Based on comments in state_standard_deductions.yaml FEDERAL_STANDARD_DEDUCTION_STATES = [ - "CT", # Connecticut "ID", # Idaho "MO", # Missouri "ND", # North Dakota "NM", # New Mexico - "SC", # South Carolina "UT", # Utah "CO", # Colorado ] diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py index b466f8c3f0d..635e33404b7 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py @@ -12,27 +12,12 @@ class ut_federal_deductions_for_taxpayer_credit(Variable): reference = "https://le.utah.gov/xcode/Title59/Chapter10/59-10-S114.html?v=C59-10-S114_2022032320220323" def formula(tax_unit, period, parameters): - p = parameters(period).gov.irs.deductions - filing_status = tax_unit("filing_status", period) - us_itemizing = tax_unit("tax_unit_itemizes", period) std_ded = tax_unit("standard_deduction", period) - - # Subtract SALT from Itemized Deductions - deductions = [ - deduction - for deduction in p.itemized_deductions - if deduction not in ["salt_deduction"] - ] - item_ded = add(tax_unit, period, deductions) - - # Include Real Estate Taxes in Itemized Deductions - real_estate_tax = add(tax_unit, period, ["real_estate_taxes"]) - - capped_real_estate_tax = min_( - real_estate_tax, p.itemized.salt_and_real_estate.cap[filing_status] + itemized = tax_unit("itemized_taxable_income_deductions", period) + state_income_or_sales_tax = tax_unit( + "state_and_local_sales_or_income_tax", period + ) + return where( + us_itemizing, max_(itemized - state_income_or_sales_tax, 0), std_ded ) - - # Line 12. Federal Standard or Itemized Deductions - total_item_ded = item_ded + capped_real_estate_tax - return where(us_itemizing, total_item_ded, std_ded) diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py index 0619d85cdf5..33dfe76e8a9 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py @@ -14,4 +14,5 @@ class ut_eitc_potential(Variable): def formula(tax_unit, period, parameters): p = parameters(period).gov.states.ut.tax.income.credits.earned_income federal_eitc = tax_unit("eitc", period) - return p.rate * federal_eitc + wages = add(tax_unit, period, ["employment_income"]) + return min_(p.rate * federal_eitc, wages) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py index 4a4a40d95e6..4e83abe9c0a 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_like_amount, +) class wa_working_families_tax_credit(Variable): @@ -14,29 +17,79 @@ class wa_working_families_tax_credit(Variable): defined_for = StateCode.WA def formula(tax_unit, period, parameters): + person = tax_unit.members + has_tin = person("has_tin", period) + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + child_count = tax_unit.sum( + person("is_qualifying_child_dependent", period) & has_tin + ) + filer_has_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + federal_identification_eligible = tax_unit( + "filer_meets_eitc_identification_requirements", period + ) + filing_status = tax_unit("filing_status", period) + separate = filing_status == filing_status.possible_values.SEPARATE + federal_child_count = tax_unit("eitc_child_count", period) + age = person("age", period) + student = person("is_full_time_student", period) + min_age = parameters.gov.irs.credits.eitc.eligibility.age.min(period) + min_age_student = parameters.gov.irs.credits.eitc.eligibility.age.min_student( + period + ) + max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) + age_floor = where(student, min_age_student, min_age) + demographic_eligible = (child_count > 0) | tax_unit.any( + is_head_or_spouse & (age >= age_floor) & (age <= max_age) + ) + # Baseline eligibility: filers who claim EITC eitc = tax_unit("eitc", period) eitc_eligible = eitc > 0 + needs_state_only_path = ( + (~federal_identification_eligible & filer_has_tin) + | separate + | (child_count > federal_child_count) + ) + state_only_eitc_eligible = needs_state_only_path & ( + calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + demographic_eligible, + filer_has_tin, + separate_filer_eligible=True, + ) + > 0 + ) # ESSB 6346 Sec. 901: age expansion eligibility (effective 2029) age_expansion_eligible = tax_unit( "wa_working_families_tax_credit_age_expansion_eligible", period ) - eligible = eitc_eligible | age_expansion_eligible + eligible = eitc_eligible | state_only_eitc_eligible | age_expansion_eligible # Parameters are based on EITC-eligible children. p = parameters( period ).gov.states.wa.tax.income.credits.working_families_tax_credit - eitc_child_count = tax_unit("eitc_child_count", period) + eitc_child_count = max_(federal_child_count, child_count) max_amount = p.amount.calc(eitc_child_count) # WFTC phases out at a certain amount below the EITC maximum AGI. # NB: The Revised Code of Washington is ambiguous: # "below the federal phase-out income" # The legislative analysis clarifies that this refers to "federal maximum AGI" # https://lawfilesext.leg.wa.gov/biennium/2021-22/Pdf/Bill%20Reports/House/1297-S.E%20HBR%20FBR%2021.pdf?q=20220706071752 - eitc_agi_limit = tax_unit("eitc_agi_limit", period) + eitc_parameters = parameters(period).gov.irs.credits.eitc + phase_out_start = eitc_parameters.phase_out.start.calc(eitc_child_count) + phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc_parameters.phase_out.joint_bonus.calc( + eitc_child_count + ) + eitc_agi_limit = phase_out_start + eitc_parameters.max.calc( + eitc_child_count + ) / eitc_parameters.phase_out.rate.calc(eitc_child_count) + eitc_agi_limit = max_(tax_unit("eitc_agi_limit", period), eitc_agi_limit) phase_out_start_reduction = p.phase_out.start_below_eitc.calc(eitc_child_count) phase_out_start = eitc_agi_limit - phase_out_start_reduction # The phase-out rates are hard-coded in the legal code, but HB 1888 (2021-22) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py index 45a30b07bf3..69aa85ed402 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py @@ -17,35 +17,35 @@ def formula(tax_unit, period, parameters): ).gov.states.wa.tax.income.credits.working_families_tax_credit.age_expansion expansion_in_effect = p.in_effect + person = tax_unit.members age_head = tax_unit("age_head", period) age_spouse = tax_unit("age_spouse", period) filer_meets_min_age = (age_head >= p.min_age) | (age_spouse >= p.min_age) investment_income_eligible = tax_unit("eitc_investment_income_eligible", period) - filers_have_ssn = tax_unit( - "filer_meets_eitc_identification_requirements", period + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + has_tin = person("has_tin", period) + filers_have_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 + child_count = tax_unit.sum( + person("is_qualifying_child_dependent", period) & has_tin ) - eitc_amount_before_take_up = min_( - tax_unit("eitc_phased_in", period), - max_( - 0, - tax_unit("eitc_maximum", period) - tax_unit("eitc_reduction", period), - ), + eitc = parameters(period).gov.irs.credits.eitc + earnings = tax_unit("filer_adjusted_earnings", period) + agi = tax_unit("adjusted_gross_income", period) + maximum = eitc.max.calc(child_count) + phased_in = min_(maximum, earnings * eitc.phase_in_rate.calc(child_count)) + phase_out_start = eitc.phase_out.start.calc(child_count) + phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc.phase_out.joint_bonus.calc( + child_count ) - - eitc = parameters.gov.irs.credits.eitc(period) - if eitc.eligibility.separate_filer: - filing_status_eligible = True - else: - filing_status = tax_unit("filing_status", period) - filing_status_eligible = ( - filing_status != filing_status.possible_values.SEPARATE - ) + reduction = max_(0, max_(earnings, agi) - phase_out_start) * eitc.phase_out.rate.calc( + child_count + ) + eitc_amount_before_take_up = min_(phased_in, max_(0, maximum - reduction)) return ( expansion_in_effect & filer_meets_min_age & investment_income_eligible - & filers_have_ssn + & filers_have_tin & (eitc_amount_before_take_up > 0) - & filing_status_eligible ) diff --git a/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py b/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py index cf0edd3f5db..f0de0325fd5 100644 --- a/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py +++ b/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py @@ -11,6 +11,7 @@ class head_of_household_eligible(Variable): def formula(tax_unit, period, parameters): married = tax_unit("tax_unit_married", period) person = tax_unit.members + is_separated = tax_unit.any(person("is_separated", period)) # Qualifying children and permanently disabled always count is_qualifying_child = person("is_qualifying_child_dependent", period) is_disabled_dependent = person( @@ -25,8 +26,10 @@ def formula(tax_unit, period, parameters): | (is_qualifying_relative & is_related) ) has_qualifying_person = tax_unit.sum(is_hoh_qualifying) > 0 - return ( - has_qualifying_person - & ~married - & ~tax_unit("surviving_spouse_eligible", period) - ) + # IRC 7703(b) treated-unmarried status supports HoH only through the + # child-abode path, not the broader qualifying-relative route. + treated_unmarried_qualifies = tax_unit.sum(is_qualifying_child) > 0 + surviving_spouse = tax_unit("surviving_spouse_eligible", period) + unmarried_qualifies = has_qualifying_person & ~married & ~is_separated + separated_qualifies = treated_unmarried_qualifies & is_separated + return (unmarried_qualifies | separated_qualifies) & ~surviving_spouse From a427a51c6e4d63ac4b0e2d65b8ffdf306fe413bf Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 7 May 2026 12:19:47 -0400 Subject: [PATCH 2/9] Freeze state EITC conformity to federal law dates --- .../hi_student_loan_interest_adjustment.yaml | 53 ++++- .../gov/states/in/tax/income/in_eitc.yaml | 14 ++ .../in/tax/income/in_eitc_eligible.yaml | 15 ++ .../wa_working_families_tax_credit.yaml | 209 +++++++++++++++--- .../ga_itemized_deductions_adjustment.py | 7 + .../hi_student_loan_interest_adjustment.py | 60 ++++- .../credits/earned_income_credit/in_eitc.py | 29 +++ .../earned_income_credit/in_eitc_eligible.py | 29 +++ .../oh_educator_expense_deduction_person.py | 10 + .../states/tax/income/credits/eitc_helpers.py | 100 +++++++-- .../credits/wa_working_families_tax_credit.py | 83 ++++--- ...ilies_tax_credit_age_expansion_eligible.py | 23 +- 12 files changed, 532 insertions(+), 100 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml index 7ad2c9f1a36..08bd05deae6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml @@ -1,17 +1,50 @@ -- name: Hawaii can add back student loan interest when the Hawaii allowance is smaller than federal +- name: Hawaii adds back part of the federal student loan deduction when Hawaii's deduction is smaller period: 2025 + absolute_error_margin: 0.1 input: - state_code: HI - adjusted_gross_income: 1_000 - hi_student_loan_interest_adjustment: -100 + people: + head: + age: 30 + employment_income: 60_000 + student_loan_interest: 2_500 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: HI output: - hi_agi: 1_100 + hi_student_loan_interest_adjustment: -1250 + hi_agi: 58_750 -- name: Hawaii can subtract more student loan interest when the Hawaii allowance is larger than federal +- name: Hawaii subtracts more than federal when Hawaii AGI excludes pension income period: 2025 + absolute_error_margin: 0.2 input: - state_code: HI - adjusted_gross_income: 1_000 - hi_student_loan_interest_adjustment: 75 + people: + head: + age: 30 + employment_income: 60_000 + taxable_pension_income: 40_000 + student_loan_interest: 2_500 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: HI output: - hi_agi: 925 + hi_student_loan_interest_adjustment: 833.33 + hi_agi: 19_166.67 diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc.yaml index 4ee1dd8e2e6..0b29a13315f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc.yaml @@ -32,3 +32,17 @@ employment_income: 19_500 output: in_eitc: 0.09 * 5_980 + +- name: in_eitc unit test 4 + absolute_error_margin: 0.01 + period: 2025 + input: + state_code: IN + eitc: 0 + in_eitc_eligible: true + filing_status: HEAD_OF_HOUSEHOLD + eitc_child_count: 1 + filer_adjusted_earnings: 10_000 + adjusted_gross_income: 10_000 + output: + in_eitc: 340 diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc_eligible.yaml index f0d512184da..24442fa4938 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/in_eitc_eligible.yaml @@ -89,3 +89,18 @@ dividend_income: 3_600 output: in_eitc_eligible: false + +- name: in_eitc_eligible unit test 8 + period: 2025 + input: + state_code: IN + eitc: 0 + filing_status: HEAD_OF_HOUSEHOLD + eitc_child_count: 1 + filer_adjusted_earnings: 10_000 + adjusted_gross_income: 10_000 + filer_meets_eitc_identification_requirements: true + would_file_taxes_voluntarily: true + takes_up_eitc: true + output: + in_eitc_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml index 67898d42295..4bc7326fbba 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml @@ -9,14 +9,30 @@ output: wa_working_families_tax_credit: 0 -- name: Case 2, single eligible person with no income gets maximum credit. +- name: Case 2, single eligible person with low income gets maximum credit. period: 2022 absolute_error_margin: 0 input: - state_code: WA - eitc: 1 - filer_adjusted_earnings: 0 - eitc_child_count: 0 + people: + head: + age: 30 + employment_income: 1 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA output: wa_working_families_tax_credit: 300 @@ -33,28 +49,68 @@ - name: Case 4, at EITC AGI limit credit phases to $50 minimum. period: 2024 - absolute_error_margin: 0.01 + absolute_error_margin: 1 input: - state_code: WA - eitc: 1 - eitc_child_count: 1 - eitc_agi_limit: 50_000 - filer_adjusted_earnings: 50_000 + people: + head: + age: 30 + employment_income: 43_490 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: WA output: # Per RCW 82.08.0206(3)(f), minimum credit is $50 wa_working_families_tax_credit: 50 - name: Case 5, mid-phaseout gives correctly reduced credit. period: 2024 - absolute_error_margin: 0.01 + absolute_error_margin: 0.2 input: - state_code: WA - eitc: 1 - eitc_child_count: 1 - eitc_agi_limit: 50_000 - filer_adjusted_earnings: 47_500 + people: + head: + age: 30 + employment_income: 40_992 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: WA output: - wa_working_families_tax_credit: 345 + wa_working_families_tax_credit: 344.82 - name: Case 6, joint filer with 1 child and moderate income receives credit. period: 2024 @@ -64,15 +120,23 @@ filer: employment_income: 40_000 age: 30 + has_tin: true + ssn_card_type: CITIZEN spouse: - dividend_income: 11_000 + employment_income: 0 age: 30 + has_tin: true + ssn_card_type: CITIZEN child: age: 5 + has_tin: true + ssn_card_type: CITIZEN tax_units: tax_unit: members: [filer, spouse, child] filing_status: JOINT + would_file_taxes_voluntarily: true + takes_up_eitc: true families: family: members: [filer, spouse, child] @@ -118,11 +182,26 @@ - name: Case 8, age 17 without EITC gets no WFTC in 2029. period: 2029 input: - state_code: WA - age_head: 17 - eitc: 0 - filer_adjusted_earnings: 10_000 - eitc_child_count: 0 + people: + head: + age: 17 + employment_income: 10_000 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA output: # Not EITC eligible and under age 18 minimum wa_working_families_tax_credit: 0 @@ -142,11 +221,26 @@ - name: Case 10, WFTC age expansion not in effect before 2029. period: 2028 input: - state_code: WA - age_head: 20 - eitc: 0 - filer_adjusted_earnings: 10_000 - eitc_child_count: 0 + people: + head: + age: 20 + employment_income: 10_000 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA output: # Age expansion starts 2029, no EITC eligibility wa_working_families_tax_credit: 0 @@ -155,10 +249,26 @@ period: 2029 absolute_error_margin: 1 input: - state_code: WA - eitc: 1 - filer_adjusted_earnings: 0 - eitc_child_count: 0 + people: + head: + age: 30 + employment_income: 10_000 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA output: # Standard EITC pathway still works # Amount is inflation-adjusted from $300 (2022) to ~$367 (2029) @@ -220,3 +330,36 @@ state_code: WA output: wa_working_families_tax_credit: 660 + +- name: Case 15, standard SSN filer still gets WFTC under frozen 2022 federal law when current federal EITC is overridden to zero. + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 30 + employment_income: 10_000 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_head: true + child: + age: 5 + has_tin: true + ssn_card_type: CITIZEN + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + eitc: 0 + would_file_taxes_voluntarily: true + takes_up_eitc: true + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: WA + output: + wa_working_families_tax_credit: 660 diff --git a/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py index 8d253b52449..993cdfd255f 100644 --- a/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py +++ b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py @@ -9,3 +9,10 @@ class ga_itemized_deductions_adjustment(Variable): definition_period = YEAR default_value = 0 defined_for = StateCode.GA + documentation = ( + "Georgia-specific reduction from federal Schedule A before computing " + "Georgia itemized deductions. This remains an explicit input because " + "the model does not separately observe components such as other-state " + "income taxes and exempt-income investment interest." + ) + reference = "https://dor.georgia.gov/document/document/2025-it-511-individual-income-tax-booklet/download" diff --git a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py index 7f5b5324d7c..07b720a4a98 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py @@ -1,14 +1,72 @@ from policyengine_us.model_api import * +class hi_student_loan_interest_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii student loan interest deduction" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" + + def formula(tax_unit, period, parameters): + person = tax_unit.members + eligible = person("student_loan_interest_ald_eligible", period) + interest_paid = tax_unit.sum(person("student_loan_interest", period) * eligible) + capped_interest = min_(interest_paid, 2_500) + + p = parameters(period).gov.states.hi.tax.income + other_additions = [ + variable + for variable in p.additions.additions + if variable != "hi_student_loan_interest_addition" + ] + other_subtractions = [ + variable + for variable in p.subtractions.subtractions + if variable != "hi_student_loan_interest_subtraction" + ] + other_additions_amount = ( + add(tax_unit, period, other_additions) if len(other_additions) > 0 else 0 + ) + other_subtractions_amount = ( + add(tax_unit, period, other_subtractions) + if len(other_subtractions) > 0 + else 0 + ) + hi_magi = tax_unit("adjusted_gross_income", period) + hi_magi += other_additions_amount + hi_magi -= other_subtractions_amount + + filing_status = tax_unit("filing_status", period) + status = filing_status.possible_values + joint = filing_status == status.JOINT + separate = filing_status == status.SEPARATE + phase_out_start = where(joint, 100_000, 50_000) + phase_out_divisor = where(joint, 30_000, 15_000) + reduction_share = np.minimum( + 1.0, np.maximum(0.0, (hi_magi - phase_out_start) / phase_out_divisor) + ) + deduction = capped_interest * (1 - reduction_share) + return where(separate, 0, deduction) + + class hi_student_loan_interest_adjustment(Variable): value_type = float entity = TaxUnit label = "Hawaii student loan interest adjustment relative to federal AGI" unit = USD definition_period = YEAR - default_value = 0 defined_for = StateCode.HI + reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" + + def formula(tax_unit, period, parameters): + hawaii_deduction = tax_unit("hi_student_loan_interest_deduction", period) + federal_deduction = tax_unit.sum( + tax_unit.members("student_loan_interest_ald", period) + ) + return hawaii_deduction - federal_deduction class hi_student_loan_interest_subtraction(Variable): diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py index dd240a434ad..5e414c7f57d 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py @@ -1,4 +1,8 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_demographic_eligibility, + calculate_eitc_like_amount, +) class in_eitc(Variable): @@ -15,6 +19,31 @@ def formula(tax_unit, period, parameters): if not ip.earned_income.decoupled: federal_eitc = tax_unit("eitc", period) return federal_eitc * ip.earned_income.match_rate + if period.start.year >= 2023: + frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") + child_count = tax_unit("eitc_child_count", period) + demographic_eligible = calculate_eitc_demographic_eligibility( + tax_unit, period, frozen_eitc, child_count + ) + filer_identification_eligible = tax_unit( + "filer_meets_eitc_identification_requirements", period + ) + investment_income_eligible = ( + tax_unit("eitc_relevant_investment_income", period) + <= frozen_eitc.phase_out.max_investment_income + ) + frozen_federal_eitc = calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + demographic_eligible, + filer_identification_eligible, + separate_filer_eligible=frozen_eitc.eligibility.separate_filer, + eitc_parameters=frozen_eitc, + investment_income_eligible=investment_income_eligible, + ) + return frozen_federal_eitc * ip.earned_income.match_rate # if Indiana EITC is decoupled from federal EITC fp = parameters(period).gov.irs.credits # ... cap child count diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py index 0936388f7cc..db2c8230f68 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py @@ -1,4 +1,8 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_demographic_eligibility, + calculate_eitc_like_amount, +) class in_eitc_eligible(Variable): @@ -16,6 +20,31 @@ def formula(tax_unit, period, parameters): gets_federal_eitc = tax_unit("eitc", period) > 0 if not p.credits.earned_income.decoupled: return gets_federal_eitc + if period.start.year >= 2023: + frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") + child_count = tax_unit("eitc_child_count", period) + demographic_eligible = calculate_eitc_demographic_eligibility( + tax_unit, period, frozen_eitc, child_count + ) + filer_identification_eligible = tax_unit( + "filer_meets_eitc_identification_requirements", period + ) + investment_income_eligible = ( + tax_unit("eitc_relevant_investment_income", period) + <= frozen_eitc.phase_out.max_investment_income + ) + frozen_federal_eitc = calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + demographic_eligible, + filer_identification_eligible, + separate_filer_eligible=frozen_eitc.eligibility.separate_filer, + eitc_parameters=frozen_eitc, + investment_income_eligible=investment_income_eligible, + ) + return frozen_federal_eitc > 0 # if Indiana EITC is decoupled from federal EITC # ... check separate filing status filing_status = tax_unit("filing_status", period) diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py index 33d281ba7cb..1cb9e92cecb 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py @@ -9,3 +9,13 @@ class oh_educator_expense_deduction_person(Variable): definition_period = YEAR default_value = 0 defined_for = StateCode.OH + documentation = ( + "Ohio-specific educator deduction allowed in excess of the federal " + "educator deduction for a qualifying Ohio educator. This remains an " + "explicit input because the baseline data do not identify Ohio " + "licensure/teaching status or expenses above the federal deduction." + ) + reference = ( + "https://codes.ohio.gov/ohio-revised-code/section-5747.01", + "https://dam.assets.ohio.gov/image/upload/v1767095693/tax.ohio.gov/forms/ohio_individual/individual/2025/it1040-booklet.pdf", + ) diff --git a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py b/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py index 4a3e8904107..694c96d96c1 100644 --- a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py +++ b/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py @@ -29,38 +29,108 @@ def eitc_filing_status_eligible( return filing_status != filing_status.possible_values.SEPARATE -def calculate_eitc_like_amount( +def calculate_eitc_demographic_eligibility( + tax_unit, period, eitc_parameters, child_count=None +): + """Apply the EITC child-count and age tests using a specific law version.""" + + if child_count is None: + child_count = tax_unit("eitc_child_count", period) + has_child = child_count > 0 + person = tax_unit.members + age = person("age", period) + student = person("is_full_time_student", period) + min_age = where( + student, + eitc_parameters.eligibility.age.min_student, + eitc_parameters.eligibility.age.min, + ) + max_age = eitc_parameters.eligibility.age.max + return has_child | tax_unit.any((age >= min_age) & (age <= max_age)) + + +def calculate_eitc_phase_out_start( tax_unit, period, - parameters, + eitc_parameters, child_count, - demographic_eligible, - filer_identification_eligible, - separate_filer_eligible=None, ): - """Calculate a federal-style EITC amount using state-specific child/ID rules.""" + """Return the EITC phase-out starting point for a chosen law version.""" + + phase_out_start = eitc_parameters.phase_out.start.calc(child_count) + phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc_parameters.phase_out.joint_bonus.calc( + child_count + ) + return phase_out_start + + +def calculate_eitc_amount_from_parameters( + tax_unit, + period, + eitc_parameters, + child_count, +): + """Calculate the uncapped EITC amount under a chosen parameter set.""" - eitc = parameters(period).gov.irs.credits.eitc earnings = tax_unit("filer_adjusted_earnings", period) agi = tax_unit("adjusted_gross_income", period) - maximum = eitc.max.calc(child_count) - phase_in_rate = eitc.phase_in_rate.calc(child_count) + maximum = eitc_parameters.max.calc(child_count) + phase_in_rate = eitc_parameters.phase_in_rate.calc(child_count) phased_in = min_(maximum, earnings * phase_in_rate) - phase_out_start = eitc.phase_out.start.calc(child_count) - phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc.phase_out.joint_bonus.calc( - child_count + phase_out_start = calculate_eitc_phase_out_start( + tax_unit, period, eitc_parameters, child_count ) - phase_out_rate = eitc.phase_out.rate.calc(child_count) + phase_out_rate = eitc_parameters.phase_out.rate.calc(child_count) reduction = max_(0, max_(earnings, agi) - phase_out_start) * phase_out_rate limitation = max_(0, maximum - reduction) - investment_income_eligible = tax_unit("eitc_investment_income_eligible", period) + return min_(phased_in, limitation) + + +def calculate_eitc_max_agi_limit( + tax_unit, + period, + eitc_parameters, + child_count, +): + """Return the maximum AGI at which a positive EITC remains available.""" + + phase_out_start = calculate_eitc_phase_out_start( + tax_unit, period, eitc_parameters, child_count + ) + return phase_out_start + eitc_parameters.max.calc( + child_count + ) / eitc_parameters.phase_out.rate.calc(child_count) + + +def calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + demographic_eligible, + filer_identification_eligible, + separate_filer_eligible=None, + eitc_parameters=None, + investment_income_eligible=None, +): + """Calculate a federal-style EITC amount using state-specific child/ID rules.""" + + if eitc_parameters is None: + eitc_parameters = parameters(period).gov.irs.credits.eitc + if investment_income_eligible is None: + investment_income_eligible = ( + tax_unit("eitc_relevant_investment_income", period) + <= eitc_parameters.phase_out.max_investment_income + ) filing_status_eligible = eitc_filing_status_eligible( tax_unit, period, parameters, separate_filer_eligible ) is_filer = eitc_filing_requirement_met(tax_unit, period) takes_up_eitc = tax_unit("takes_up_eitc", period) return ( - min_(phased_in, limitation) + calculate_eitc_amount_from_parameters( + tax_unit, period, eitc_parameters, child_count + ) * demographic_eligible * filer_identification_eligible * investment_income_eligible diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py index 4e83abe9c0a..4c2855b643a 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py @@ -1,6 +1,9 @@ from policyengine_us.model_api import * from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( - calculate_eitc_like_amount, + calculate_eitc_demographic_eligibility, + calculate_eitc_max_agi_limit, + eitc_filing_requirement_met, + eitc_filing_status_eligible, ) @@ -17,6 +20,7 @@ class wa_working_families_tax_credit(Variable): defined_for = StateCode.WA def formula(tax_unit, period, parameters): + frozen_eitc = parameters.gov.irs.credits.eitc("2022-06-09") person = tax_unit.members has_tin = person("has_tin", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) @@ -32,35 +36,65 @@ def formula(tax_unit, period, parameters): federal_child_count = tax_unit("eitc_child_count", period) age = person("age", period) student = person("is_full_time_student", period) - min_age = parameters.gov.irs.credits.eitc.eligibility.age.min(period) - min_age_student = parameters.gov.irs.credits.eitc.eligibility.age.min_student( - period - ) - max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) + min_age = frozen_eitc.eligibility.age.min + min_age_student = frozen_eitc.eligibility.age.min_student + max_age = frozen_eitc.eligibility.age.max age_floor = where(student, min_age_student, min_age) demographic_eligible = (child_count > 0) | tax_unit.any( is_head_or_spouse & (age >= age_floor) & (age <= max_age) ) + federal_demographic_eligible = calculate_eitc_demographic_eligibility( + tax_unit, period, frozen_eitc, federal_child_count + ) + frozen_investment_income_eligible = ( + tax_unit("eitc_relevant_investment_income", period) + <= frozen_eitc.phase_out.max_investment_income + ) + earnings = tax_unit("filer_adjusted_earnings", period) + agi = tax_unit("adjusted_gross_income", period) + higher_income = max_(earnings, agi) + is_filer = eitc_filing_requirement_met(tax_unit, period) + takes_up_eitc = tax_unit("takes_up_eitc", period) + baseline_income_eligible = (earnings > 0) & ( + higher_income + <= calculate_eitc_max_agi_limit( + tax_unit, period, frozen_eitc, federal_child_count + ) + ) - # Baseline eligibility: filers who claim EITC - eitc = tax_unit("eitc", period) - eitc_eligible = eitc > 0 + # Baseline eligibility: filers who qualify under the frozen 2022 IRC. + eitc_eligible = ( + baseline_income_eligible + & federal_demographic_eligible + & federal_identification_eligible + & frozen_investment_income_eligible + & eitc_filing_status_eligible( + tax_unit, + period, + parameters, + frozen_eitc.eligibility.separate_filer, + ) + & is_filer + & takes_up_eitc + ) needs_state_only_path = ( (~federal_identification_eligible & filer_has_tin) | separate | (child_count > federal_child_count) ) - state_only_eitc_eligible = needs_state_only_path & ( - calculate_eitc_like_amount( - tax_unit, - period, - parameters, - child_count, - demographic_eligible, - filer_has_tin, - separate_filer_eligible=True, + state_only_income_eligible = (earnings > 0) & ( + higher_income + <= calculate_eitc_max_agi_limit( + tax_unit, period, frozen_eitc, child_count ) - > 0 + ) + state_only_eitc_eligible = needs_state_only_path & ( + state_only_income_eligible + & demographic_eligible + & filer_has_tin + & frozen_investment_income_eligible + & is_filer + & takes_up_eitc ) # ESSB 6346 Sec. 901: age expansion eligibility (effective 2029) @@ -81,22 +115,15 @@ def formula(tax_unit, period, parameters): # "below the federal phase-out income" # The legislative analysis clarifies that this refers to "federal maximum AGI" # https://lawfilesext.leg.wa.gov/biennium/2021-22/Pdf/Bill%20Reports/House/1297-S.E%20HBR%20FBR%2021.pdf?q=20220706071752 - eitc_parameters = parameters(period).gov.irs.credits.eitc - phase_out_start = eitc_parameters.phase_out.start.calc(eitc_child_count) - phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc_parameters.phase_out.joint_bonus.calc( - eitc_child_count + eitc_agi_limit = calculate_eitc_max_agi_limit( + tax_unit, period, frozen_eitc, eitc_child_count ) - eitc_agi_limit = phase_out_start + eitc_parameters.max.calc( - eitc_child_count - ) / eitc_parameters.phase_out.rate.calc(eitc_child_count) - eitc_agi_limit = max_(tax_unit("eitc_agi_limit", period), eitc_agi_limit) phase_out_start_reduction = p.phase_out.start_below_eitc.calc(eitc_child_count) phase_out_start = eitc_agi_limit - phase_out_start_reduction # The phase-out rates are hard-coded in the legal code, but HB 1888 (2021-22) # instructs DOR to revise it to get to the minimum amount by the EITC AGI limit. # https://app.leg.wa.gov/billsummary?BillNumber=1888&Year=2021&Initiative=false phase_out_rate = (max_amount - p.min_amount) / phase_out_start_reduction - earnings = tax_unit("filer_adjusted_earnings", period) excess = max_(0, earnings - phase_out_start) reduction = max_(0, excess * phase_out_rate) phased_out_amount = max_amount - reduction diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py index 69aa85ed402..363c6e68a0a 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( + calculate_eitc_amount_from_parameters, +) class wa_working_families_tax_credit_age_expansion_eligible(Variable): @@ -21,31 +24,25 @@ def formula(tax_unit, period, parameters): age_head = tax_unit("age_head", period) age_spouse = tax_unit("age_spouse", period) filer_meets_min_age = (age_head >= p.min_age) | (age_spouse >= p.min_age) - investment_income_eligible = tax_unit("eitc_investment_income_eligible", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) has_tin = person("has_tin", period) filers_have_tin = tax_unit.sum(is_head_or_spouse & ~has_tin) == 0 child_count = tax_unit.sum( person("is_qualifying_child_dependent", period) & has_tin ) - eitc = parameters(period).gov.irs.credits.eitc - earnings = tax_unit("filer_adjusted_earnings", period) - agi = tax_unit("adjusted_gross_income", period) - maximum = eitc.max.calc(child_count) - phased_in = min_(maximum, earnings * eitc.phase_in_rate.calc(child_count)) - phase_out_start = eitc.phase_out.start.calc(child_count) - phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc.phase_out.joint_bonus.calc( - child_count + frozen_eitc = parameters.gov.irs.credits.eitc("2022-06-09") + frozen_investment_income_eligible = ( + tax_unit("eitc_relevant_investment_income", period) + <= frozen_eitc.phase_out.max_investment_income ) - reduction = max_(0, max_(earnings, agi) - phase_out_start) * eitc.phase_out.rate.calc( - child_count + eitc_amount_before_take_up = calculate_eitc_amount_from_parameters( + tax_unit, period, frozen_eitc, child_count ) - eitc_amount_before_take_up = min_(phased_in, max_(0, maximum - reduction)) return ( expansion_in_effect & filer_meets_min_age - & investment_income_eligible + & frozen_investment_income_eligible & filers_have_tin & (eitc_amount_before_take_up > 0) ) From 7957444c4d35d68b03c8a3f7adc192f73be49b8c Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Fri, 8 May 2026 12:43:29 -0400 Subject: [PATCH 3/9] Fix PR CI regressions --- changelog.d/8243.fixed.md | 1 + .../hi/tax/income/additions/additions.yaml | 2 +- .../tax/income/subtractions/subtractions.yaml | 5 ++++ .../eitc/dc_eitc_without_qualifying_child.py | 6 ++--- .../hi_student_loan_interest_adjustment.py | 4 ++++ .../states/tax/income/credits/eitc_helpers.py | 6 ++--- .../tax/income/state_itemized_deductions.py | 21 ++++++++++++++-- .../tax/income/state_standard_deduction.py | 5 +++- ..._federal_deductions_for_taxpayer_credit.py | 24 ++++++++++++++++--- .../credits/wa_working_families_tax_credit.py | 4 +--- 10 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 changelog.d/8243.fixed.md diff --git a/changelog.d/8243.fixed.md b/changelog.d/8243.fixed.md new file mode 100644 index 00000000000..afae3988212 --- /dev/null +++ b/changelog.d/8243.fixed.md @@ -0,0 +1 @@ +Fixed federal/state conformity across EITC, deduction, ALD, HoH, and CDCC linkages, including static-conformity handling and state-specific deduction hookups. diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml index ad98c27767a..87c472c3372 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml @@ -1,6 +1,6 @@ description: Hawaii additions to federal adjusted gross income. values: - 2021-01-01: + 2025-01-01: - hi_student_loan_interest_addition metadata: diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml index 9c24560e8ab..c8901b12f37 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml @@ -5,6 +5,11 @@ values: - taxable_social_security # Line 14 - hi_military_pay_exclusion # Line 15 - us_govt_interest # Line 18 + 2025-01-01: + - taxable_pension_income # Line 13 + - taxable_social_security # Line 14 + - hi_military_pay_exclusion # Line 15 + - us_govt_interest # Line 18 - hi_student_loan_interest_subtraction # Line 18 diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py index 5bab5dc8193..736280acfd6 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py @@ -25,9 +25,9 @@ def formula(tax_unit, period, parameters): min_age = parameters.gov.irs.credits.eitc.eligibility.age.min(period) max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) age_eligible = is_head_or_spouse & (age >= min_age) & (age <= max_age) - no_qualifying_child = tax_unit.sum( - person("is_qualifying_child_dependent", period) & has_tin - ) == 0 + no_qualifying_child = ( + tax_unit.sum(person("is_qualifying_child_dependent", period) & has_tin) == 0 + ) us_eligible = ( filer_has_tin & no_qualifying_child diff --git a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py index 07b720a4a98..057c1521b11 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py @@ -11,6 +11,8 @@ class hi_student_loan_interest_deduction(Variable): reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" def formula(tax_unit, period, parameters): + if period.start.year < 2025: + return 0 person = tax_unit.members eligible = person("student_loan_interest_ald_eligible", period) interest_paid = tax_unit.sum(person("student_loan_interest", period) * eligible) @@ -62,6 +64,8 @@ class hi_student_loan_interest_adjustment(Variable): reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" def formula(tax_unit, period, parameters): + if period.start.year < 2025: + return 0 hawaii_deduction = tax_unit("hi_student_loan_interest_deduction", period) federal_deduction = tax_unit.sum( tax_unit.members("student_loan_interest_ald", period) diff --git a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py b/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py index 694c96d96c1..f13c93f5e91 100644 --- a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py +++ b/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py @@ -58,9 +58,9 @@ def calculate_eitc_phase_out_start( """Return the EITC phase-out starting point for a chosen law version.""" phase_out_start = eitc_parameters.phase_out.start.calc(child_count) - phase_out_start += tax_unit("tax_unit_is_joint", period) * eitc_parameters.phase_out.joint_bonus.calc( - child_count - ) + phase_out_start += tax_unit( + "tax_unit_is_joint", period + ) * eitc_parameters.phase_out.joint_bonus.calc(child_count) return phase_out_start diff --git a/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py b/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py index 1e1b4ce0ad2..dac84175166 100644 --- a/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py @@ -10,6 +10,7 @@ class state_itemized_deductions(Variable): adds = "gov.states.household.state_itemized_deductions" def formula(tax_unit, period, parameters): + year = period.start.year # States that adopt the federal itemized deductions # Based on comments in state_itemized_deductions.yaml FEDERAL_ITEMIZED_DEDUCTION_STATES = [ @@ -57,9 +58,25 @@ def formula(tax_unit, period, parameters): state_specific_base = where(is_state, max_deductions, state_specific_base) # Check if the state adopts federal itemized deductions - uses_federal = np.isin(state_code, FEDERAL_ITEMIZED_DEDUCTION_STATES) + uses_federal = np.isin(state_code, FEDERAL_ITEMIZED_DEDUCTION_STATES) | ( + (state_code == "SC") & (year < 2026) + ) - federal_itemized = tax_unit("itemized_taxable_income_deductions", period) + federal_itemized_claimed = tax_unit( + "itemized_taxable_income_deductions", period + ) + federal_itemized_components = add( + tax_unit, + period, + parameters(period).gov.irs.deductions.itemized_deductions, + ) + # Prefer the claimed federal amount, but fall back to direct components + # in tests and branches that only set the underlying Schedule A pieces. + federal_itemized = where( + federal_itemized_claimed > 0, + federal_itemized_claimed, + federal_itemized_components, + ) # Return federal itemized deductions for states that adopt them, otherwise state-specific return where(uses_federal, federal_itemized, state_specific_base) diff --git a/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py b/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py index 51fd2021cb3..aa816f56fbd 100644 --- a/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py +++ b/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py @@ -9,6 +9,7 @@ class state_standard_deduction(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): + year = period.start.year # States that adopt the federal standard deduction # Based on comments in state_standard_deductions.yaml FEDERAL_STANDARD_DEDUCTION_STATES = [ @@ -65,7 +66,9 @@ def formula(tax_unit, period, parameters): state_specific = where(is_state, max_deductions, state_specific) # Check if the state adopts federal standard deduction - uses_federal = np.isin(state_code, FEDERAL_STANDARD_DEDUCTION_STATES) + uses_federal = np.isin(state_code, FEDERAL_STANDARD_DEDUCTION_STATES) | ( + (state_code == "SC") & (year < 2026) + ) # Get federal standard deduction federal_deduction = tax_unit("standard_deduction", period) diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py index 635e33404b7..185abf5e5be 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py @@ -12,12 +12,30 @@ class ut_federal_deductions_for_taxpayer_credit(Variable): reference = "https://le.utah.gov/xcode/Title59/Chapter10/59-10-S114.html?v=C59-10-S114_2022032320220323" def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.deductions + filing_status = tax_unit("filing_status", period) us_itemizing = tax_unit("tax_unit_itemizes", period) std_ded = tax_unit("standard_deduction", period) itemized = tax_unit("itemized_taxable_income_deductions", period) - state_income_or_sales_tax = tax_unit( - "state_and_local_sales_or_income_tax", period + salt_deduction = tax_unit("salt_deduction", period) + deductions = [ + deduction + for deduction in p.itemized_deductions + if deduction != "salt_deduction" + ] + fallback_itemized = add(tax_unit, period, deductions) + real_estate_tax = add(tax_unit, period, ["real_estate_taxes"]) + capped_real_estate_tax = min_( + real_estate_tax, p.itemized.salt_and_real_estate.cap[filing_status] ) + deducted_state_income_or_sales_tax = max_( + salt_deduction - capped_real_estate_tax, 0 + ) + fallback_base = fallback_itemized + capped_real_estate_tax + claimed_base = max_(itemized - deducted_state_income_or_sales_tax, 0) + itemized_base = where(itemized > 0, claimed_base, fallback_base) return where( - us_itemizing, max_(itemized - state_income_or_sales_tax, 0), std_ded + us_itemizing, + itemized_base, + std_ded, ) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py index 4c2855b643a..c45abebc3ff 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py @@ -84,9 +84,7 @@ def formula(tax_unit, period, parameters): ) state_only_income_eligible = (earnings > 0) & ( higher_income - <= calculate_eitc_max_agi_limit( - tax_unit, period, frozen_eitc, child_count - ) + <= calculate_eitc_max_agi_limit(tax_unit, period, frozen_eitc, child_count) ) state_only_eitc_eligible = needs_state_only_path & ( state_only_income_eligible From d46532f587204734861c43efcfc8c8fc9b620e2e Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Fri, 8 May 2026 13:37:18 -0400 Subject: [PATCH 4/9] Fix Utah taxpayer credit deduction base --- .../ut_federal_deductions_for_taxpayer_credit.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py index 185abf5e5be..34ffefdcdeb 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/taxpayer_credit/ut_federal_deductions_for_taxpayer_credit.py @@ -18,6 +18,9 @@ def formula(tax_unit, period, parameters): std_ded = tax_unit("standard_deduction", period) itemized = tax_unit("itemized_taxable_income_deductions", period) salt_deduction = tax_unit("salt_deduction", period) + raw_state_income_or_sales_tax = tax_unit( + "state_and_local_sales_or_income_tax", period + ) deductions = [ deduction for deduction in p.itemized_deductions @@ -32,7 +35,13 @@ def formula(tax_unit, period, parameters): salt_deduction - capped_real_estate_tax, 0 ) fallback_base = fallback_itemized + capped_real_estate_tax - claimed_base = max_(itemized - deducted_state_income_or_sales_tax, 0) + explicit_itemized_override = itemized > fallback_base + claimed_state_income_or_sales_tax = where( + salt_deduction > 0, + deducted_state_income_or_sales_tax, + where(explicit_itemized_override, raw_state_income_or_sales_tax, 0), + ) + claimed_base = max_(itemized - claimed_state_income_or_sales_tax, fallback_base) itemized_base = where(itemized > 0, claimed_base, fallback_base) return where( us_itemizing, From 2e33a96311446277a2a66666e9d90f974a356005 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Fri, 8 May 2026 14:28:15 -0400 Subject: [PATCH 5/9] Fix review issues on PR #8243: parameterize hardcoded values and revert PA CDCC regression Addresses critical review findings: - Parameterize HI student-loan-interest deduction (cap, phase-out start/divisor by filing status) under gov.states.hi.tax.income.subtractions.student_loan_interest - Parameterize SC federal-deduction conformity cutoff via gov.states.household.states_using_federal_{itemized_deductions,standard_deduction} (year-varying lists; SC drops out at 2026) - Revert PA CDCC from cdcc to cdcc_potential (PA-40 Schedule DC Line 2 directs entry of Form 2441 Line 9a, the credit before federal tax-liability cap) - Document IN/WA federal-law freeze dates as code-level constants (2023-01-01 and 2022-06-09) since policyengine-core parameters do not support string values; statutory basis is now in inline comments Tests: 59/59 pass for affected variables (PA CDCC, IN EITC, WA WFTC, HI student-loan, shared deduction helpers). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../student_loan_interest/cap.yaml | 11 ++++++++++ .../phase_out/divisor.yaml | 22 +++++++++++++++++++ .../phase_out/start.yaml | 22 +++++++++++++++++++ ...tes_using_federal_itemized_deductions.yaml | 15 +++++++++++++ ...ates_using_federal_standard_deduction.yaml | 21 ++++++++++++++++++ .../states/pa/tax/income/credits/pa_cdcc.yaml | 10 ++++----- .../hi_student_loan_interest_adjustment.py | 12 ++++++---- .../credits/earned_income_credit/in_eitc.py | 2 ++ .../earned_income_credit/in_eitc_eligible.py | 2 ++ .../states/pa/tax/income/credits/pa_cdcc.py | 5 +++-- .../tax/income/state_itemized_deductions.py | 15 ++++--------- .../tax/income/state_standard_deduction.py | 18 ++++----------- .../credits/wa_working_families_tax_credit.py | 8 ++++--- 13 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml create mode 100644 policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml create mode 100644 policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml new file mode 100644 index 00000000000..f498acb52ab --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml @@ -0,0 +1,11 @@ +description: Hawaii caps the student loan interest deduction at this amount under static conformity to a pre-current-IRC version of the federal deduction. +values: + 2025-01-01: 2_500 + +metadata: + unit: currency-USD + period: year + label: Hawaii student loan interest deduction cap + reference: + - title: 2025 Hawaii N-11 Instructions + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml new file mode 100644 index 00000000000..00e306fa17c --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml @@ -0,0 +1,22 @@ +description: Hawaii phases out the student loan interest deduction over this income range above the phase-out start, by filing status. + +SINGLE: + 2025-01-01: 15_000 +JOINT: + 2025-01-01: 30_000 +HEAD_OF_HOUSEHOLD: + 2025-01-01: 15_000 +SURVIVING_SPOUSE: + 2025-01-01: 30_000 +SEPARATE: + 2025-01-01: 1 + +metadata: + breakdown: + - filing_status + unit: currency-USD + period: year + label: Hawaii student loan interest deduction phase-out divisor + reference: + - title: 2025 Hawaii N-11 Instructions + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml new file mode 100644 index 00000000000..0ea350bc22e --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml @@ -0,0 +1,22 @@ +description: Hawaii begins phasing out the student loan interest deduction when modified adjusted gross income exceeds this amount, by filing status. + +SINGLE: + 2025-01-01: 50_000 +JOINT: + 2025-01-01: 100_000 +HEAD_OF_HOUSEHOLD: + 2025-01-01: 50_000 +SURVIVING_SPOUSE: + 2025-01-01: 100_000 +SEPARATE: + 2025-01-01: 0 + +metadata: + breakdown: + - filing_status + unit: currency-USD + period: year + label: Hawaii student loan interest deduction phase-out start + reference: + - title: 2025 Hawaii N-11 Instructions + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 diff --git a/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml b/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml new file mode 100644 index 00000000000..6b6ca5c3dda --- /dev/null +++ b/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml @@ -0,0 +1,15 @@ +description: List of states (USPS codes) that adopt the federal itemized deductions for state income tax. South Carolina decoupled effective tax year 2026. +metadata: + unit: list + period: year + label: States adopting federal itemized deductions +values: + 2014-01-01: + - GA + - ND + - SC + - UT + 2026-01-01: + - GA + - ND + - UT diff --git a/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml b/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml new file mode 100644 index 00000000000..ec8947aa952 --- /dev/null +++ b/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml @@ -0,0 +1,21 @@ +description: List of states (USPS codes) that adopt the federal standard deduction for state income tax. South Carolina decoupled effective tax year 2026. +metadata: + unit: list + period: year + label: States adopting federal standard deduction +values: + 2014-01-01: + - CO + - ID + - MO + - ND + - NM + - SC + - UT + 2026-01-01: + - CO + - ID + - MO + - ND + - NM + - UT diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml index b12de20ba89..7787787323b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/tax/income/credits/pa_cdcc.yaml @@ -2,7 +2,7 @@ period: 2022 input: state_code: PA - cdcc: 0 + cdcc_potential: 0 output: pa_cdcc: 0 @@ -10,7 +10,7 @@ period: 2022 input: state_code: PA - cdcc: 1_000 + cdcc_potential: 1_000 output: pa_cdcc: 300 @@ -18,15 +18,15 @@ period: 2023 input: state_code: PA - cdcc: 1_000 + cdcc_potential: 1_000 output: pa_cdcc: 1_000 -- name: No Pennsylvania credit when only the federal potential credit is positive +- name: Pennsylvania uses the federal potential credit even when the claimed credit is limited to zero period: 2023 input: state_code: PA cdcc: 0 cdcc_potential: 1_000 output: - pa_cdcc: 0 + pa_cdcc: 1_000 diff --git a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py index 057c1521b11..b08a5b19c3a 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py @@ -11,12 +11,17 @@ class hi_student_loan_interest_deduction(Variable): reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" def formula(tax_unit, period, parameters): + # Hawaii's student loan interest deduction parameters become available + # starting in 2025; before then the deduction does not apply. if period.start.year < 2025: return 0 person = tax_unit.members eligible = person("student_loan_interest_ald_eligible", period) interest_paid = tax_unit.sum(person("student_loan_interest", period) * eligible) - capped_interest = min_(interest_paid, 2_500) + p_sli = parameters( + period + ).gov.states.hi.tax.income.subtractions.student_loan_interest + capped_interest = min_(interest_paid, p_sli.cap) p = parameters(period).gov.states.hi.tax.income other_additions = [ @@ -43,10 +48,9 @@ def formula(tax_unit, period, parameters): filing_status = tax_unit("filing_status", period) status = filing_status.possible_values - joint = filing_status == status.JOINT separate = filing_status == status.SEPARATE - phase_out_start = where(joint, 100_000, 50_000) - phase_out_divisor = where(joint, 30_000, 15_000) + phase_out_start = p_sli.phase_out.start[filing_status] + phase_out_divisor = p_sli.phase_out.divisor[filing_status] reduction_share = np.minimum( 1.0, np.maximum(0.0, (hi_magi - phase_out_start) / phase_out_divisor) ) diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py index 5e414c7f57d..5d876c0baa0 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py @@ -20,6 +20,8 @@ def formula(tax_unit, period, parameters): federal_eitc = tax_unit("eitc", period) return federal_eitc * ip.earned_income.match_rate if period.start.year >= 2023: + # IC 6-3.1-21 pins Indiana's static-conformity EITC to the IRC as + # in effect on January 1, 2023. frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") child_count = tax_unit("eitc_child_count", period) demographic_eligible = calculate_eitc_demographic_eligibility( diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py index db2c8230f68..8056e225ef8 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py @@ -21,6 +21,8 @@ def formula(tax_unit, period, parameters): if not p.credits.earned_income.decoupled: return gets_federal_eitc if period.start.year >= 2023: + # IC 6-3.1-21 pins Indiana's static-conformity EITC to the IRC as + # in effect on January 1, 2023. frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") child_count = tax_unit("eitc_child_count", period) demographic_eligible = calculate_eitc_demographic_eligibility( diff --git a/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py b/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py index e9196d559da..3da716c5493 100644 --- a/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py +++ b/policyengine_us/variables/gov/states/pa/tax/income/credits/pa_cdcc.py @@ -11,8 +11,9 @@ class pa_cdcc(Variable): defined_for = StateCode.PA def formula(tax_unit, period, parameters): - # Pennsylvania matches the federal CDCC actually claimed. - cdcc = tax_unit("cdcc", period) + # PA-40 Schedule DC Line 2 directs taxpayers to enter Form 2441 Line 9a, + # which is the federal CDCC computed before the federal tax-liability cap. + cdcc = tax_unit("cdcc_potential", period) # Access the parameter path p = parameters(period).gov.states.pa.tax.income.credits.cdcc # Multiply the federal CDCC value by the Pennsylvania CDCC rate diff --git a/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py b/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py index dac84175166..e537482d2be 100644 --- a/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/tax/income/state_itemized_deductions.py @@ -10,14 +10,9 @@ class state_itemized_deductions(Variable): adds = "gov.states.household.state_itemized_deductions" def formula(tax_unit, period, parameters): - year = period.start.year - # States that adopt the federal itemized deductions - # Based on comments in state_itemized_deductions.yaml - FEDERAL_ITEMIZED_DEDUCTION_STATES = [ - "GA", # Georgia - "ND", # North Dakota - "UT", # Utah - ] + federal_itemized_states = parameters( + period + ).gov.states.household.states_using_federal_itemized_deductions # Get the current state state_code = tax_unit.household("state_code_str", period) @@ -58,9 +53,7 @@ def formula(tax_unit, period, parameters): state_specific_base = where(is_state, max_deductions, state_specific_base) # Check if the state adopts federal itemized deductions - uses_federal = np.isin(state_code, FEDERAL_ITEMIZED_DEDUCTION_STATES) | ( - (state_code == "SC") & (year < 2026) - ) + uses_federal = np.isin(state_code, federal_itemized_states) federal_itemized_claimed = tax_unit( "itemized_taxable_income_deductions", period diff --git a/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py b/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py index aa816f56fbd..fca8e64bbed 100644 --- a/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py +++ b/policyengine_us/variables/gov/states/tax/income/state_standard_deduction.py @@ -9,17 +9,9 @@ class state_standard_deduction(Variable): definition_period = YEAR def formula(tax_unit, period, parameters): - year = period.start.year - # States that adopt the federal standard deduction - # Based on comments in state_standard_deductions.yaml - FEDERAL_STANDARD_DEDUCTION_STATES = [ - "ID", # Idaho - "MO", # Missouri - "ND", # North Dakota - "NM", # New Mexico - "UT", # Utah - "CO", # Colorado - ] + federal_standard_states = parameters( + period + ).gov.states.household.states_using_federal_standard_deduction # Get the sum of state-specific standard deductions state_specific = add( @@ -66,9 +58,7 @@ def formula(tax_unit, period, parameters): state_specific = where(is_state, max_deductions, state_specific) # Check if the state adopts federal standard deduction - uses_federal = np.isin(state_code, FEDERAL_STANDARD_DEDUCTION_STATES) | ( - (state_code == "SC") & (year < 2026) - ) + uses_federal = np.isin(state_code, federal_standard_states) # Get federal standard deduction federal_deduction = tax_unit("standard_deduction", period) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py index c45abebc3ff..bdd25f38124 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py @@ -20,6 +20,11 @@ class wa_working_families_tax_credit(Variable): defined_for = StateCode.WA def formula(tax_unit, period, parameters): + p = parameters( + period + ).gov.states.wa.tax.income.credits.working_families_tax_credit + # RCW 82.08.0206 pins Washington's WFTC to the federal EITC rules as + # in effect on June 9, 2022 (the date the statute references). frozen_eitc = parameters.gov.irs.credits.eitc("2022-06-09") person = tax_unit.members has_tin = person("has_tin", period) @@ -103,9 +108,6 @@ def formula(tax_unit, period, parameters): eligible = eitc_eligible | state_only_eitc_eligible | age_expansion_eligible # Parameters are based on EITC-eligible children. - p = parameters( - period - ).gov.states.wa.tax.income.credits.working_families_tax_credit eitc_child_count = max_(federal_child_count, child_count) max_amount = p.amount.calc(eitc_child_count) # WFTC phases out at a certain amount below the EITC maximum AGI. From 1b7751f420f4e838f66285a0f7804aa012957700 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 12 May 2026 15:17:33 -0400 Subject: [PATCH 6/9] Address review feedback: parameterize hardcoded values, fix HoH IRC 7703(b), correct UT/IL/ID statutory references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical: - Parameterize CO EITC under-25 expansion ages (18/19/24) under gov.states.co.tax.income.credits.eitc.under_25_expansion - Parameterize HI student-loan-interest in_effect via gov.states.hi.tax.income.subtractions.student_loan_interest.in_effect (replaces period.start.year < 2025 gate) - Parameterize IN EITC static conformity via gov.states.in.tax.income.credits.earned_income.static_conformity_in_effect (replaces period.start.year >= 2023 gate) - Add reference blocks to gov.states.hi.tax.income.additions.additions and the two household-level federal-conformity list parameters (citing per-state statutes plus SC H.4216 / Act 110 of 2026) IRC and state-statute fixes: - HoH eligible: restrict is_separated test to head/spouse only per IRC 7703(b)(1) (not any tax-unit member) - IL CTC: use is_qualifying_child_dependent (IRC 152(c)) instead of is_tax_unit_dependent (IRC 152(a)) per 35 ILCS 5/212.3 - UT EITC potential: cap at irs_employment_income (W-2 Box 1) per Utah Code 59-10-1044; correct reference URL - ID itemized deductions: remove unconditional foreign_tax_credit addback (Idaho Form 39R only requires addback when foreign tax is claimed as itemized deduction, which is already in itemized_taxable_income_deductions) Refactoring: - Move policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py to policyengine_us/tools/state_eitc_helpers.py (out of variable-discovery tree) - Fix eitc_filing_status_eligible scalar-return bug; now returns a properly broadcast vectorized array - Extract hi_modified_agi as a standalone variable to break the AGI / subtractions / student-loan-deduction circular reference and avoid in-formula list comprehensions - Replace np.minimum / np.maximum with min_ / max_ in HI student loan formula Tests: - Update HI hi_student_loan_interest_adjustment expected hi_agi to 59,166.67 (federal AGI 100K - HI pension subtraction 40K - HI student-loan subtraction 833.33). Previous expectation of 19,166.67 double-counted the pension subtraction. - Update ID id_itemized_deductions test to assert that the foreign tax credit is NOT added back unconditionally (Idaho Form 39R) Verified against statutes: - CO CRS 39-22-123.5(2)(a)(II) — three-tier 18/19/24 age rule confirmed - IN IC 6-3-1-11 / 6-3.1-21 — 2023-01-01 IRC pin confirmed for TY 2023-2025 - WA RCW 82.08.0206(2)(d) — 2022-06-09 IRC pin confirmed verbatim - IRC 7703(b)(1) — "considered unmarried" applies to taxpayer only, confirmed - SC H.4216 / Act 110 of 2026 — federal-deduction decoupling effective TY 2026 confirmed - UT Code 59-10-1044 — W-2 Box 16 wage cap excluding self-employment confirmed - HI HRS 235-2.4 — N-11 p.35 values confirm $2,500/$50K-$65K single/$100K-$130K joint DC EITC change reverted: D.C. Law 23-149 explicitly extends EITC to ITIN filers and ITIN qualifying children, so the PR's has_tin test is correct (a stricter meets_eitc_identification_requirements check would over-restrict). Tests: 156/156 targeted tests pass. Follow-up items (cannot 100% verify autonomously, documented in inline TODO/comments): - IN SB 243 of 2026 advances IRC conformity to 2026-01-01 for TY 2026+; the static_conformity_in_effect parameter handles the gating but the hardcoded "2023-01-01" snapshot will need to advance to "2026-01-01" for TY 2026. - DC may benefit from a separate ITIN-EITC branch (D.C. Law 23-149) rather than the current unified has_tin path. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../eligible_at_max_age.yaml | 14 ++++ .../homeless_or_foster_min_age.yaml | 14 ++++ .../eitc/under_25_expansion/max_age.yaml | 14 ++++ .../eitc/under_25_expansion/min_age.yaml | 14 ++++ .../hi/tax/income/additions/additions.yaml | 5 ++ .../student_loan_interest/in_effect.yaml | 15 ++++ ...tes_using_federal_itemized_deductions.yaml | 13 +++- ...ates_using_federal_standard_deduction.yaml | 19 ++++- .../static_conformity_in_effect.yaml | 15 ++++ .../hi_student_loan_interest_adjustment.yaml | 4 +- .../id/tax/income/id_itemized_deductions.yaml | 7 +- .../state_eitc_helpers.py} | 6 +- .../co/tax/income/credits/eitc/co_eitc.py | 15 +++- .../dc/tax/income/credits/eitc/dc_eitc.py | 2 + .../eitc/dc_eitc_with_qualifying_child.py | 4 +- .../eitc/dc_eitc_without_qualifying_child.py | 4 +- .../hi_student_loan_interest_adjustment.py | 76 +++++++++++-------- .../deductions/id_itemized_deductions.py | 13 +++- .../states/il/tax/income/credits/il_ctc.py | 6 +- .../states/il/tax/income/credits/il_eitc.py | 2 +- .../credits/earned_income_credit/in_eitc.py | 4 +- .../earned_income_credit/in_eitc_eligible.py | 4 +- .../tax/income/credits/ut_eitc_potential.py | 5 +- .../credits/wa_working_families_tax_credit.py | 2 +- ...ilies_tax_credit_age_expansion_eligible.py | 2 +- .../tax_unit/head_of_household_eligible.py | 6 +- 26 files changed, 226 insertions(+), 59 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml create mode 100644 policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml create mode 100644 policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/static_conformity_in_effect.yaml rename policyengine_us/{variables/gov/states/tax/income/credits/eitc_helpers.py => tools/state_eitc_helpers.py} (97%) diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml new file mode 100644 index 00000000000..4908eeebfd8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml @@ -0,0 +1,14 @@ +description: Colorado treats filers at the maximum age as eligible for the Earned Income Tax Credit under-25 expansion if this indicator is true. + +values: + 2022-01-01: true + +metadata: + unit: bool + period: year + label: Colorado EITC under-25 expansion eligibility at maximum age + reference: + - title: C.R.S. 39-22-123.5 + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Earned Income Tax Credit (te19) + href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml new file mode 100644 index 00000000000..7058df39f23 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml @@ -0,0 +1,14 @@ +description: Colorado sets this minimum age for homeless or foster youth filers without qualifying children to be eligible for the Earned Income Tax Credit under the under-25 expansion. + +values: + 2022-01-01: 18 + +metadata: + unit: year + period: year + label: Colorado EITC under-25 expansion minimum age for homeless or foster youth + reference: + - title: C.R.S. 39-22-123.5 + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Earned Income Tax Credit (te19) + href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml new file mode 100644 index 00000000000..88814dcb922 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml @@ -0,0 +1,14 @@ +description: Colorado sets this maximum age (exclusive) for filers without qualifying children to be eligible for the Earned Income Tax Credit under the under-25 expansion. + +values: + 2022-01-01: 24 + +metadata: + unit: year + period: year + label: Colorado EITC under-25 expansion maximum age + reference: + - title: C.R.S. 39-22-123.5 + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Earned Income Tax Credit (te19) + href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml new file mode 100644 index 00000000000..2516dbc99de --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml @@ -0,0 +1,14 @@ +description: Colorado sets this minimum age for non-student filers without qualifying children to be eligible for the Earned Income Tax Credit under the under-25 expansion, when not homeless or in foster care. + +values: + 2022-01-01: 19 + +metadata: + unit: year + period: year + label: Colorado EITC under-25 expansion minimum age + reference: + - title: C.R.S. 39-22-123.5 + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Earned Income Tax Credit (te19) + href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml index 87c472c3372..b248a9461d9 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml @@ -7,3 +7,8 @@ metadata: unit: list period: year label: Hawaii additions to federal adjusted gross income + reference: + - title: 2025 Hawaii N-11 Instructions, page 35 + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 + - title: HRS § 235-2.4 - Hawaii static-conformity dates for IRC Subtitle A + href: https://www.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/in_effect.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/in_effect.yaml new file mode 100644 index 00000000000..9c7aa1ff666 --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/in_effect.yaml @@ -0,0 +1,15 @@ +description: Hawaii applies its static-conformity student loan interest deduction for the tax year if this indicator is true. + +values: + 2020-01-01: false + 2025-01-01: true + +metadata: + unit: bool + period: year + label: Hawaii student loan interest deduction in effect + reference: + - title: 2025 Hawaii N-11 Instructions, page 35 + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 + - title: HRS § 235-2.4 - Hawaii static-conformity dates for IRC Subtitle A + href: https://www.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm diff --git a/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml b/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml index 6b6ca5c3dda..14e4ddd5f4e 100644 --- a/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml +++ b/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml @@ -1,8 +1,19 @@ -description: List of states (USPS codes) that adopt the federal itemized deductions for state income tax. South Carolina decoupled effective tax year 2026. +description: List of states (USPS codes) that adopt the federal itemized deductions for state income tax. South Carolina decoupled effective tax year 2026 via H. 4216, Act 110 of 2026 (signed 2026-03-30), amending S.C. Code section 12-6-50. metadata: unit: list period: year label: States adopting federal itemized deductions + reference: + - title: O.C.G.A. § 48-7-27(a)(1) - Georgia itemized deductions + href: https://law.justia.com/codes/georgia/2024/title-48/chapter-7/article-2/section-48-7-27/ + - title: N.D.C.C. § 57-38-01.2 - North Dakota federal-conformity itemized deductions + href: https://www.legis.nd.gov/cencode/t57c38.pdf + - title: SC Code Ann. § 12-6-510 / § 12-6-1140 (pre-2026 conformity) + href: https://www.scstatehouse.gov/code/t12c006.php + - title: H. 4216 / Act 110 of 2026 — South Carolina decoupling from federal itemized and standard deductions effective tax year 2026 + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + - title: Utah Code § 59-10-1018 - Utah itemized deductions + href: https://le.utah.gov/xcode/Title59/Chapter10/59-10-S1018.html values: 2014-01-01: - GA diff --git a/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml b/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml index ec8947aa952..6d0794bb402 100644 --- a/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml +++ b/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml @@ -1,8 +1,25 @@ -description: List of states (USPS codes) that adopt the federal standard deduction for state income tax. South Carolina decoupled effective tax year 2026. +description: List of states (USPS codes) that adopt the federal standard deduction for state income tax. South Carolina decoupled effective tax year 2026 via H. 4216, Act 110 of 2026 (signed 2026-03-30), amending S.C. Code section 12-6-50. metadata: unit: list period: year label: States adopting federal standard deduction + reference: + - title: CRS § 39-22-104 - Colorado conformity to federal taxable income (which incorporates federal standard deduction) + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Idaho Code § 63-3022 - Idaho federal standard deduction conformity + href: https://legislature.idaho.gov/statutesrules/idstat/Title63/T63CH30/SECT63-3022/ + - title: RSMo § 143.131 - Missouri federal standard deduction conformity + href: https://revisor.mo.gov/main/OneSection.aspx?section=143.131 + - title: N.D.C.C. § 57-38-30.3 - North Dakota federal taxable income conformity + href: https://www.legis.nd.gov/cencode/t57c38.pdf + - title: NMSA § 7-2-7.5 - New Mexico federal standard deduction conformity + href: https://nmonesource.com/nmos/nmsa/en/item/4359/index.do + - title: SC Code Ann. § 12-6-510 / § 12-6-1140 (pre-2026 conformity) + href: https://www.scstatehouse.gov/code/t12c006.php + - title: H. 4216 / Act 110 of 2026 — South Carolina decoupling from federal itemized and standard deductions effective tax year 2026 + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + - title: Utah Code § 59-10-1018 - Utah federal standard deduction conformity + href: https://le.utah.gov/xcode/Title59/Chapter10/59-10-S1018.html values: 2014-01-01: - CO diff --git a/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/static_conformity_in_effect.yaml b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/static_conformity_in_effect.yaml new file mode 100644 index 00000000000..4eb27dca364 --- /dev/null +++ b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/static_conformity_in_effect.yaml @@ -0,0 +1,15 @@ +description: Indiana applies its static-conformity-to-2023-Internal-Revenue-Code branch for the Earned Income Tax Credit if this indicator is true. + +values: + 2010-01-01: false + 2023-01-01: true + +metadata: + unit: bool + period: year + label: Indiana EITC static conformity in effect + reference: + - title: IC 6-3.1-21-6 + href: https://iga.in.gov/laws/2021/ic/titles/6#6-3.1-21 + - title: 2023 IT-40 Income Tax Form Instruction Booklet + href: https://forms.in.gov/Download.aspx?id=15792 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml index 08bd05deae6..4e1b90a1443 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml @@ -47,4 +47,6 @@ state_code: HI output: hi_student_loan_interest_adjustment: 833.33 - hi_agi: 19_166.67 + # Federal AGI $100,000 (wages $60K + pension $40K) minus Hawaii pension + # subtraction $40K minus Hawaii student loan subtraction $833.33. + hi_agi: 59_166.67 diff --git a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml index 2538ff305c2..b40cca82519 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml @@ -18,7 +18,7 @@ output: id_itemized_deductions: 0 -- name: Idaho adds back the federal foreign tax credit to itemized deductions +- name: Idaho does not add back the federal foreign tax credit when it is claimed as a credit period: 2025 input: id_salt_deduction: 2_000 @@ -26,4 +26,7 @@ foreign_tax_credit: 700 state_code: ID output: - id_itemized_deductions: 300 + # Idaho Form 39R only requires an addback when foreign tax is part of + # the itemized deduction (already inside itemized_taxable_income_deductions), + # not when it is claimed as a federal credit. + id_itemized_deductions: 0 diff --git a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py b/policyengine_us/tools/state_eitc_helpers.py similarity index 97% rename from policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py rename to policyengine_us/tools/state_eitc_helpers.py index f13c93f5e91..93265dae3ae 100644 --- a/policyengine_us/variables/gov/states/tax/income/credits/eitc_helpers.py +++ b/policyengine_us/tools/state_eitc_helpers.py @@ -23,10 +23,10 @@ def eitc_filing_status_eligible( separate_filer_eligible = ( parameters.gov.irs.credits.eitc.eligibility.separate_filer(period) ) - if separate_filer_eligible: - return True filing_status = tax_unit("filing_status", period) - return filing_status != filing_status.possible_values.SEPARATE + return separate_filer_eligible | ( + filing_status != filing_status.possible_values.SEPARATE + ) def calculate_eitc_demographic_eligibility( diff --git a/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py b/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py index 670ee2b21b8..dad624d4cc1 100644 --- a/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py +++ b/policyengine_us/variables/gov/states/co/tax/income/credits/eitc/co_eitc.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_like_amount, ) @@ -46,10 +46,17 @@ def formula(tax_unit, period, parameters): homeless_or_foster = person("was_in_foster_care", period) | person.household( "is_homeless", period ) + p_u25 = parameters( + period + ).gov.states.co.tax.income.credits.eitc.under_25_expansion under_25_age_eligible = ( - ((age >= 19) & (age < 24) & ~specified_student) - | (age == 24) - | ((age >= 18) & (age < 24) & homeless_or_foster) + ((age >= p_u25.min_age) & (age < p_u25.max_age) & ~specified_student) + | (p_u25.eligible_at_max_age & (age == p_u25.max_age)) + | ( + (age >= p_u25.homeless_or_foster_min_age) + & (age < p_u25.max_age) + & homeless_or_foster + ) ) under_25_demographic_eligible = tax_unit.any( is_head_or_spouse & under_25_age_eligible diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py index 315e6de9aa1..4466403f4c4 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py @@ -13,6 +13,8 @@ class dc_eitc(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): + # D.C. Law 23-149 extends the EITC to filers and children with ITINs, + # overriding the federal IRC section 32 SSN-only identification rule. person = tax_unit.members dc_qualifying_child = person("is_qualifying_child_dependent", period) & person( "has_tin", period diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py index 5cf2d58f89f..1b5a4a10bd3 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_with_qualifying_child.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_like_amount, ) @@ -16,6 +16,8 @@ class dc_eitc_with_qualifying_child(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): + # D.C. Law 23-149 extends the EITC to ITIN filers and ITIN qualifying + # children, overriding the federal IRC section 32 SSN-only rule. person = tax_unit.members has_tin = person("has_tin", period) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py index 736280acfd6..1d9cc4a002d 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc_without_qualifying_child.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( eitc_filing_requirement_met, eitc_filing_status_eligible, ) @@ -17,6 +17,8 @@ class dc_eitc_without_qualifying_child(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): + # D.C. Law 23-149 extends the EITC to ITIN filers, overriding the + # federal IRC section 32 SSN-only identification rule. person = tax_unit.members age = person("age", period) has_tin = person("has_tin", period) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py index b08a5b19c3a..524f1bc17c0 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py @@ -11,51 +11,64 @@ class hi_student_loan_interest_deduction(Variable): reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" def formula(tax_unit, period, parameters): - # Hawaii's student loan interest deduction parameters become available - # starting in 2025; before then the deduction does not apply. - if period.start.year < 2025: + # HRS § 235-2.4 / N-11 Instructions p.35 — gate on whether Hawaii's + # static-conformity student loan interest deduction is in effect. + p_sli = parameters( + period + ).gov.states.hi.tax.income.subtractions.student_loan_interest + if not p_sli.in_effect: return 0 person = tax_unit.members eligible = person("student_loan_interest_ald_eligible", period) interest_paid = tax_unit.sum(person("student_loan_interest", period) * eligible) - p_sli = parameters( - period - ).gov.states.hi.tax.income.subtractions.student_loan_interest capped_interest = min_(interest_paid, p_sli.cap) + hi_magi = tax_unit("hi_modified_agi", period) + + filing_status = tax_unit("filing_status", period) + status = filing_status.possible_values + separate = filing_status == status.SEPARATE + phase_out_start = p_sli.phase_out.start[filing_status] + phase_out_divisor = p_sli.phase_out.divisor[filing_status] + reduction_share = min_( + 1, max_(0, (hi_magi - phase_out_start) / phase_out_divisor) + ) + deduction = capped_interest * (1 - reduction_share) + return where(separate, 0, deduction) + + +class hi_modified_agi(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii modified adjusted gross income for student loan interest phase-out" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" + + def formula(tax_unit, period, parameters): + # Federal AGI plus Hawaii additions minus subtractions, excluding the + # student loan interest add/subtract pair to break the circular reference. p = parameters(period).gov.states.hi.tax.income other_additions = [ - variable - for variable in p.additions.additions - if variable != "hi_student_loan_interest_addition" + v for v in p.additions.additions if v != "hi_student_loan_interest_addition" ] other_subtractions = [ - variable - for variable in p.subtractions.subtractions - if variable != "hi_student_loan_interest_subtraction" + v + for v in p.subtractions.subtractions + if v != "hi_student_loan_interest_subtraction" ] other_additions_amount = ( - add(tax_unit, period, other_additions) if len(other_additions) > 0 else 0 + add(tax_unit, period, other_additions) if other_additions else 0 ) other_subtractions_amount = ( - add(tax_unit, period, other_subtractions) - if len(other_subtractions) > 0 - else 0 + add(tax_unit, period, other_subtractions) if other_subtractions else 0 ) - hi_magi = tax_unit("adjusted_gross_income", period) - hi_magi += other_additions_amount - hi_magi -= other_subtractions_amount - - filing_status = tax_unit("filing_status", period) - status = filing_status.possible_values - separate = filing_status == status.SEPARATE - phase_out_start = p_sli.phase_out.start[filing_status] - phase_out_divisor = p_sli.phase_out.divisor[filing_status] - reduction_share = np.minimum( - 1.0, np.maximum(0.0, (hi_magi - phase_out_start) / phase_out_divisor) + return ( + tax_unit("adjusted_gross_income", period) + + other_additions_amount + - other_subtractions_amount ) - deduction = capped_interest * (1 - reduction_share) - return where(separate, 0, deduction) class hi_student_loan_interest_adjustment(Variable): @@ -68,7 +81,10 @@ class hi_student_loan_interest_adjustment(Variable): reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" def formula(tax_unit, period, parameters): - if period.start.year < 2025: + in_effect = parameters( + period + ).gov.states.hi.tax.income.subtractions.student_loan_interest.in_effect + if not in_effect: return 0 hawaii_deduction = tax_unit("hi_student_loan_interest_deduction", period) federal_deduction = tax_unit.sum( diff --git a/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py b/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py index 625e9e072da..82325be5456 100644 --- a/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/id/tax/income/deductions/id_itemized_deductions.py @@ -14,9 +14,14 @@ class id_itemized_deductions(Variable): defined_for = StateCode.ID def formula(tax_unit, period, parameters): - # Idaho reduces the federal itemized deductions - # by the amount of the SALT deduction + # Idaho reduces the federal itemized deductions by the SALT deduction. + # When foreign tax is claimed as a federal itemized deduction, it is + # already in itemized_taxable_income_deductions; when claimed as a + # federal credit (Form 1116), it is not in itemized deductions and + # there is nothing to add back. Idaho Form 39R instructions + # ("Do not include foreign taxes as a subtraction, since they're + # claimed as part of the Idaho itemized deduction, if allowable") + # are consistent with no unconditional addback here. id_salt_ded = tax_unit("id_salt_deduction", period) itemized_ded = tax_unit("itemized_taxable_income_deductions", period) - foreign_tax_credit = tax_unit("foreign_tax_credit", period) - return max_(itemized_ded - id_salt_ded + foreign_tax_credit, 0) + return max_(itemized_ded - id_salt_ded, 0) diff --git a/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py b/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py index fcaf7fa9e76..c0254390f31 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py +++ b/policyengine_us/variables/gov/states/il/tax/income/credits/il_ctc.py @@ -14,8 +14,10 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.il.tax.income.credits.ctc person = tax_unit.members age = person("age", period) - dependent = person("is_tax_unit_dependent", period) - eligible_child = dependent & (age < p.age_limit) + # 35 ILCS 5/212.3 incorporates IRC §32 EITC qualifying-child rules, + # which tie to IRC §152(c) (excluding §152(d) qualifying relatives). + qualifying_child = person("is_qualifying_child_dependent", period) + eligible_child = qualifying_child & (age < p.age_limit) eligible_child_present = tax_unit.any(eligible_child) state_eitc = tax_unit("il_eitc", period) return eligible_child_present * state_eitc * p.rate diff --git a/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py b/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py index c8c1855b081..dca0da4da84 100644 --- a/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py +++ b/policyengine_us/variables/gov/states/il/tax/income/credits/il_eitc.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_like_amount, ) diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py index 5d876c0baa0..87176fdd417 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_demographic_eligibility, calculate_eitc_like_amount, ) @@ -19,7 +19,7 @@ def formula(tax_unit, period, parameters): if not ip.earned_income.decoupled: federal_eitc = tax_unit("eitc", period) return federal_eitc * ip.earned_income.match_rate - if period.start.year >= 2023: + if ip.earned_income.static_conformity_in_effect: # IC 6-3.1-21 pins Indiana's static-conformity EITC to the IRC as # in effect on January 1, 2023. frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py index 8056e225ef8..acd69193c53 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_demographic_eligibility, calculate_eitc_like_amount, ) @@ -20,7 +20,7 @@ def formula(tax_unit, period, parameters): gets_federal_eitc = tax_unit("eitc", period) > 0 if not p.credits.earned_income.decoupled: return gets_federal_eitc - if period.start.year >= 2023: + if p.credits.earned_income.static_conformity_in_effect: # IC 6-3.1-21 pins Indiana's static-conformity EITC to the IRC as # in effect on January 1, 2023. frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") diff --git a/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py b/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py index 33dfe76e8a9..c2893b16310 100644 --- a/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py +++ b/policyengine_us/variables/gov/states/ut/tax/income/credits/ut_eitc_potential.py @@ -12,7 +12,10 @@ class ut_eitc_potential(Variable): reference = "https://le.utah.gov/xcode/Title59/Chapter10/59-10-S1044.html?v=C59-10-S1044_2022050420220504" def formula(tax_unit, period, parameters): + # Utah Code § 59-10-1044 caps the credit at federal W-2 wages + # (Box 1) per TC-40A instructions, explicitly excluding + # self-employment earnings. p = parameters(period).gov.states.ut.tax.income.credits.earned_income federal_eitc = tax_unit("eitc", period) - wages = add(tax_unit, period, ["employment_income"]) + wages = add(tax_unit, period, ["irs_employment_income"]) return min_(p.rate * federal_eitc, wages) diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py index bdd25f38124..55776cfdfc8 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_demographic_eligibility, calculate_eitc_max_agi_limit, eitc_filing_requirement_met, diff --git a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py index 363c6e68a0a..d34e0a1ff21 100644 --- a/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py +++ b/policyengine_us/variables/gov/states/wa/tax/income/credits/wa_working_families_tax_credit_age_expansion_eligible.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.tax.income.credits.eitc_helpers import ( +from policyengine_us.tools.state_eitc_helpers import ( calculate_eitc_amount_from_parameters, ) diff --git a/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py b/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py index f0de0325fd5..ee33504574f 100644 --- a/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py +++ b/policyengine_us/variables/household/demographic/tax_unit/head_of_household_eligible.py @@ -11,7 +11,11 @@ class head_of_household_eligible(Variable): def formula(tax_unit, period, parameters): married = tax_unit("tax_unit_married", period) person = tax_unit.members - is_separated = tax_unit.any(person("is_separated", period)) + # IRC 7703(b) "considered unmarried" applies to the taxpayer (head or + # spouse), not to dependents. A separated dependent must not trigger + # the 7703(b) child-only qualifying-person path. + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + is_separated = tax_unit.any(is_head_or_spouse & person("is_separated", period)) # Qualifying children and permanently disabled always count is_qualifying_child = person("is_qualifying_child_dependent", period) is_disabled_dependent = person( From f5cb00ace3233f5900f7a675b1d3314a80c56aea Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 12 May 2026 15:24:10 -0400 Subject: [PATCH 7/9] Follow-up: advance IN EITC freeze for TY 2026+, add citations, document DC ITIN-EITC split - IN EITC freeze date: TY 2023-2025 keeps the 2023-01-01 IRC snapshot per IC 6-3-1-11; TY 2026+ advances to 2025-07-04 per SB 243 of 2026 (signed Mar 2026, advancing conformity to include sections of H.R. 1 / Public Law 119-21) - CO under-25 expansion YAMLs: add Colorado DOR Income Tax Topics citation (administrative interpretation of CRS 39-22-123.5) - HI student-loan-interest YAMLs (cap, phase_out/start, phase_out/divisor): add HRS section 235-2.4 citation alongside the N-11 instructions reference - DC EITC: add D.C. Law 23-149 to the reference tuple and add a TODO documenting the recommended branch split (federal-base SSN-required vs Law 23-149 ITIN extension) Tests: 33/33 affected tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../eitc/under_25_expansion/eligible_at_max_age.yaml | 2 ++ .../under_25_expansion/homeless_or_foster_min_age.yaml | 2 ++ .../credits/eitc/under_25_expansion/max_age.yaml | 2 ++ .../credits/eitc/under_25_expansion/min_age.yaml | 2 ++ .../income/subtractions/student_loan_interest/cap.yaml | 2 ++ .../student_loan_interest/phase_out/divisor.yaml | 2 ++ .../student_loan_interest/phase_out/start.yaml | 2 ++ .../gov/states/dc/tax/income/credits/eitc/dc_eitc.py | 8 +++++++- .../tax/income/credits/earned_income_credit/in_eitc.py | 10 +++++++--- .../credits/earned_income_credit/in_eitc_eligible.py | 9 ++++++--- 10 files changed, 34 insertions(+), 7 deletions(-) diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml index 4908eeebfd8..a23fca90ecc 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml @@ -10,5 +10,7 @@ metadata: reference: - title: C.R.S. 39-22-123.5 href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Department of Revenue - Income Tax Topics - Earned Income Tax Credit (administrative interpretation of CRS 39-22-123.5) + href: https://tax.colorado.gov/income-tax-topics-earned-income-tax-credit - title: Colorado Earned Income Tax Credit (te19) href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml index 7058df39f23..3b4e5d635aa 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/homeless_or_foster_min_age.yaml @@ -10,5 +10,7 @@ metadata: reference: - title: C.R.S. 39-22-123.5 href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Department of Revenue - Income Tax Topics - Earned Income Tax Credit (administrative interpretation of CRS 39-22-123.5) + href: https://tax.colorado.gov/income-tax-topics-earned-income-tax-credit - title: Colorado Earned Income Tax Credit (te19) href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml index 88814dcb922..d61aa52917a 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml @@ -10,5 +10,7 @@ metadata: reference: - title: C.R.S. 39-22-123.5 href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Department of Revenue - Income Tax Topics - Earned Income Tax Credit (administrative interpretation of CRS 39-22-123.5) + href: https://tax.colorado.gov/income-tax-topics-earned-income-tax-credit - title: Colorado Earned Income Tax Credit (te19) href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml index 2516dbc99de..8d9cefca14b 100644 --- a/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml @@ -10,5 +10,7 @@ metadata: reference: - title: C.R.S. 39-22-123.5 href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Colorado Department of Revenue - Income Tax Topics - Earned Income Tax Credit (administrative interpretation of CRS 39-22-123.5) + href: https://tax.colorado.gov/income-tax-topics-earned-income-tax-credit - title: Colorado Earned Income Tax Credit (te19) href: https://leg.colorado.gov/sites/default/files/te19_colorado_earned_income_tax_credit.pdf diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml index f498acb52ab..e0db8f4c8ef 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml @@ -7,5 +7,7 @@ metadata: period: year label: Hawaii student loan interest deduction cap reference: + - title: HRS section 235-2.4 - Hawaii operative IRC provisions (sections 63 to 530) + href: https://www.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm - title: 2025 Hawaii N-11 Instructions href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml index 00e306fa17c..0a87d3efd38 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml @@ -18,5 +18,7 @@ metadata: period: year label: Hawaii student loan interest deduction phase-out divisor reference: + - title: HRS section 235-2.4 - Hawaii operative IRC provisions (sections 63 to 530) + href: https://www.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm - title: 2025 Hawaii N-11 Instructions href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml index 0ea350bc22e..eb32ff5800e 100644 --- a/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml @@ -18,5 +18,7 @@ metadata: period: year label: Hawaii student loan interest deduction phase-out start reference: + - title: HRS section 235-2.4 - Hawaii operative IRC provisions (sections 63 to 530) + href: https://www.capitol.hawaii.gov/hrscurrent/Vol04_Ch0201-0257/HRS0235/HRS_0235-0002_0004.htm - title: 2025 Hawaii N-11 Instructions href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py index 4466403f4c4..c3e0d831857 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py @@ -8,10 +8,16 @@ class dc_eitc(Variable): unit = USD definition_period = YEAR reference = ( - "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04" # (f) + "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04", # (f) + "https://code.dccouncil.gov/us/dc/council/laws/23-149", # D.C. Law 23-149 ITIN expansion ) defined_for = StateCode.DC + # TODO: split into two named branches to mirror DC's statutory architecture: + # - dc_base_eitc: SSN-required, per section 47-1806.04 federal IRC section 32 incorporation + # - dc_itin_eitc: ITIN-eligible, per D.C. Law 23-149 (Earned Income Tax Credit Expansion Clarification Amendment) + # The unified has_tin path below is behaviorally correct but conceptually merges the two. + def formula(tax_unit, period, parameters): # D.C. Law 23-149 extends the EITC to filers and children with ITINs, # overriding the federal IRC section 32 SSN-only identification rule. diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py index 87176fdd417..af0979acdd5 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc.py @@ -20,9 +20,13 @@ def formula(tax_unit, period, parameters): federal_eitc = tax_unit("eitc", period) return federal_eitc * ip.earned_income.match_rate if ip.earned_income.static_conformity_in_effect: - # IC 6-3.1-21 pins Indiana's static-conformity EITC to the IRC as - # in effect on January 1, 2023. - frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") + # IC 6-3-1-11 (Indiana's IRC definition for IC 6-3.1-21): + # - TY 2023 through 2025: IRC as in effect on January 1, 2023. + # - TY 2026 onward: IRC as in effect on July 4, 2025, per SB 243 + # of 2026 (signed by Governor Braun, advancing conformity to + # include sections of H.R. 1 / Public Law 119-21). + snapshot_date = "2025-07-04" if period.start.year >= 2026 else "2023-01-01" + frozen_eitc = parameters.gov.irs.credits.eitc(snapshot_date) child_count = tax_unit("eitc_child_count", period) demographic_eligible = calculate_eitc_demographic_eligibility( tax_unit, period, frozen_eitc, child_count diff --git a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py index acd69193c53..df62d185a1b 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py +++ b/policyengine_us/variables/gov/states/in/tax/income/credits/earned_income_credit/in_eitc_eligible.py @@ -21,9 +21,12 @@ def formula(tax_unit, period, parameters): if not p.credits.earned_income.decoupled: return gets_federal_eitc if p.credits.earned_income.static_conformity_in_effect: - # IC 6-3.1-21 pins Indiana's static-conformity EITC to the IRC as - # in effect on January 1, 2023. - frozen_eitc = parameters.gov.irs.credits.eitc("2023-01-01") + # IC 6-3-1-11 (Indiana's IRC definition for IC 6-3.1-21): + # - TY 2023 through 2025: IRC as in effect on January 1, 2023. + # - TY 2026 onward: IRC as in effect on July 4, 2025, per SB 243 + # of 2026. + snapshot_date = "2025-07-04" if period.start.year >= 2026 else "2023-01-01" + frozen_eitc = parameters.gov.irs.credits.eitc(snapshot_date) child_count = tax_unit("eitc_child_count", period) demographic_eligible = calculate_eitc_demographic_eligibility( tax_unit, period, frozen_eitc, child_count From 99f6a79d96b3e8977ef860183a533e81b8a013d6 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 12 May 2026 16:06:16 -0400 Subject: [PATCH 8/9] Add missing tests for PR 8243 Adds 17 new tests covering the variables and behavior introduced/changed by PR 8243: - HoH eligible (2): separated filer with qualifying child positive case; separated filer with only a qualifying relative negative case (IRC 7703(b)(1) "child within the meaning of section 152(f)(1)"). - IL CTC (2): age-11 qualifying child receives the credit; age-11 dependent who is NOT a qualifying child does not receive the credit (validates the new IRC 152(c) eligibility test). - CO EITC (3): under-25 expansion boundary cases - age 17 below all mins, age 18 default no homeless/foster, age 20 specified-student exclusion. - DC childless EITC (2): age 24 below federal minimum, age 65 above federal maximum. - HI student loan interest (5): pre-2025 zeros, married-filing-separately zero, joint above phase-out end zero, subtraction = max(adjustment, 0) wrapper, addition = max(0, -adjustment) wrapper. - GA itemized deductions adjustment (2): default-zero passthrough; explicit input flows through to ga_deductions. - SC 2026 decoupling (2): SC adopts federal itemized deductions in TY 2025; SC decouples in TY 2026 per H. 4216 / Act 110 of 2026. - WA WFTC (1): married-filing-separately state-only-path qualifies for credit. All 77 affected tests pass (17 new + 60 existing). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../co/tax/income/credits/eitc/co_eitc.yaml | 75 +++++++++++++ .../dc_eitc_without_qualifying_children.yaml | 51 +++++++++ .../ga_itemized_deductions_adjustment.yaml | 25 +++++ .../hi_student_loan_interest_adjustment.yaml | 100 ++++++++++++++++++ .../states/il/tax/income/credits/il_ctc.yaml | 46 ++++++++ .../tax/income/state_itemized_deductions.yaml | 31 ++++++ .../wa_working_families_tax_credit.yaml | 30 ++++++ .../tax_unit/head_of_household_eligible.yaml | 48 +++++++++ 8 files changed, 406 insertions(+) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_itemized_deductions_adjustment.yaml diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml index af3285014a3..3113273496c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml @@ -125,3 +125,78 @@ state_code: CO output: co_eitc: 324.5 + +- name: Colorado EITC under-25 path - age 17 is below all minimums. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 17 + employment_income: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + # Age 17 is below all under_25_expansion minimums (default 19, homeless/foster 18). + # Federal EITC childless minimum age (25) also not met. + co_eitc: 0 + +- name: Colorado EITC under-25 path - age 18 without homeless/foster status. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 18 + employment_income: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + # Age 18 only qualifies under the homeless/foster minimum (18); default + # minimum is 19 for non-students. Federal EITC also not eligible. + co_eitc: 0 + +- name: Colorado EITC under-25 path - age 20 full-time college student is excluded. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 20 + employment_income: 10_000 + is_full_time_college_student: true + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + # Specified students are excluded from the default age 19-23 under-25 path. + co_eitc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml index 166fe58cc58..10f4902f834 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_eitc_without_qualifying_children.yaml @@ -66,3 +66,54 @@ state_code: DC output: dc_eitc_without_qualifying_child: 612 + +- name: DC childless EITC - age 24 single filer is below federal minimum age + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 24 + employment_income: 10_000 + is_tax_unit_head: true + has_tin: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: DC + output: + # Federal EITC childless minimum age is 25 for tax year 2025; below that + # threshold there is no DC childless credit. + dc_eitc_without_qualifying_child: 0 + +- name: DC childless EITC - age 65 single filer is above federal maximum age + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 65 + employment_income: 10_000 + is_tax_unit_head: true + has_tin: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: DC + output: + # Federal EITC childless maximum age is 64 for tax year 2025. + dc_eitc_without_qualifying_child: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_itemized_deductions_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_itemized_deductions_adjustment.yaml new file mode 100644 index 00000000000..ee733a5fad3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ga/tax/income/ga_itemized_deductions_adjustment.yaml @@ -0,0 +1,25 @@ +- name: Georgia itemized deductions adjustment defaults to zero when not provided. + period: 2023 + input: + tax_unit_itemizes: true + ga_standard_deduction: 10_000 + itemized_taxable_income_deductions: 18_000 + state_code: GA + output: + # No ga_itemized_deductions_adjustment input, so it defaults to 0 + # and Georgia deductions equal the federal itemized amount unchanged. + ga_itemized_deductions_adjustment: 0 + ga_deductions: 18_000 + +- name: Setting Georgia itemized deductions adjustment reduces Georgia deductions by that amount. + period: 2023 + input: + tax_unit_itemizes: true + ga_standard_deduction: 10_000 + itemized_taxable_income_deductions: 18_000 + ga_itemized_deductions_adjustment: 500 + state_code: GA + output: + # ga_deductions = itemized 18_000 - ga_itemized_deductions_adjustment 500. + ga_itemized_deductions_adjustment: 500 + ga_deductions: 17_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml index 4e1b90a1443..28d20b93d96 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml @@ -50,3 +50,103 @@ # Federal AGI $100,000 (wages $60K + pension $40K) minus Hawaii pension # subtraction $40K minus Hawaii student loan subtraction $833.33. hi_agi: 59_166.67 + +- name: Pre-2025 Hawaii student loan interest variables are zero before the deduction takes effect + period: 2024 + input: + people: + head: + age: 30 + employment_income: 60_000 + student_loan_interest: 2_500 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: HI + output: + # The student_loan_interest.in_effect parameter is false before 2025-01-01. + hi_student_loan_interest_deduction: 0 + hi_student_loan_interest_adjustment: 0 + hi_student_loan_interest_subtraction: 0 + hi_student_loan_interest_addition: 0 + +- name: Married filing separately receives no Hawaii student loan interest deduction + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 30_000 + student_loan_interest: 2_500 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SEPARATE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: HI + output: + # The `where(separate, 0, deduction)` guard in hi_student_loan_interest_deduction. + hi_student_loan_interest_deduction: 0 + +- name: Joint filer above the phase-out end receives no Hawaii student loan interest deduction + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 150_000 + student_loan_interest: 2_500 + is_tax_unit_head: true + spouse: + age: 35 + employment_income: 0 + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + spm_units: + spm_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: HI + output: + # Joint phase-out: $100K start with $30K divisor -> ends at $130K MAGI. + # MAGI = $150K is above $130K, so deduction is fully phased out. + hi_student_loan_interest_deduction: 0 + +- name: Hawaii student loan subtraction equals max(adjustment, 0) + period: 2025 + input: + state_code: HI + hi_student_loan_interest_adjustment: 500 + output: + hi_student_loan_interest_subtraction: 500 + hi_student_loan_interest_addition: 0 + +- name: Hawaii student loan addition equals max(0, -adjustment) + period: 2025 + input: + state_code: HI + hi_student_loan_interest_adjustment: -250 + output: + hi_student_loan_interest_subtraction: 0 + hi_student_loan_interest_addition: 250 diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml index 8e4b1adfbf6..cdc19f1a707 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_ctc.yaml @@ -84,3 +84,49 @@ il_eitc: 100 output: il_ctc: 20 + +- name: Age-11 qualifying child dependent receives the Illinois CTC. + period: 2024 + input: + people: + head: + age: 30 + dependent: + age: 11 + is_tax_unit_dependent: true + is_qualifying_child_dependent: true + households: + household: + members: [head, dependent] + state_code: IL + tax_units: + tax_unit: + members: [head, dependent] + il_eitc: 100 + output: + # IL CTC: 100 * 0.20 (20% match) = 20. + il_ctc: 20 + +- name: Age-11 dependent who is not a qualifying child does not receive the Illinois CTC. + period: 2024 + input: + people: + head: + age: 30 + dependent: + age: 11 + is_tax_unit_dependent: true + is_qualifying_child_dependent: false + households: + household: + members: [head, dependent] + state_code: IL + tax_units: + tax_unit: + members: [head, dependent] + il_eitc: 100 + output: + # 35 ILCS 5/212.3 ties to IRC 152(c) qualifying-child rules. + # A dependent who is not a 152(c) qualifying child (e.g. qualifying relative) + # does not generate the IL CTC. + il_ctc: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml index 27225b9cd81..e11a5c61b86 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_itemized_deductions.yaml @@ -191,3 +191,34 @@ output: # Should take the maximum (joint in this case) state_itemized_deductions: 7_600 + +- name: South Carolina adopts federal itemized deductions in tax year 2025 (pre-decoupling) + period: 2025 + input: + state_code: SC + filing_status: SINGLE + age_head: 35 + # Federal itemized components total $20,000. + charitable_deduction: 5_000 + interest_deduction: 10_000 + salt_deduction: 5_000 + output: + # Pre-2026, SC is in states_using_federal_itemized_deductions, so the state + # uses the federal sum. + state_itemized_deductions: 20_000 + +- name: South Carolina decouples from federal itemized deductions in tax year 2026 + period: 2026 + input: + state_code: SC + filing_status: SINGLE + age_head: 35 + # Federal itemized components total $20,000 again. + charitable_deduction: 5_000 + interest_deduction: 10_000 + salt_deduction: 5_000 + output: + # Per H. 4216 / Act 110 of 2026, SC drops out of + # states_using_federal_itemized_deductions effective 2026-01-01. + # SC has no state-specific itemized variable yet, so the result is 0. + state_itemized_deductions: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml index 4bc7326fbba..6b0dbe12e48 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml @@ -363,3 +363,33 @@ state_code: WA output: wa_working_families_tax_credit: 660 + +- name: WA WFTC - married filing separately filer with one child can qualify via state-only path + period: 2024 + absolute_error_margin: 1 + input: + people: + head: + age: 35 + employment_income: 15_000 + is_tax_unit_head: true + has_tin: true + child: + age: 5 + is_tax_unit_dependent: true + has_tin: true + tax_units: + tax_unit: + members: [head, child] + filing_status: SEPARATE + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: WA + output: + # Frozen 2022-06-09 federal EITC parameters allow separate filers, so the + # state-only path applies for MFS filers and yields a positive credit. + wa_working_families_tax_credit: 640 diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml index 175b025b949..61d8ecbfb61 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/head_of_household_eligible.yaml @@ -132,3 +132,51 @@ # Unrelated household members are qualifying relatives under IRC 152(d) # but do not count for HOH purposes per IRC 2(b)(3) head_of_household_eligible: false + +- name: Separated married filer with a qualifying child is treated as unmarried per IRC 7703(b). + period: 2024 + input: + people: + parent: + age: 38 + is_tax_unit_head: true + is_separated: true + child: + age: 6 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child] + # Filing as married filing separately while living apart from spouse. + households: + household: + members: [parent, child] + output: + # IRC 7703(b)(1) "considered unmarried" path: separated filer with a child + # qualifying-person abode qualifies for HOH. + head_of_household_eligible: true + +- name: Separated married filer with only a qualifying relative does not qualify per IRC 7703(b)(1). + period: 2024 + input: + people: + head: + age: 40 + is_tax_unit_head: true + is_separated: true + grandparent: + age: 78 + is_tax_unit_dependent: true + is_related_to_head_or_spouse: true + employment_income: 1_000 + tax_units: + tax_unit: + members: [head, grandparent] + households: + household: + members: [head, grandparent] + output: + # IRC 7703(b)(1) refers to "a child (within the meaning of section 152(f)(1))". + # A qualifying relative alone is not sufficient for the treated-unmarried path, + # so the separated filer does not qualify for HOH. + head_of_household_eligible: false From 381babf5133586bea2a8542dd06ae27b445fd752 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 12 May 2026 16:55:53 -0400 Subject: [PATCH 9/9] Split DC EITC into base (SSN) and ITIN-inclusive paths; add boundary tests DC ITIN-EITC branch split: - New variable dc_base_eitc implements the credit DC would grant under section 47-1806.04 alone (federal IRC section 32 incorporation), requiring an SSN for the filer and each qualifying child. This dispatches between the with- and without-qualifying-child branches internally using meets_eitc_identification_requirements rather than has_tin. - Existing dc_eitc remains the Law 23-149 ITIN-inclusive credit (behavior unchanged). The TODO comment is replaced with an architecture doc explaining that the difference dc_eitc - dc_base_eitc is the incremental Law 23-149 benefit. - 3 new tests in dc_base_eitc.yaml: - SSN filer with SSN child: dc_base_eitc == dc_eitc (no ITIN expansion) - ITIN filer: dc_base_eitc == 0, dc_eitc > 0 (Law 23-149 grants the credit) - SSN filer with ITIN child: base path falls back to childless EITC Smaller test gaps closed: - HI joint mid-phase-out: $115K joint MAGI -> partial $1,458 deduction - WA WFTC extra-ITIN-children: state-only path with state child_count > federal_child_count - WA WFTC age expansion: 2029+ in_effect with min_age 18; tests positive case, age-17 negative case, and 2028 not-yet-in-effect case - CO age-24 qualifies via eligible_at_max_age; age-25 via standard federal EITC 58/58 affected tests pass locally (10 new + 48 existing). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../co/tax/income/credits/eitc/co_eitc.yaml | 51 ++++++++ .../states/dc/tax/income/dc_base_eitc.yaml | 97 +++++++++++++++ .../hi_student_loan_interest_adjustment.yaml | 33 +++++ .../wa_working_families_tax_credit.yaml | 117 ++++++++++++++++++ .../tax/income/credits/eitc/dc_base_eitc.py | 72 +++++++++++ .../dc/tax/income/credits/eitc/dc_eitc.py | 13 +- 6 files changed, 379 insertions(+), 4 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_base_eitc.yaml create mode 100644 policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_base_eitc.py diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml index 3113273496c..d970cb4deb7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/tax/income/credits/eitc/co_eitc.yaml @@ -200,3 +200,54 @@ output: # Specified students are excluded from the default age 19-23 under-25 path. co_eitc: 0 + +- name: Colorado EITC under-25 path - age 24 qualifies via eligible_at_max_age + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 24 + employment_income: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + # Age 24 qualifies under the eligible_at_max_age branch (24 alone qualifies). + # Same income as the existing age-19 test yields the same credit. + co_eitc: 324.5 + +- name: Colorado EITC under-25 path - age 25 above max for under-25 expansion but qualifies via federal EITC + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 25 + employment_income: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: CO + output: + # At age 25, the under-25 expansion no longer applies, but the federal + # EITC childless minimum age is also 25, so the filer qualifies for the + # standard 50% Colorado match of the federal EITC. + co_eitc: 324.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_base_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_base_eitc.yaml new file mode 100644 index 00000000000..a5d5d5333be --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/dc/tax/income/dc_base_eitc.yaml @@ -0,0 +1,97 @@ +- name: SSN filer with SSN child - dc_base_eitc matches dc_eitc + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 15_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: CITIZEN + child: + age: 5 + is_tax_unit_dependent: true + has_tin: true + ssn_card_type: CITIZEN + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: DC + output: + # SSN-qualifying household - base path and Law 23-149 path produce the same credit. + # Federal EITC at $15K HoH with 1 qualifying child = $4,328 (phase-in capped + # at max for one child). DC match is 100% in 2025. + dc_base_eitc: 4_328 + dc_eitc: 4_328 + +- name: ITIN filer - dc_base_eitc is zero, dc_eitc is positive via Law 23-149 + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 8_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: DC + output: + # Section 47-1806.04 base path requires an SSN-valid identification; ITIN + # filers receive zero under the base path but Law 23-149 grants the credit. + dc_base_eitc: 0 + dc_eitc: 612 + +- name: SSN filer with ITIN child - dc_base_eitc uses no-child branch, dc_eitc uses with-child branch + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 35 + employment_income: 10_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: CITIZEN + child: + age: 5 + is_tax_unit_dependent: true + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [head, child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: DC + output: + # SSN-only base path: no SSN qualifying child, so the base falls back + # to the childless EITC branch (filer is age 35 with SSN, age-eligible). + # Federal childless EITC at $10K plus DC phase-out at $10K AGI yields + # a positive credit. Law 23-149 (dc_eitc) routes through the with-child + # branch since the ITIN child is counted there. + # Expected dc_base_eitc value calculated by tracing dc_eitc_without_qualifying_child. + dc_base_eitc: 649 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml index 28d20b93d96..5f5368df07b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml @@ -150,3 +150,36 @@ output: hi_student_loan_interest_subtraction: 0 hi_student_loan_interest_addition: 250 + +- name: Joint filer at $115K Hawaii MAGI receives a partial Hawaii student loan interest deduction + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 40 + employment_income: 115_000 + student_loan_interest: 2_500 + is_tax_unit_head: true + spouse: + age: 40 + employment_income: 0 + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + spm_units: + spm_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: HI + output: + # Federal AGI is $112,500 (= $115K wages - $2,500 federal student loan + # interest deduction). hi_modified_agi equals federal AGI plus 0 net + # Hawaii additions/subtractions = $112,500. Joint phase-out: start $100K, + # divisor $30K -> reduction share = (112,500-100,000)/30,000 = 0.4167. + # Deduction = $2,500 * (1 - 0.4167) = $1,458.33. + hi_student_loan_interest_deduction: 1_458.33 diff --git a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml index 6b0dbe12e48..e6bde84b689 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/wa/tax/income/credits/wa_working_families_tax_credit.yaml @@ -393,3 +393,120 @@ # Frozen 2022-06-09 federal EITC parameters allow separate filers, so the # state-only path applies for MFS filers and yields a positive credit. wa_working_families_tax_credit: 640 + +- name: WA WFTC - SSN filer with one SSN child and one ITIN child uses state-only path + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 35 + employment_income: 20_000 + is_tax_unit_head: true + has_tin: true + ssn_card_type: CITIZEN + ssn_child: + age: 5 + is_tax_unit_dependent: true + has_tin: true + ssn_card_type: CITIZEN + itin_child: + age: 7 + is_tax_unit_dependent: true + has_tin: true + ssn_card_type: OTHER_NON_CITIZEN + tax_units: + tax_unit: + members: [head, ssn_child, itin_child] + filing_status: HEAD_OF_HOUSEHOLD + spm_units: + spm_unit: + members: [head, ssn_child, itin_child] + households: + household: + members: [head, ssn_child, itin_child] + state_code: WA + output: + # Federal EITC counts only the SSN child (federal_child_count = 1) but the + # WA WFTC counts both children (state child_count = 2). Because child_count + # > federal_child_count, the state-only path applies and the credit reflects + # the 2-child WFTC amount; partially phased out at $20K earnings. + wa_working_families_tax_credit: 995 + +- name: WA WFTC age expansion - age 18 filer qualifies in tax year 2029 + period: 2029 + absolute_error_margin: 1 + input: + people: + head: + age: 18 + employment_income: 8_000 + is_tax_unit_head: true + has_tin: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA + output: + # ESSB 6346 Sec. 901 age expansion is in_effect from 2029-01-01 with + # min_age = 18. Age 18 single filer who would otherwise be too young for + # federal EITC childless (min 25) qualifies for WFTC via the age expansion. + wa_working_families_tax_credit_age_expansion_eligible: true + +- name: WA WFTC age expansion - age 17 filer does not qualify in 2029 (below min age 18) + period: 2029 + absolute_error_margin: 1 + input: + people: + head: + age: 17 + employment_income: 8_000 + is_tax_unit_head: true + has_tin: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA + output: + # Below the age expansion minimum (18) - not eligible via this path. + wa_working_families_tax_credit_age_expansion_eligible: false + +- name: WA WFTC age expansion - age 18 filer in 2028 (before in_effect) does not qualify via expansion + period: 2028 + absolute_error_margin: 1 + input: + people: + head: + age: 18 + employment_income: 8_000 + is_tax_unit_head: true + has_tin: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + households: + household: + members: [head] + state_code: WA + output: + # ESSB 6346 Sec. 901 takes effect 2029-01-01; in 2028 the expansion is not yet + # active so the age 18 filer does not qualify via this path. + wa_working_families_tax_credit_age_expansion_eligible: false diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_base_eitc.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_base_eitc.py new file mode 100644 index 00000000000..c75427e7c04 --- /dev/null +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_base_eitc.py @@ -0,0 +1,72 @@ +from policyengine_us.model_api import * +from policyengine_us.tools.state_eitc_helpers import ( + calculate_eitc_like_amount, + eitc_filing_requirement_met, + eitc_filing_status_eligible, +) + + +class dc_base_eitc(Variable): + value_type = float + entity = TaxUnit + label = "DC EITC under the federal-incorporation base path" + documentation = ( + "The credit DC would grant under section 47-1806.04 alone, which " + "incorporates IRC section 32 by reference and therefore requires a " + "Social Security Number for the filer and each qualifying child. " + "This is the base value; the ITIN expansion under D.C. Law 23-149 " + "is the difference between dc_eitc and this variable." + ) + unit = USD + definition_period = YEAR + reference = "https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.04" + defined_for = StateCode.DC + + def formula(tax_unit, period, parameters): + person = tax_unit.members + age = person("age", period) + meets_eitc_id = person("meets_eitc_identification_requirements", period) + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + filer_meets_id = tax_unit.sum(is_head_or_spouse & ~meets_eitc_id) == 0 + qualifying_child = ( + person("is_qualifying_child_dependent", period) & meets_eitc_id + ) + child_count = tax_unit.sum(qualifying_child) + has_qualifying_child = child_count > 0 + p = parameters(period).gov.states.dc.tax.income.credits + + with_child_eitc = ( + calculate_eitc_like_amount( + tax_unit, + period, + parameters, + child_count, + has_qualifying_child, + filer_meets_id, + ) + * p.eitc.with_children.match + ) + + min_age = parameters.gov.irs.credits.eitc.eligibility.age.min(period) + max_age = parameters.gov.irs.credits.eitc.eligibility.age.max(period) + age_eligible = is_head_or_spouse & (age >= min_age) & (age <= max_age) + without_child_eligible = ( + filer_meets_id + & ~has_qualifying_child + & tax_unit.any(age_eligible) + & tax_unit("eitc_investment_income_eligible", period) + & eitc_filing_status_eligible(tax_unit, period, parameters) + & eitc_filing_requirement_met(tax_unit, period) + & tax_unit("takes_up_eitc", period) + ) + without_child_phased_in = without_child_eligible * tax_unit( + "eitc_phased_in", period + ) + earnings = tax_unit("tax_unit_earned_income", period) + agi = tax_unit("adjusted_gross_income", period) + greater_of = max_(earnings, agi) + excess = max_(0, greater_of - p.eitc.without_children.phase_out.start) + without_child_phase_out = excess * p.eitc.without_children.phase_out.rate + without_child_eitc = max_(0, without_child_phased_in - without_child_phase_out) + + return where(has_qualifying_child, with_child_eitc, without_child_eitc) diff --git a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py index c3e0d831857..1e2c6cc29f3 100644 --- a/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py +++ b/policyengine_us/variables/gov/states/dc/tax/income/credits/eitc/dc_eitc.py @@ -13,10 +13,15 @@ class dc_eitc(Variable): ) defined_for = StateCode.DC - # TODO: split into two named branches to mirror DC's statutory architecture: - # - dc_base_eitc: SSN-required, per section 47-1806.04 federal IRC section 32 incorporation - # - dc_itin_eitc: ITIN-eligible, per D.C. Law 23-149 (Earned Income Tax Credit Expansion Clarification Amendment) - # The unified has_tin path below is behaviorally correct but conceptually merges the two. + # DC EITC architecture: + # - dc_base_eitc: the SSN-only credit under section 47-1806.04 (federal + # IRC section 32 incorporation). This is the base statutory entitlement. + # - dc_eitc (this variable): the Law 23-149 ITIN-inclusive credit. Law + # 23-149 (Earned Income Tax Credit Expansion Clarification Amendment + # Act of 2020) extends the EITC to filers and children with ITINs, so + # dc_eitc >= dc_base_eitc for any household. + # The difference dc_eitc - dc_base_eitc is the incremental Law 23-149 + # benefit. def formula(tax_unit, period, parameters): # D.C. Law 23-149 extends the EITC to filers and children with ITINs,