Skip to content

OH refundable EITC reform (create_oh_refundable_eitc) pays zero refund and discards other non-refundable credits #8656

@PavelMakarchuk

Description

@PavelMakarchuk

Summary

The bundled Ohio refundable-EITC contrib reform (gov.contrib.states.oh.child_poverty_impact_dashboard.eitc.in_effect = true) runs without crashing but is functionally wrong on two fronts: it pays no refundable credit to filers with no OH tax liability (the exact case refundability is meant to help), and it discards Ohio's other six non-refundable credits in the process. Same dual-functional-bug profile as #8640 (MO) and #8644 (UT), just expressed differently because OH has no add() string crash.

Reproduce

from policyengine_us import Simulation
from policyengine_core.reforms import Reform

reform = Reform.from_dict({
    "gov.contrib.states.oh.child_poverty_impact_dashboard.eitc.in_effect": {"2026-01-01": True},
}, country_id="us")

sim = Simulation(
    situation={
        "people": {"head": {"age": {2026: 35}, "employment_income": {2026: 20000}}},
        "tax_units": {"tu": {"members": ["head"]}},
        "households": {"hh": {"members": ["head"], "state_name": {2026: "OH"}}},
    },
    reform=reform,
)
print(sim.calculate("oh_refundable_credits", period=2026))  # expected positive; actual 0

Bug 1 — refundable EITC capped at tax liability (silent zero-refund)

policyengine_us/reforms/states/oh/eitc/oh_refundable_eitc_reform.py:

class oh_refundable_eitc(Variable):
    def formula(tax_unit, period, parameters):
        return tax_unit("oh_eitc", period)

oh_eitc is the applied (nonrefundable-capped) EITC — applied_state_non_refundable_credit(..., "oh_eitc_potential"). A zero-liability filer's oh_eitc is 0, so the reform's oh_refundable_eitc is also 0 — no refund is paid even though the reform claims to make the credit refundable.

Fix: read oh_eitc_potential (which already exists at policyengine_us/variables/gov/states/oh/tax/income/credits/oh_eitc_potential.py and computes federal_eitc * rate). Same shape as the MO fix (mo_wftcmo_wftc_potential, see #8642) and the UT fix (ut_eitcut_eitc_potential, see #8645).

Bug 2 — discards Ohio's other non-refundable credits

class oh_non_refundable_credits(Variable):
    def formula(tax_unit, period, parameters):
        return tax_unit("oh_non_refundable_eitc", period)

Under the reform oh_non_refundable_eitc evaluates to 0, so the reform's oh_non_refundable_credits is unconditionally 0. The baseline walks the full ordered Ohio non-refundable credit list — currently seven entries:

2023-01-01:
  - oh_eitc
  - oh_cdcc
  - oh_senior_citizen_credit
  - oh_retirement_credit
  - oh_non_public_school_credits
  - oh_exemption_credit
  - oh_joint_filing_credit

The reform's intent is to move only oh_eitc out of the bucket; it should not erase the other six credits. As written, an OH filer claiming the CDCC, retirement, exemption, or joint-filing credit loses all of them whenever the reform is in effect.

Fix: mirror the UT pattern from #8645 — walk the same ordered list with oh_eitc filtered out:

from policyengine_us.variables.gov.states.tax.income.non_refundable_credit_cap import (
    ordered_capped_state_non_refundable_credits,
)
...
def formula(tax_unit, period, parameters):
    ordered_credits = parameters(period).gov.states.oh.tax.income.credits.non_refundable
    filtered_credits = [c for c in list(ordered_credits) if c != "oh_eitc"]
    return ordered_capped_state_non_refundable_credits(
        tax_unit, period, filtered_credits, "oh_income_tax_before_non_refundable_credits",
    )

Why this slipped through

Same test-shielding pattern as MO/UT: the existing contrib tests at policyengine_us/tests/policy/contrib/states/oh/child_poverty_impact_dashboard/eitc/oh_refundable_eitc.yaml pin oh_eitc: 500 as input and only assert on oh_refundable_eitc, oh_non_refundable_eitc, and a oh_non_refundable_credits case with no other credits present. No test drives eitc (federal), pins oh_income_tax_before_non_refundable_credits: 0, and asserts the refundable EITC pays out — which is exactly the case the reform is meant to model.

Expected behavior

Under the reform, a Utah-style "fully refundable EITC" semantics: filers receive the full oh_eitc_potential as a refundable credit (subject to the existing rate × federal-EITC formula), regardless of OH tax liability; and Ohio's other six non-refundable credits continue to apply normally.

Environment

  • policyengine-us==1.715.2 (and current main)
  • Python 3.10/3.11

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions