Fix OH refundable EITC reform zero-refund and lost-credit bugs#8657
Conversation
Mirrors the UT fix in #8645 for Ohio's analogous bundled reform. - oh_refundable_eitc: pay the uncapped potential credit (oh_eitc_potential) instead of the tax-liability-capped oh_eitc so the reform delivers a refund to zero-liability filers (the case refundability is meant to help). - oh_non_refundable_credits: replace the formula that returned only oh_non_refundable_eitc (= 0) — which silently discarded Ohio's other six non-refundable credits — with ordered_capped_state_non_refundable_credits on the same ordered list, filtering out oh_eitc since it is paid as refundable here. Updates the contrib tests to drive from federal eitc and pin oh_income_tax_before_non_refundable_credits, mirroring the UT pattern, and adds a new regression test verifying that other OH non-refundable credits (e.g. oh_joint_filing_credit) still apply under the reform. Fixes #8656 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add 2023/2024/2025 OH Schedule of Credits references on
oh_refundable_credits and oh_non_refundable_credits to match the
baseline non_refundable.yaml parameter.
- Note in the docstring that this reform departs from ORC § 5747.71
(which makes the credit nonrefundable) — it is a contrib/what-if
module, not a baseline change.
- Add two reform tests for previously-untested edges of the ordered
cap walk: partial-absorption (refundable EITC paid in full while
other credits consume the smaller remaining liability) and a
multi-credit binding case (CDCC + exemption + joint-filing
competing for liability, total capped at remaining liability).
- Tighten test comment ("OH EITC rate is 30%, constant since 2020"
instead of "2024 OH EITC rate") and changelog wording ("zeroed out"
instead of "discarded").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add an oh_income_tax end-to-end refund assertion to the zero-liability reform test, confirming the refund actually pays out (not just lands in the refundable bucket). - Document the HB 62 (eff. 2019-07-03) cap repeal in the reform docstring, explaining why reading oh_eitc_potential is the full refundable amount for the modeled era (ORC 5747.71). - Correct the "six other credits" wording in the code comment and test header to "every other entry in the ordered list" — the 2021-2022 list also carries oh_adoption_credit (repealed 2023). The formula was already correct since it filters the live period-specific list. - Swap the non-OH negative test from CA to TX. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Reviewed locally with a multi-validator pass — regulatory, references, code patterns, test coverage, plus a deep dataflow trace run live in a Correctness verified
Follow-up commit (polish only, 3ba79b6)
Reform tests (4) and contrib tests (4) pass locally; leaving CI to confirm. |
PR Review — #8657: Fix OH refundable EITC reform zero-refund and lost-credit bugsThe Ohio member of a three-PR family of state refundable-EITC reform fixes (alongside #8642 MO and #8645 UT). Reviewed read-only for correctness, code patterns, and test coverage. Both claimed bugs are genuine; both fixes verified correct, single-counted end-to-end; CI fully green (26/26). 🔴 Critical (Must Fix)None. 🟡 Should AddressNone blocking. 🟢 Suggestions
Findings detail
Validation Summary
Review Severity: APPROVEBoth bugs are genuine, both fixes correct and consistent with the baseline and the MO/UT siblings, single-counting verified end-to-end, thorough test coverage, CI fully green. No required changes. |
Fixes #8656
Problem
The bundled Ohio refundable-EITC contrib reform (
gov.contrib.states.oh.child_poverty_impact_dashboard.eitc.in_effect) ran without crashing but was functionally wrong on two fronts — same shape as the MO and UT bugs fixed in #8642 and #8645, but noadd()string crash:Zero refund at zero liability.
oh_refundable_eitc.formulareturnedtax_unit("oh_eitc", period)— the applied nonrefundable-capped EITC. A zero-liability filer'soh_eitcis0, so the reform'soh_refundable_eitcwas also0— no refund paid in exactly the case refundability is meant to address.Other Ohio non-refundable credits discarded.
oh_non_refundable_credits.formulareturned justtax_unit("oh_non_refundable_eitc", period)(= 0 under the reform), silently zeroing out every other non-refundable credit walked by the baseline ordered list (oh_cdcc,oh_senior_citizen_credit,oh_retirement_credit,oh_non_public_school_credits,oh_exemption_credit,oh_joint_filing_credit, plusoh_adoption_creditfor pre-2023 years). An OH filer claiming any of those lost them all whenever the reform was active.Fixes in
oh_refundable_eitc_reform.pyoh_refundable_eitcnow readsoh_eitc_potential(uncapped at liability; OH does not apply a separate cap layer above the potential, so this is the full refundable amount). Mirrors the MOmo_wftc→mo_wftc_potentialchange in #8642 and the UTut_eitc→ut_eitc_potentialchange in #8645.oh_non_refundable_creditsnow walks the same ordered list the baseline uses (gov.states.oh.tax.income.credits.non_refundable) viaordered_capped_state_non_refundable_credits, withoh_eitcfiltered out of the list since the reform pays it as refundable. Identical pattern to the UT fix.Verification
eitcandoh_income_tax_before_non_refundable_creditsrather than injecting cappedoh_eitc, in line with the new uncapped behaviour).tests/policy/reform/oh_refundable_eitc.yamladds four regression tests: full refundable payout at zero liability (assertingoh_income_taxgoes negative, i.e. the refund is actually paid); confirmation thatoh_joint_filing_creditstill applies under the reform (proves the other credits are no longer discarded); no double-counting when liability partially absorbs other credits; and the filtered ordered walk capping multiple non-EITC credits at remaining liability.test_non_refundable_credit_downstream_consumers.pyinvariant still passes (the reform's reference tooh_eitcin the filter is unchanged).Why this slipped through
Same test-shielding pattern as MO/UT — see the OH stanza of the bug-pattern analysis in the parent issue. The existing contrib tests pinned
oh_eitc: 500as input and only checkedoh_refundable_eitc/oh_non_refundable_eitc/oh_non_refundable_creditswith no other OH credits present, which dodged both broken code paths.🤖 Generated with Claude Code