Skip to content

UT fully refundable EITC reform (create_ut_fully_refundable_eitc) crashes when triggered #8644

@PavelMakarchuk

Description

@PavelMakarchuk

Summary

Triggering the bundled Utah fully-refundable-EITC contrib reform (gov.contrib.states.ut.child_poverty_impact_dashboard.eitc.in_effect = true) on policyengine-us==1.715.2 raises AttributeError: 'NoneType' object has no attribute 'entity' at calculation time. Same shape as #8640 (MO) — Utah carries the same two bugs.

Reproduce

from policyengine_us import Simulation
from policyengine_core.reforms import Reform

reform = Reform.from_dict({
    "gov.contrib.states.ut.child_poverty_impact_dashboard.eitc.in_effect": {"2026-01-01": True},
    "gov.states.ut.tax.income.credits.earned_income.rate": {"2026-01-01": 0.3},
}, country_id="us")

sim = Simulation(
    situation={
        "people": {"head": {"age": {2026: 35}, "employment_income": {2026: 40000}}},
        "tax_units": {"tu": {"members": ["head"]}},
        "households": {"hh": {"members": ["head"], "state_name": {2026: "UT"}}},
    },
    reform=reform,
)
sim.calculate("household_net_income", period=2026)

Bug 1 — add() receives a parameter path string

policyengine_us/reforms/states/ut/child_poverty_eitc/ut_fully_refundable_eitc_reform.py:49
ut_non_refundable_credits.formula calls:

baseline_non_refundable = add(
    tax_unit,
    period,
    "gov.states.ut.tax.income.credits.non_refundable",
)

policyengine_core.commons.formulas.add expects variables: List[str] and iterates it; when handed a string it iterates character-by-character ("g", "o", "v", …) and get_variable("g") returns None, producing AttributeError: 'NoneType' object has no attribute 'entity'.

Fix: resolve the parameter to its list value before handing it to add():

non_refundable_list = parameters(period).gov.states.ut.tax.income.credits.non_refundable
baseline_non_refundable = add(tax_unit, period, non_refundable_list)

Bug 2 — strict-check ValueError on PE-core 3.26.8 – 3.27.0

Before bug 1 surfaces, on policyengine-core 3.26.8 – 3.27.0 you hit:

ValueError: Variable "ut_refundable_credits" mixes computation modes:
formula and adds/subtracts.

The reform redeclares ut_refundable_credits (and ut_non_refundable_credits) with a formula, but the merge with the baseline inherits adds/subtracts. PE-core 3.27.1 papers over this with an inheritance-aware check, but the reform should still clear adds = None (and subtracts = None if applicable) on each redeclared class so it doesn't depend on PE-core leniency.

Bug 3 (suspected) — refundable EITC capped at tax liability

ut_fully_refundable_eitc.formula returns tax_unit("ut_eitc", period), but ut_eitc is the applied (nonrefundable-capped) credit. A zero-liability filer would receive $0 even though the reform is meant to pay the full credit. Suggest using a ut_eitc_potential (or equivalent uncapped variable) — same fix shape as the MO PR (#8642) switching mo_wftcmo_wftc_potential. Worth verifying whether such a variable exists in PE-US; if not, computing the uncapped EITC inline.

Expected behavior

The UT fully-refundable EITC reform should run without raising, and should pay the full UT EITC as a refundable credit (not capped at UT income tax liability).

Impact

Any caller setting the in_effect flag — including the child-poverty-impact-dashboard — crashes the simulation entirely (Bug 2) or silently produces household_net_income = 0 after the calling code swallows the AttributeError (Bug 1). The same dual-bug profile as MO (#8640).

Environment

  • policyengine-us==1.715.2
  • policyengine-core>=3.26.8 (Bug 2); any version (Bug 1)
  • Python 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