Skip to content

MO refundable EITC reform (create_mo_refundable_eitc) crashes when triggered #8640

@PavelMakarchuk

Description

@PavelMakarchuk

Summary

Triggering the bundled MO refundable-EITC contrib reform (gov.contrib.states.mo.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. Two distinct bugs in policyengine_us/reforms/states/mo/eitc/mo_refundable_eitc_reform.py are at play.

Reproduce

from policyengine_us import Simulation
from policyengine_core.reforms import Reform

reform = Reform.from_dict({
    "gov.contrib.states.mo.child_poverty_impact_dashboard.eitc.in_effect": {"2026-01-01": True},
    "gov.states.mo.tax.income.credits.wftc.match": {"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: "MO"}}},
    },
    reform=reform,
)
sim.calculate("household_net_income", period=2026)

Bug 1 — add() receives a parameter path string

mo_refundable_credits.formula calls:

other_refundable = add(
    tax_unit,
    period,
    "gov.states.mo.tax.income.credits.refundable",
)

policyengine_core.commons.formulas.add expects variables: List[str] and iterates it:

for variable in variables:
    variable_entity = entity.entity.get_variable(variable).entity

When variables is a string, Python 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():

refundable_list = parameters(period).gov.states.mo.tax.income.credits.refundable
other_refundable = add(tax_unit, period, refundable_list)

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

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

ValueError: Variable "mo_refundable_credits" mixes computation modes:
formula and adds/subtracts. Variables must use at most one of formula,
adds/subtracts, or uprating; plain input or constant variables should
use none.

mo_refundable_credits in the reform defines a formula but doesn't clear adds; the merge with the baseline (which has adds = "gov.states.mo.tax.income.credits.refundable") inherits the adds attribute. PE-core's strict computation-mode check rejects the combination.

PE-core 3.27.1 exempts baseline-inherited attributes from the check (changelog), so bumping core papers over bug 2. The proper fix is in this reform module: set adds = None (and subtracts = None) explicitly on any class that redeclares a formula via update_variable.

Expected behavior

The MO refundable-EITC reform should run without raising, converting the WFTC from nonrefundable to refundable.

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).

Environment

  • policyengine-us==1.715.2
  • policyengine-core>=3.26.8 (Bug 2); any version (Bug 1)
  • Python 3.11

Workaround (downstream)

We've applied a runtime monkey-patch in our Modal endpoint that replaces create_mo_refundable_eitc with a corrected version, and pinned policyengine-core>=3.27.1. See our PR for the workaround we ship from the caller side.

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