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.
Summary
Triggering the bundled MO refundable-EITC contrib reform (
gov.contrib.states.mo.child_poverty_impact_dashboard.eitc.in_effect = true) onpolicyengine-us==1.715.2raisesAttributeError: 'NoneType' object has no attribute 'entity'at calculation time. Two distinct bugs inpolicyengine_us/reforms/states/mo/eitc/mo_refundable_eitc_reform.pyare at play.Reproduce
Bug 1 —
add()receives a parameter path stringmo_refundable_credits.formulacalls:policyengine_core.commons.formulas.addexpectsvariables: List[str]and iterates it:When
variablesis a string, Python iterates character-by-character ("g","o","v",".", …) andget_variable("g")returnsNone, producingAttributeError: 'NoneType' object has no attribute 'entity'.Fix: resolve the parameter to its list value before handing it to
add():Bug 2 — strict-check
ValueErroron PE-core 3.26.8 – 3.27.0Before bug 1 even surfaces, on
policyengine-core3.26.8 through 3.27.0 you hit:mo_refundable_creditsin the reform defines aformulabut doesn't clearadds; the merge with the baseline (which hasadds = "gov.states.mo.tax.income.credits.refundable") inherits theaddsattribute. 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(andsubtracts = None) explicitly on any class that redeclares aformulaviaupdate_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_effectflag — including the child-poverty-impact-dashboard — crashes the simulation entirely (Bug 2) or silently produceshousehold_net_income = 0after the calling code swallows theAttributeError(Bug 1).Environment
policyengine-us==1.715.2policyengine-core>=3.26.8(Bug 2); any version (Bug 1)Workaround (downstream)
We've applied a runtime monkey-patch in our Modal endpoint that replaces
create_mo_refundable_eitcwith a corrected version, and pinnedpolicyengine-core>=3.27.1. See our PR for the workaround we ship from the caller side.