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
1 change: 1 addition & 0 deletions changelog.d/fix-mo-refundable-eitc-reform-crash.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed the Missouri refundable EITC contrib reform, which crashed at calculation time and paid no refundable credit to filers with no Missouri tax liability.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ class mo_refundable_wftc(Variable):
defined_for = StateCode.MO

def formula(tax_unit, period, parameters):
return tax_unit("mo_wftc", period)
# Use the potential (uncapped) WFTC so the full credit is paid as
# a refund; `mo_wftc` is capped at tax liability and would zero out
# the credit for the low-liability filers refundability is meant
# to help.
return tax_unit("mo_wftc_potential", period)

class mo_non_refundable_wftc(Variable):
value_type = float
Expand All @@ -43,7 +47,12 @@ class mo_non_refundable_credits(Variable):
defined_for = StateCode.MO

def formula(tax_unit, period, parameters):
# Include the nonrefundable WFTC (0 when reform is in effect)
# Today `gov.states.mo.tax.income.credits.non_refundable` is
# just `[mo_wftc]`, so returning the (zeroed) reform replacement
# is equivalent to the baseline. If Missouri later adds a second
# nonrefundable credit, this formula would silently drop it —
# at that point switch to summing the full nonrefundable list
# with `mo_wftc` filtered out, mirroring the UT/OH fix pattern.
return tax_unit("mo_non_refundable_wftc", period)

class mo_refundable_credits(Variable):
Expand All @@ -53,14 +62,19 @@ class mo_refundable_credits(Variable):
unit = USD
definition_period = YEAR
defined_for = StateCode.MO
# 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):
# Standard refundable credits
other_refundable = add(
tax_unit,
period,
"gov.states.mo.tax.income.credits.refundable",
)
# Standard refundable credits, resolved to the list of variable
# names before passing to `add` (which iterates variable names).
refundable_credits = parameters(
period
).gov.states.mo.tax.income.credits.refundable
other_refundable = add(tax_unit, period, refundable_credits)
# Add refundable WFTC (positive when reform is in effect)
refundable_wftc = tax_unit("mo_refundable_wftc", period)
return other_refundable + refundable_wftc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@
"md_non_refundable_eitc": {
"variables/gov/states/md/tax/income/credits/eitc/md_eitc.py",
},
"mo_wftc": {
"reforms/states/mo/eitc/mo_refundable_eitc_reform.py",
},
"ny_household_credit": {
"reforms/states/ny/wftc/ny_working_families_tax_credit.py",
"variables/gov/states/ny/tax/income/credits/ny_eitc.py",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
- name: Case 1 - MO WFTC is refundable when reform active
# The reform makes the Missouri WFTC fully refundable. It pays the uncapped
# potential credit (mo_wftc_potential), so these tests drive the credit from
# the federal EITC rather than injecting the capped mo_wftc. The 2024 WFTC
# match is 20% of the federal EITC.

- name: Case 1 - MO WFTC is fully refundable (uncapped) when reform active
period: 2024
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc
input:
state_code: MO
mo_wftc: 500
eitc: 2_500
output:
mo_refundable_wftc: 500
mo_wftc_potential: 500 # 2,500 * 0.2
mo_wftc: 0 # capped at zero tax liability
mo_refundable_wftc: 500 # full potential paid as a refund
mo_non_refundable_wftc: 0

- name: Case 2 - MO WFTC is 0 when WFTC is 0
- name: Case 2 - MO refundable WFTC is 0 when the federal EITC is 0
period: 2024
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc
input:
state_code: MO
mo_wftc: 0
eitc: 0
output:
mo_refundable_wftc: 0
mo_non_refundable_wftc: 0
Expand All @@ -23,7 +30,7 @@
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc
input:
state_code: CA
mo_wftc: 500
eitc: 2_500
output:
mo_refundable_wftc: 0
mo_non_refundable_wftc: 0
Expand All @@ -33,6 +40,6 @@
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc
input:
state_code: MO
mo_wftc: 500
eitc: 2_500
output:
mo_non_refundable_credits: 0
93 changes: 93 additions & 0 deletions policyengine_us/tests/policy/reform/mo_refundable_eitc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Tests for the Missouri refundable EITC (WFTC) contrib reform.
# Regression coverage for https://github.com/PolicyEngine/policyengine-us/issues/8640
# (the reform previously crashed at calculation time and, once running, paid no
# refundable credit to zero-liability filers).

- name: Reform pays the full potential WFTC as a refundable credit at zero liability
absolute_error_margin: 0.01
period: 2026
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc_reform.mo_refundable_eitc
input:
state_code: MO
eitc: 5_000
output:
# 2026 WFTC match is 20% of the federal EITC: 5,000 * 0.2 = 1,000.
mo_wftc_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.
mo_wftc: 0
# WFTC is moved out of the nonrefundable bucket...
mo_non_refundable_wftc: 0
mo_non_refundable_credits: 0
# ...and paid in full as refundable, even with no Missouri tax liability.
mo_refundable_wftc: 1_000
mo_refundable_credits: 1_000

- name: Reform does not double count when liability already absorbs the WFTC
absolute_error_margin: 0.01
period: 2026
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc_reform.mo_refundable_eitc
input:
state_code: MO
eitc: 5_000
mo_income_tax_before_credits: 1_000
output:
# Full potential paid as refundable; nonrefundable portion stays zero.
mo_refundable_wftc: 1_000
mo_non_refundable_wftc: 0
mo_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.
mo_income_tax_before_refundable_credits: 1_000
mo_income_tax: 0

- name: Reform preserves other Missouri refundable credits (does not drop them)
absolute_error_margin: 0.01
period: 2026
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc_reform.mo_refundable_eitc
input:
state_code: MO
eitc: 5_000
# The other Missouri refundable credit in
# `gov.states.mo.tax.income.credits.refundable` is mo_property_tax_credit.
# Pin it to a positive value: the reform's mo_refundable_credits formula
# must sum it alongside mo_refundable_wftc, not replace it.
mo_property_tax_credit: 300
output:
mo_refundable_wftc: 1_000
# 1,000 (refundable WFTC) + 300 (property tax credit) = 1,300.
mo_refundable_credits: 1_300

- name: Reform pays zero when the federal EITC is zero (zero-EITC boundary)
absolute_error_margin: 0.01
period: 2026
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc_reform.mo_refundable_eitc
input:
state_code: MO
eitc: 0
output:
mo_wftc_potential: 0
mo_refundable_wftc: 0
mo_refundable_credits: 0

- name: Reform pays in full when liability only partially absorbs the WFTC
absolute_error_margin: 0.01
period: 2026
reforms: policyengine_us.reforms.states.mo.eitc.mo_refundable_eitc_reform.mo_refundable_eitc
input:
state_code: MO
eitc: 5_000
# Potential WFTC = 1,000. Liability of 400 would absorb only part of the
# baseline (nonrefundable-capped) WFTC; the reform should still pay the
# full uncapped potential as refundable.
mo_income_tax_before_credits: 400
output:
mo_wftc_potential: 1_000
mo_refundable_wftc: 1_000
mo_refundable_credits: 1_000
# The full $1,000 refundable credit flows through against $400 of
# liability, producing a $600 refund. mo_income_tax goes negative
# (refund) by exactly the surplus — proves no double-counting (would
# be -$1,000 if the credit were applied twice).
mo_income_tax: -600
Loading