diff --git a/changelog.d/8243.fixed.md b/changelog.d/8243.fixed.md new file mode 100644 index 00000000000..4eee37657f4 --- /dev/null +++ b/changelog.d/8243.fixed.md @@ -0,0 +1,9 @@ +Fixed federal/state conformity across EITC, deduction, ALD, HoH, and CDCC linkages. + +- Federal HoH: tighten IRC 7703(b) "considered unmarried" to MFS/HoH/single filers only; a JOINT filer with `is_separated=true` no longer qualifies. +- IN EITC: advance the TY 2026+ static-conformity IRC snapshot to January 1, 2026 per Indiana SEA 243 (2025); the IN snapshot date and the WA WFTC 2022-06-09 snapshot remain Python literals because policyengine-core does not support string-valued parameters. Replace the IN 2021-only branch with a `gov.states.in.tax.income.credits.earned_income.childless.in_effect` gate. +- CT dropped from `states_using_federal_itemized_deductions` and `states_using_federal_standard_deduction` (CT-1040 uses neither — only a personal exemption); SC drops out effective TY 2026 per H. 4216 / Act 110 of 2026. +- DC: `dc_eitc` continues to model Law 23-149 ITIN inclusivity; the previously-added `dc_base_eitc` diagnostic variable is removed (unreferenced in this PR's scope). +- OH educator expense: the federal `educator_expense` is no longer in OH's deductions list. ORC § 5747.01(A)(31) only allows the *excess* above the federal $300 cap, modeled via the new `oh_educator_expense_deduction_person` input variable. Filers without explicit input receive 0 for the OH-only excess; downstream microdata should populate it. The federal-cap interaction is no longer auto-applied to the OH deduction list — an under-implementation, documented as a known limitation. +- GA: `ga_itemized_deductions_adjustment` ships as an explicit-input stub for now (other-state taxes and exempt-investment interest are not separately observed in the baseline microdata). +- HI: new student loan interest deduction subtree (`gov.states.hi.tax.income.subtractions.student_loan_interest/*`) and supporting `hi_modified_agi`, `hi_student_loan_interest_*` variables modelling pre-current IRC § 221, effective TY 2025+. The pre-2025 HI subtractions list retains `student_loan_interest_ald` so years 2021-2024 continue to receive the federal-equivalent SLI deduction. 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 319950498d8..a8f8759e735 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/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..2d7e999ec36 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/eligible_at_max_age.yaml @@ -0,0 +1,16 @@ +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://content.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 Office of the State Auditor - Tax Expenditure Evaluation 19 - Colorado EITC + 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..d3cffc97394 --- /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,16 @@ +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://content.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 Office of the State Auditor - Tax Expenditure Evaluation 19 - Colorado EITC + 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..0d33ed9183d --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/max_age.yaml @@ -0,0 +1,16 @@ +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://content.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 Office of the State Auditor - Tax Expenditure Evaluation 19 - Colorado EITC + 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..1c64842434e --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/tax/income/credits/eitc/under_25_expansion/min_age.yaml @@ -0,0 +1,16 @@ +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://content.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 Office of the State Auditor - Tax Expenditure Evaluation 19 - Colorado EITC + 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 new file mode 100644 index 00000000000..37ba3786580 --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/additions/additions.yaml @@ -0,0 +1,14 @@ +description: Hawaii additions to federal adjusted gross income. +values: + 2025-01-01: + - hi_student_loan_interest_addition + +metadata: + unit: list + period: year + label: Hawaii additions to federal adjusted gross income + reference: + - title: 2025 Hawaii N-11 Instructions + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 + - 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 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..e0db8f4c8ef --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/cap.yaml @@ -0,0 +1,13 @@ +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: 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/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..562bc88a4b4 --- /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 + href: https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35 + - 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 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..0a87d3efd38 --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/divisor.yaml @@ -0,0 +1,24 @@ +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: 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 new file mode 100644 index 00000000000..eb32ff5800e --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/student_loan_interest/phase_out/start.yaml @@ -0,0 +1,24 @@ +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: 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/subtractions.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/subtractions/subtractions.yaml index f8444c3f216..241ecdea1a0 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 @@ -6,6 +6,12 @@ values: - hi_military_pay_exclusion # Line 15 - us_govt_interest # Line 18 - student_loan_interest_ald # 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 metadata: 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..82d33c0f8d9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/household/states_using_federal_itemized_deductions.yaml @@ -0,0 +1,28 @@ +description: List of USPS state codes whose income tax adopts the federal itemized deductions under the state income tax program. +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(1)(b) - North Dakota federal-conformity itemized deductions + href: https://www.legis.nd.gov/cencode/t57c38.pdf#page=2 + - title: S.C. Code Ann. § 12-6-50 - South Carolina conformity statute (Internal Revenue Code sections specifically not adopted; amended by H. 4216 / Act 110 of 2026 to add § 63) + href: https://law.justia.com/codes/south-carolina/title-12/chapter-6/section-12-6-50/ + - title: South Carolina H. 4216 / Act 110 of 2026 - decouples from federal itemized and standard deductions effective tax year 2026 + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + - title: Conn. Gen. Stat. § 12-701 - Connecticut adjusted gross income definition (CT has no itemized/standard deduction; excluded from both federal-conformity lists) + href: https://www.cga.ct.gov/current/pub/chap_229.htm#sec_12-701 + - title: Utah Code § 59-10-1018(1)(a) - Utah itemized deductions + href: https://le.utah.gov/xcode/Title59/Chapter10/59-10-S1018.html +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..1146b4334bb --- /dev/null +++ b/policyengine_us/parameters/gov/states/household/states_using_federal_standard_deduction.yaml @@ -0,0 +1,40 @@ +description: List of USPS state codes whose income tax adopts the federal standard deduction under the state income tax program. +metadata: + unit: list + period: year + label: States adopting federal standard deduction + reference: + - title: CRS § 39-22-104(2) - Colorado conformity to federal taxable income + href: https://leg.colorado.gov/sites/default/files/images/olls/crs2024-title-39.pdf + - title: Idaho Code § 63-3022(j)(1) - Idaho federal standard deduction conformity + href: https://legislature.idaho.gov/statutesrules/idstat/Title63/T63CH30/SECT63-3022/ + - title: RSMo § 143.131(1) - 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(1)(b) - North Dakota federal taxable income conformity + href: https://www.legis.nd.gov/cencode/t57c38.pdf#page=2 + - title: NMSA § 7-2-7.5 - New Mexico federal standard deduction conformity + href: https://nmonesource.com/nmos/nmsa/en/item/4359/index.do + - title: S.C. Code Ann. § 12-6-50 - South Carolina conformity statute (Internal Revenue Code sections specifically not adopted; amended by H. 4216 / Act 110 of 2026 to add § 63) + href: https://law.justia.com/codes/south-carolina/title-12/chapter-6/section-12-6-50/ + - title: South Carolina H. 4216 / Act 110 of 2026 - decouples from federal itemized and standard deductions effective tax year 2026 + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + - title: Conn. Gen. Stat. § 12-701 - Connecticut adjusted gross income definition (CT has no itemized/standard deduction; excluded from both federal-conformity lists) + href: https://www.cga.ct.gov/current/pub/chap_229.htm#sec_12-701 + - title: Utah Code § 59-10-1018(1)(a) - Utah federal standard deduction conformity + href: https://le.utah.gov/xcode/Title59/Chapter10/59-10-S1018.html +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/parameters/gov/states/il/tax/income/credits/eitc/childless_min_age.yaml b/policyengine_us/parameters/gov/states/il/tax/income/credits/eitc/childless_min_age.yaml new file mode 100644 index 00000000000..1e7020ae2f3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/il/tax/income/credits/eitc/childless_min_age.yaml @@ -0,0 +1,16 @@ +description: Illinois sets this minimum age for filers without qualifying children to be eligible for the Earned Income Tax Credit under the Illinois Earned Income Tax Credit program. + +values: + 2023-01-01: 18 + +metadata: + unit: year + period: year + label: Illinois EITC childless minimum age + reference: + - title: 35 ILCS 5/212 (Illinois Earned Income Tax Credit, including the age-18 minimum for filers without qualifying children added by P.A. 102-0700) + href: https://www.ilga.gov/Legislation/ILCS/fulltext.asp?DocName=003500050K212&SeqStart=15400000&SeqEnd=16000000 + - title: Illinois Public Act 102-0700 (HB 4920) - Illinois EITC expansion to childless filers age 18+ + href: https://www.ilga.gov/Legislation/PublicActs/Fulltext.asp?Name=102-0700&GA=102 + - title: Illinois Department of Revenue - Illinois Earned Income Tax Credit eligibility + href: https://tax.illinois.gov/individuals/credits/earnedincomecredit.html diff --git a/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/childless/in_effect.yaml b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/childless/in_effect.yaml new file mode 100644 index 00000000000..686542b71ed --- /dev/null +++ b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/childless/in_effect.yaml @@ -0,0 +1,12 @@ +description: Indiana applies its state-specific decoupled childless Earned Income Tax Credit parameters when this indicator is true. +values: + 2010-01-01: false + 2021-01-01: true + 2022-01-01: false +metadata: + unit: bool + period: year + label: Indiana EITC childless decoupled parameters in effect + reference: + - title: IC 6-3.1-21 - Indiana Earned Income Tax Credit (2021 childless override) + href: https://iga.in.gov/laws/2021/ic/titles/6#6-3.1-21 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..a85f974da2d 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 @@ -1,18 +1,17 @@ -description: Indiana has a state EITC that is decoupled from the federal EITC if this parameter is true. +description: Indiana decouples its Earned Income Tax Credit from the federal EITC under the Indiana Earned Income Tax Credit program when this indicator is true. metadata: unit: bool + period: year label: Whether IN EITC is decoupled from federal EITC reference: - - title: Indiana General Assembly Laws - href: https://iga.in.gov/laws/2021/ic/titles/6#6-3.1-21 - - title: 2023 IT-40 Tax Form Instructions Booklet, pg.3 - href: https://forms.in.gov/Download.aspx?id=15792 - - title: 2025 IT-40 Income Tax Form Instruction Booklet, pg. 30 - href: https://forms.in.gov/Download.aspx?id=16915 - - + - title: IC 6-3-1-11 - Indiana definition of Internal Revenue Code (fixes the federal-law reference date for the decoupled branch) + href: https://iga.in.gov/laws/2024/ic/titles/6#6-3-1-11 + - title: IC 6-3.1-21 - Indiana Earned Income Tax Credit chapter + href: https://iga.in.gov/laws/2024/ic/titles/6#6-3.1-21 + - title: 2023 IT-40 Individual Income Tax Booklet (Indiana EITC discussion) + href: https://forms.in.gov/Download.aspx?id=15792#page=9 + - title: 2025 IT-40 Income Tax Form Instruction Booklet + href: https://forms.in.gov/Download.aspx?id=16915#page=30 values: 2010-01-01: false 2011-01-01: true - 2023-01-01: false - 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..7c88006d9fc --- /dev/null +++ b/policyengine_us/parameters/gov/states/in/tax/income/credits/earned_income/static_conformity_in_effect.yaml @@ -0,0 +1,19 @@ +description: Indiana applies its static-conformity-to-Internal-Revenue-Code branch for the Earned Income Tax Credit if this indicator is true. The applicable snapshot is January 1, 2023 for tax years 2023-2025, and January 1, 2026 for tax years 2026 onward per Indiana SEA 243 of 2025. + +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-11 - Indiana definition of Internal Revenue Code, fixing the federal-law reference date + href: https://iga.in.gov/laws/2024/ic/titles/6#6-3-1-11 + - title: IC 6-3.1-21 - Indiana Earned Income Tax Credit chapter + href: https://iga.in.gov/laws/2024/ic/titles/6#6-3.1-21 + - title: Indiana SEA 243 (2025) - Advances IRC conformity to January 1, 2026 + href: https://iga.in.gov/legislative/2025/bills/senate/243 + - title: 2023 IT-40 Individual Income Tax Booklet (Indiana EITC discussion) + href: https://forms.in.gov/Download.aspx?id=15792#page=9 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 index 89d3de31157..1a1230b4f4a 100644 --- 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 @@ -1,3 +1,14 @@ +- 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 + - name: SSTB self-employment income is included in Alabama AGI period: 2024 input: 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..67cbfd979ee 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,231 @@ # 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 + +- 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 + +- 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 + +- name: Colorado EITC under-25 path - age 18 homeless filer qualifies + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 18 + employment_income: 10_000 + is_tax_unit_head: true + households: + household: + members: [head] + state_code: CO + is_homeless: true + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + output: + # CO under-25 expansion homeless_or_foster_min_age path (CRS 39-22-123.5): + # age 18 + homeless qualifies for the same credit as the age-19 default + # path at this income level. + co_eitc: 324.5 + +- name: Colorado EITC under-25 path - age 18 foster youth qualifies + period: 2025 + absolute_error_margin: 1 + input: + people: + head: + age: 18 + employment_income: 10_000 + is_tax_unit_head: true + was_in_foster_care: true + households: + household: + members: [head] + state_code: CO + tax_units: + tax_unit: + members: [head] + filing_status: SINGLE + spm_units: + spm_unit: + members: [head] + 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..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 @@ -41,3 +41,79 @@ 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 + +- 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_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/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 new file mode 100644 index 00000000000..5f5368df07b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/hi_student_loan_interest_adjustment.yaml @@ -0,0 +1,185 @@ +- 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: + 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_student_loan_interest_adjustment: -1250 + hi_agi: 58_750 + +- name: Hawaii subtracts more than federal when Hawaii AGI excludes pension income + period: 2025 + absolute_error_margin: 0.2 + input: + 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_student_loan_interest_adjustment: 833.33 + # 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 + +- 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/id/tax/income/id_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml index d820b852a51..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 @@ -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,20 @@ 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 does not add back the federal foreign tax credit when it is claimed as a credit + period: 2025 + input: + id_salt_deduction: 2_000 + itemized_taxable_income_deductions: 1_600 + foreign_tax_credit: 700 + state_code: ID + output: + # 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/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..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 @@ -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,70 @@ 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 + +- 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/il/tax/income/credits/il_eitc.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tax/income/credits/il_eitc.yaml index e34a35bc618..86c8465196c 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,82 @@ 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 + +- name: Illinois EITC childless filer age 18 qualifies for the state EITC + period: 2024 + 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: IL + output: + # 35 ILCS 5/212 (P.A. 102-0700) - IL EITC childless minimum age is 18 + # from 2023 onward; age 18 single filer with earned income qualifies. + il_eitc: 122.4 + +- name: Illinois EITC childless filer age 17 does not qualify + period: 2024 + 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: IL + output: + # Below the IL childless minimum age of 18; not eligible. + il_eitc: 0 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..b0ed5db3407 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,48 @@ 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 + +- name: Indiana EITC TY 2026 uses the SEA 243 advanced January 1, 2026 IRC snapshot + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + employment_income: 15_000 + 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 + spm_units: + spm_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: IN + output: + # TY 2026 onwards Indiana SEA 243 (2025) advances the IRC reference to + # January 1, 2026. The static_conformity_in_effect parameter is true; + # the year-conditional snapshot logic picks "2026-01-01" instead of + # "2023-01-01". A positive credit confirms the post-SEA 243 frozen-IRC + # code path is exercised. + in_eitc: 442.70 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/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..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 @@ -21,3 +21,12 @@ cdcc_potential: 1_000 output: pa_cdcc: 1_000 + +- 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: 1_000 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..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 @@ -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: @@ -178,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/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 59bfd0b0a2e..a57ea60a8fe 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] @@ -120,12 +184,26 @@ - name: Case 8, age 17 without EITC gets no WFTC in 2029. period: 2029 input: - state_code: WA - age_head: 17 - eitc: 0 - eitc_earned_income: 10_000 - 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 @@ -145,12 +223,26 @@ - name: Case 10, WFTC age expansion not in effect before 2029. period: 2028 input: - state_code: WA - age_head: 20 - eitc: 0 - eitc_earned_income: 10_000 - 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 @@ -159,10 +251,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) @@ -196,3 +304,213 @@ 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 + +- 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 + +- 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 + +- 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/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/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..d7fe6115967 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,102 @@ # 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 + +- name: Surviving spouse takes precedence over the separated-with-qualifying-child HoH path + period: 2024 + input: + people: + head: + age: 38 + is_tax_unit_head: true + is_separated: true + child: + age: 6 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [head, child] + surviving_spouse_eligible: true + households: + household: + members: [head, child] + output: + # head_of_household_eligible returns false when surviving_spouse_eligible + # is true, even when the separated-with-qualifying-child path would + # otherwise grant HoH. + head_of_household_eligible: false + +- name: Separated JOINT filer with qualifying child is NOT eligible for HoH + # IRC 7703(b)(1) "considered unmarried" path requires filing a separate + # return. A JOINT filer who happens to be separated forfeits the + # treated-unmarried route by choosing joint filing status. + period: 2024 + input: + people: + parent: + age: 38 + is_tax_unit_head: true + is_separated: true + spouse: + age: 36 + is_tax_unit_spouse: true + child: + age: 6 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, spouse, child] + filing_status: JOINT + households: + household: + members: [parent, spouse, child] + output: + head_of_household_eligible: false diff --git a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/wa/working_families_tax_credit.yaml b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/wa/working_families_tax_credit.yaml index dca662b6e02..276f3d01df5 100644 --- a/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/wa/working_families_tax_credit.yaml +++ b/policyengine_us/tests/policy/baseline/partners/analytics_coverage/edge_cases/state/wa/working_families_tax_credit.yaml @@ -120,7 +120,7 @@ output: tax_units: tax_unit: - wa_working_families_tax_credit: 342.59 + wa_working_families_tax_credit: 223.25 adjusted_gross_income: 15000.0 - name: wa_wftc_age_18_before_2029_expansion (wa) period: 2026 diff --git a/policyengine_us/tools/state_eitc_helpers.py b/policyengine_us/tools/state_eitc_helpers.py new file mode 100644 index 00000000000..02f891b989c --- /dev/null +++ b/policyengine_us/tools/state_eitc_helpers.py @@ -0,0 +1,140 @@ +"""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( + period + ).gov.irs.credits.eitc.eligibility.separate_filer + filing_status = tax_unit("filing_status", period) + return separate_filer_eligible | ( + filing_status != filing_status.possible_values.SEPARATE + ) + + +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, + eitc_parameters, + child_count, +): + """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.""" + + earnings = tax_unit("filer_adjusted_earnings", period) + agi = tax_unit("adjusted_gross_income", period) + 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 = calculate_eitc_phase_out_start( + tax_unit, period, eitc_parameters, 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) + 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 ( + calculate_eitc_amount_from_parameters( + tax_unit, period, eitc_parameters, child_count + ) + * demographic_eligible + * filer_identification_eligible + * investment_income_eligible + * filing_status_eligible + * is_filer + * takes_up_eitc + ) 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..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,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.tools.state_eitc_helpers import ( + calculate_eitc_like_amount, +) class co_eitc(Variable): @@ -12,5 +15,60 @@ 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 + ) + p_u25 = parameters( + period + ).gov.states.co.tax.income.credits.eitc.under_25_expansion + under_25_age_eligible = ( + ((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 + ) + 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..02dc122e75b 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,13 +8,20 @@ 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 def formula(tax_unit, period, parameters): + # D.C. Law 23-149 extends EITC eligibility to filers and qualifying + # children with ITINs; dc_base_eitc holds the SSN-only baseline. + 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..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,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.tools.state_eitc_helpers import ( + calculate_eitc_like_amount, +) class dc_eitc_with_qualifying_child(Variable): @@ -13,6 +16,21 @@ class dc_eitc_with_qualifying_child(Variable): defined_for = StateCode.DC def formula(tax_unit, period, parameters): - federal_eitc = tax_unit("eitc", period) + # 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) + 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..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,4 +1,8 @@ from policyengine_us.model_api import * +from policyengine_us.tools.state_eitc_helpers import ( + eitc_filing_requirement_met, + eitc_filing_status_eligible, +) class dc_eitc_without_qualifying_child(Variable): @@ -13,8 +17,28 @@ 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) + # 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) + 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..412512d142c --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/tax/income/deductions/ga_itemized_deductions_adjustment.py @@ -0,0 +1,19 @@ +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 + # Georgia-specific reduction from federal Schedule A before computing + # Georgia itemized deductions. Remains an explicit input because the + # baseline microdata do not separately observe components such as + # other-state income taxes and exempt-income investment interest. + reference = ( + "https://law.justia.com/codes/georgia/2024/title-48/chapter-7/article-2/section-48-7-27/", + "https://dor.georgia.gov/document/document/2025-it-511-individual-income-tax-booklet/download#page=16", + ) 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..c4c7203eeff --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/tax/income/subtractions/hi_student_loan_interest_adjustment.py @@ -0,0 +1,123 @@ +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): + # 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) + 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] + # SEPARATE filers receive no Hawaii SLI deduction; we still need a + # non-zero divisor here so the vectorized phase-out arithmetic does + # not divide by zero. The where(separate, 0, ...) guard below masks + # the result. + 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 = [ + v for v in p.additions.additions if v != "hi_student_loan_interest_addition" + ] + other_subtractions = [ + v + for v in p.subtractions.subtractions + if v != "hi_student_loan_interest_subtraction" + ] + other_additions_amount = ( + add(tax_unit, period, other_additions) if other_additions else 0 + ) + other_subtractions_amount = ( + add(tax_unit, period, other_subtractions) if other_subtractions else 0 + ) + return ( + tax_unit("adjusted_gross_income", period) + + other_additions_amount + - other_subtractions_amount + ) + + +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 + defined_for = StateCode.HI + reference = "https://files.hawaii.gov/tax/forms/current/n11ins.pdf#page=35" + + def formula(tax_unit, period, parameters): + 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( + tax_unit.members("student_loan_interest_ald", period) + ) + return hawaii_deduction - federal_deduction + + +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..05838bc0c2e 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 @@ -10,12 +10,20 @@ class id_itemized_deductions(Variable): reference = ( "https://tax.idaho.gov/wp-content/uploads/forms/EIN00046/EIN00046_03-01-2023.pdf#page=8", "https://tax.idaho.gov/wp-content/uploads/forms/EFO00089/EFO00089_09-23-2021.pdf", + "https://legislature.idaho.gov/statutesrules/idstat/Title63/T63CH30/SECT63-3022/", # Idaho Code 63-3022 (subtractions/itemized framework) + "https://tax.idaho.gov/wp-content/uploads/forms/EFO00088/", # Idaho Form 39R - Additions and Subtractions instructions (foreign-tax addback rule) ) 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) 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 03104d26b75..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,9 +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) - 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 + # 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 21dc19e29a0..0fed0a4d988 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.tools.state_eitc_helpers import ( + calculate_eitc_like_amount, +) class il_eitc(Variable): @@ -11,6 +14,24 @@ 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 + p = parameters(period).gov.states.il.tax.income.credits.eitc + demographic_eligible = (child_count > 0) | tax_unit.any( + is_head_or_spouse & (age >= p.childless_min_age) + ) + 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/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..5374d0af264 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.tools.state_eitc_helpers import ( + calculate_eitc_demographic_eligibility, + calculate_eitc_like_amount, +) class in_eitc(Variable): @@ -15,6 +19,39 @@ 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 ip.earned_income.static_conformity_in_effect: + # 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 January 1, 2026, per + # Indiana SEA 243 (2025). + # The snapshot dates are statutory literals; policyengine-core + # parameters do not support date-valued types, so they appear + # here rather than in the parameter tree. + snapshot_date = "2026-01-01" 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 + ) + 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 @@ -25,8 +62,9 @@ def formula(tax_unit, period, parameters): pi_rate = fp.eitc.phase_in_rate.calc(kids) po_start = fp.eitc.phase_out.start.calc(kids) # no JOINT bonus po_rate = fp.eitc.phase_out.rate.calc(kids) - if str(period) == "2021": - # ... additional decoupling of parameters for childless taxpayers + if ip.earned_income.childless.in_effect: + # 2021-only decoupled-childless-parameter branch, gated by + # gov.states.in.tax.income.credits.earned_income.childless.in_effect. maximum0 = ip.earned_income.childless.maximum pi_rate0 = ip.earned_income.childless.phase_in_rate po_start0 = ip.earned_income.childless.phase_out_start 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..5df47ec3ac6 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.tools.state_eitc_helpers import ( + calculate_eitc_demographic_eligibility, + calculate_eitc_like_amount, +) class in_eitc_eligible(Variable): @@ -16,6 +20,39 @@ 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 p.credits.earned_income.static_conformity_in_effect: + # 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 January 1, 2026, per + # Indiana SEA 243 (2025). + # The snapshot dates are statutory literals; policyengine-core + # parameters do not support date-valued types, so they appear + # here rather than in the parameter tree. + snapshot_date = "2026-01-01" 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 + ) + 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 new file mode 100644 index 00000000000..2d5ac005513 --- /dev/null +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/oh_educator_expense_deduction_person.py @@ -0,0 +1,24 @@ +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 + # ORC § 5747.01(A)(31): Ohio allows a deduction ONLY for educator + # expenses in EXCESS of the federal IRC § 62(a)(2)(D) cap (currently + # $300). The federal-cap portion is already deducted via federal AGI; + # adding the federal `educator_expense` here would double-count. This + # stub variable therefore replaces (not supplements) the federal + # educator deduction in oh/.../deductions.yaml. It remains an explicit + # input because the baseline data do not identify Ohio licensure or + # the excess-over-federal amount; filers without microdata input + # receive 0, which matches the federal cap behavior. + reference = ( + "https://codes.ohio.gov/ohio-revised-code/section-5747.01#A_31", # ORC 5747.01(A)(31): Ohio educator deduction + "https://dam.assets.ohio.gov/image/upload/v1767095693/tax.ohio.gov/forms/ohio_individual/individual/2025/it1040-booklet.pdf#page=25", # 2025 IT-1040 booklet, Ohio educator expense deduction + ) 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..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,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 + # 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 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 086420bef56..25643e85cab 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 @@ -9,15 +9,9 @@ class state_itemized_deductions(Variable): definition_period = YEAR 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 - ] + 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,14 +52,23 @@ 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_states) - # Get federal itemized deductions (sum of all federal itemized deduction components) - federal_itemized = add( + 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 32cdf244fd0..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,18 +9,9 @@ class state_standard_deduction(Variable): definition_period = YEAR 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 - ] + federal_standard_states = parameters( + period + ).gov.states.household.states_using_federal_standard_deduction # Get the sum of state-specific standard deductions state_specific = add( @@ -67,7 +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) + 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/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..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 @@ -14,25 +14,37 @@ class ut_federal_deductions_for_taxpayer_credit(Variable): 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 + 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 - if deduction not in ["salt_deduction"] + if deduction != "salt_deduction" ] - item_ded = add(tax_unit, period, deductions) - - # Include Real Estate Taxes in Itemized Deductions + 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] ) - - # 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) + deducted_state_income_or_sales_tax = max_( + salt_deduction - capped_real_estate_tax, 0 + ) + fallback_base = fallback_itemized + capped_real_estate_tax + 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, + itemized_base, + 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..8fb68e71fdf 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,6 +12,12 @@ 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 W-2 state wages + # (Box 16 per TC-40A worksheet), explicitly excluding self-employment + # earnings. W-2 Box 1 (irs_employment_income) is used as a proxy + # because PolicyEngine does not expose a state-wages variable; for + # most filers Box 16 and Box 1 are identical. 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, ["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 4a4a40d95e6..c499bafa4e5 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,10 @@ from policyengine_us.model_api import * +from policyengine_us.tools.state_eitc_helpers import ( + calculate_eitc_demographic_eligibility, + calculate_eitc_max_agi_limit, + eitc_filing_requirement_met, + eitc_filing_status_eligible, +) class wa_working_families_tax_credit(Variable): @@ -14,36 +20,115 @@ class wa_working_families_tax_credit(Variable): defined_for = StateCode.WA def formula(tax_unit, period, parameters): - # Baseline eligibility: filers who claim EITC - eitc = tax_unit("eitc", period) - eitc_eligible = eitc > 0 + p = parameters( + period + ).gov.states.wa.tax.income.credits.working_families_tax_credit + # RCW 82.08.0206(2)(d) pins Washington's WFTC to the federal EITC + # rules as in effect on June 9, 2022. The snapshot date is a + # statutory literal; policyengine-core parameters do not support + # date-valued types, so the date appears here rather than in the + # parameter tree. + 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) + 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 = 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 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_income_eligible = (earnings > 0) & ( + higher_income + <= calculate_eitc_max_agi_limit(tax_unit, period, frozen_eitc, child_count) + ) + 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) 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) - max_amount = p.amount.calc(eitc_child_count) + # WFTC child count is the larger of the federally-counted children + # (SSN-eligible) and Washington-counted children (TIN-eligible). + wftc_child_count = max_(federal_child_count, child_count) + max_amount = p.amount.calc(wftc_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) - phase_out_start_reduction = p.phase_out.start_below_eitc.calc(eitc_child_count) + eitc_agi_limit = calculate_eitc_max_agi_limit( + tax_unit, period, frozen_eitc, wftc_child_count + ) + phase_out_start_reduction = p.phase_out.start_below_eitc.calc(wftc_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 45a30b07bf3..392afdc12cb 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.tools.state_eitc_helpers import ( + calculate_eitc_amount_from_parameters, +) class wa_working_families_tax_credit_age_expansion_eligible(Variable): @@ -12,40 +15,47 @@ class wa_working_families_tax_credit_age_expansion_eligible(Variable): def formula(tax_unit, period, parameters): # ESSB 6346 Sec. 901(2)(a)(ii)(D): individuals who would otherwise # qualify for EITC except for age can qualify if at least age 18. + # + # Methodology note (age): Sec. 901(2)(a)(ii)(D) refers to age as of the + # prior tax year. PolicyEngine evaluates a single period, so this + # formula uses current-period age_head / age_spouse as an approximation. + # The off-by-one-year edge case (filer turning min_age during the tax + # year) is not modeled. + # + # No separate-filer (MFS) guard is applied here because the WFTC is a + # state-only credit that, by design, does not honor the federal MFS + # bar; the main wa_working_families_tax_credit variable's state-only + # path treats `separate` filers as eligible. p = parameters( period ).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), - ), + # RCW 82.08.0206(2)(d) pins WFTC to the federal EITC rules as in + # effect on June 9, 2022; this snapshot date is a statutory literal. + 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 + ) + eitc_amount_before_take_up = calculate_eitc_amount_from_parameters( + tax_unit, period, frozen_eitc, 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 - ) return ( expansion_in_effect & filer_meets_min_age - & investment_income_eligible - & filers_have_ssn + & frozen_investment_income_eligible + & 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..7a6fd30e58b 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 @@ -9,8 +9,20 @@ class head_of_household_eligible(Variable): reference = "https://www.law.cornell.edu/uscode/text/26/2#b" def formula(tax_unit, period, parameters): + # tax_unit_married is true iff a spouse is present in this tax unit. + # JOINT filers have the spouse in the same unit (married=true); + # MFS filers have the spouse in a separate unit (married=false). + # We use this signal to distinguish the IRC 7703(b) treated-unmarried + # path (MFS, married=false) from a stray is_separated=true on a + # JOINT filer (married=true), without reading filing_status and + # creating a circular dependency through filing_status -> hoh_eligible. married = tax_unit("tax_unit_married", period) person = tax_unit.members + # 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( @@ -25,8 +37,17 @@ 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. + # IRC 7703(b)(1) further requires the taxpayer to file a separate + # return: a JOINT filer who is separated cannot claim HoH because + # the joint return forecloses the "considered unmarried" path. + 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 + # ~married masks the JOINT-with-is_separated=true case: a JOINT + # filer cannot use IRC 7703(b)(1) because they have not filed a + # separate return. MFS filers (spouse in a different tax unit) have + # married=false and continue to qualify via this branch. + separated_qualifies = treated_unmarried_qualifies & is_separated & ~married + return (unmarried_qualifies | separated_qualifies) & ~surviving_spouse