Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed the Utah fully refundable EITC contrib reform, which crashed at calculation time and paid no refundable credit to filers with no Utah tax liability.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_
from policyengine_us.variables.gov.states.tax.income.non_refundable_credit_cap import (
ordered_capped_state_non_refundable_credits,
)


def create_ut_fully_refundable_eitc() -> Reform:
Expand All @@ -21,7 +24,12 @@ class ut_fully_refundable_eitc(Variable):
defined_for = StateCode.UT

def formula(tax_unit, period, parameters):
return tax_unit("ut_eitc", period)
# Use the potential (uncapped) UT EITC so the full credit is paid
# as a refund; `ut_eitc` is capped at tax liability and would zero
# out the credit for the low-liability filers refundability is
# meant to help. `ut_eitc_potential` still applies the W-2 wages
# cap mandated by Utah Code § 59-10-1044.
return tax_unit("ut_eitc_potential", period)

class ut_non_refundable_eitc(Variable):
value_type = float
Expand All @@ -44,18 +52,24 @@ class ut_non_refundable_credits(Variable):
defined_for = StateCode.UT

def formula(tax_unit, period, parameters):
# Use parameter-driven approach: get baseline non-refundable credits
# then subtract ut_eitc (now refundable) and add back ut_non_refundable_eitc (0)
baseline_non_refundable = add(
# Mirror the baseline's ordered-cap logic but drop ut_eitc from
# the non-refundable bucket — it's paid as refundable under this
# reform. A raw `sum - ut_eitc` instead of the ordered walk would
# overstate the non-refundable total whenever the bucket binds at
# liability (later credits in the ordered list would no longer
# see the EITC's slot freed correctly).
ordered_credits = parameters(
period
).gov.states.ut.tax.income.credits.non_refundable
filtered_credits = [
credit for credit in list(ordered_credits) if credit != "ut_eitc"
]
return ordered_capped_state_non_refundable_credits(
tax_unit,
period,
"gov.states.ut.tax.income.credits.non_refundable",
filtered_credits,
"ut_income_tax_before_non_refundable_credits",
)
# Remove ut_eitc from non-refundable (it's now handled separately)
ut_eitc = tax_unit("ut_eitc", period)
# Add back nonrefundable EITC (0 when reform is in effect)
nonrefundable_eitc = tax_unit("ut_non_refundable_eitc", period)
return baseline_non_refundable - ut_eitc + nonrefundable_eitc

class ut_refundable_credits(Variable):
value_type = float
Expand All @@ -64,6 +78,11 @@ class ut_refundable_credits(Variable):
unit = USD
definition_period = YEAR
defined_for = StateCode.UT
# The baseline variable computes via `adds`. We replace it with a
# formula, so clear the inherited computation modes to avoid mixing
# `formula` with `adds`/`subtracts` (rejected by the core engine).
adds = None
subtracts = None

def formula(tax_unit, period, parameters):
# Add the fully refundable EITC (positive when reform is in effect)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
- name: Case 1 - UT EITC is fully refundable when reform active
# The reform makes the Utah EITC fully refundable. It pays the uncapped
# potential credit (ut_eitc_potential), so these tests drive the credit from
# the federal EITC rather than injecting the capped ut_eitc. The 2024 UT EITC
# rate is 20% of the federal EITC; the credit is also capped at W-2 wages
# (Utah Code § 59-10-1044), so the head's employment income is set high
# enough not to bind.

- name: Case 1 - UT EITC is fully refundable (uncapped) when reform active
period: 2024
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc
input:
state_code: UT
ut_eitc: 500
eitc: 2_500
ut_income_tax_before_non_refundable_credits: 0
employment_income: 30_000
output:
ut_fully_refundable_eitc: 500
ut_eitc_potential: 500 # 2,500 * 0.2
ut_eitc: 0 # capped at zero tax liability
ut_fully_refundable_eitc: 500 # full potential paid as a refund
ut_non_refundable_eitc: 0

- name: Case 2 - UT refundable credits include EITC when reform active
period: 2024
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc
input:
state_code: UT
ut_eitc: 1_000
eitc: 5_000
employment_income: 30_000
output:
ut_fully_refundable_eitc: 1_000
ut_refundable_credits: 1_000
Expand All @@ -23,17 +35,18 @@
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc
input:
state_code: UT
ut_eitc: 300
eitc: 1_500
employment_income: 30_000
output:
ut_fully_refundable_eitc: 300
ut_refundable_credits: 300

- name: Case 4 - UT EITC is 0 when EITC is 0
- name: Case 4 - UT refundable EITC is 0 when the federal EITC is 0
period: 2024
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc
input:
state_code: UT
ut_eitc: 0
eitc: 0
output:
ut_fully_refundable_eitc: 0
ut_refundable_credits: 0
Expand All @@ -43,6 +56,7 @@
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc
input:
state_code: CA
ut_eitc: 500
eitc: 2_500
employment_income: 30_000
output:
ut_fully_refundable_eitc: 0
78 changes: 78 additions & 0 deletions policyengine_us/tests/policy/reform/ut_fully_refundable_eitc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Tests for the Utah fully refundable EITC contrib reform.
# Regression coverage for https://github.com/PolicyEngine/policyengine-us/issues/8644
# (the reform previously crashed at calculation time and, once running, paid no
# refundable credit to zero-liability filers).

- name: Reform pays the full potential UT EITC as a refundable credit at zero liability
period: 2026
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc_reform.ut_fully_refundable_eitc
input:
state_code: UT
eitc: 5_000
# Pin UT tax liability to zero so the nonrefundable cap binds at $0 —
# the test is whether the reform pays the credit despite the cap.
ut_income_tax_before_non_refundable_credits: 0
# Set wages comfortably above the UT EITC potential so the W-2 wages cap
# (Utah Code § 59-10-1044) does not bind.
employment_income: 30_000
output:
# 2026 UT EITC rate is 20% of the federal EITC: 5,000 * 0.2 = 1,000.
ut_eitc_potential: 1_000
# The capped credit is 0 at zero liability; the fix pays the uncapped
# potential instead, so this contrast is the whole point of the reform.
ut_eitc: 0
# EITC is moved out of the nonrefundable bucket...
ut_non_refundable_eitc: 0
# ...and paid in full as refundable, even with no Utah tax liability.
ut_fully_refundable_eitc: 1_000
ut_refundable_credits: 1_000

- name: Reform does not double count when liability already absorbs the EITC
period: 2026
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc_reform.ut_fully_refundable_eitc
input:
state_code: UT
eitc: 5_000
employment_income: 30_000
ut_income_tax_before_non_refundable_credits: 1_000
output:
# Full potential paid as refundable; nonrefundable EITC portion stays zero.
ut_fully_refundable_eitc: 1_000
ut_non_refundable_eitc: 0
ut_refundable_credits: 1_000
# The $1,000 liability is offset exactly once by the refundable credit:
# 1,000 before refundable credits - 1,000 refundable = 0. If the credit
# were double counted this would go negative.
ut_income_tax: 0

- name: W-2 wages cap (Utah Code § 59-10-1044) still binds under the reform
period: 2026
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc_reform.ut_fully_refundable_eitc
input:
state_code: UT
eitc: 5_000
# Wages below the 20%-of-federal-EITC potential, so the wages cap binds.
employment_income: 400
ut_income_tax_before_non_refundable_credits: 0
output:
# Potential = min(0.2 * 5,000, 400) = 400. Reform pays the wages-capped
# potential as refundable, NOT the full 1,000 — § 59-10-1044 still applies.
ut_eitc_potential: 400
ut_fully_refundable_eitc: 400
ut_refundable_credits: 400

- name: Self-employment-only filer receives no refundable EITC (no W-2 wages)
period: 2026
reforms: policyengine_us.reforms.states.ut.child_poverty_eitc.ut_fully_refundable_eitc_reform.ut_fully_refundable_eitc
input:
state_code: UT
eitc: 5_000
employment_income: 0
self_employment_income: 30_000
ut_income_tax_before_non_refundable_credits: 0
output:
# § 59-10-1044 explicitly excludes self-employment earnings from the cap;
# zero W-2 wages → zero potential credit → zero refundable credit.
ut_eitc_potential: 0
ut_fully_refundable_eitc: 0
ut_refundable_credits: 0
Loading