You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bare date keys apply for a single instant, silently.{"param": {"2026-01-01": 0.39}} parses via period_("2026-01-01") and updates the parameter only at that instant — every later year reads the baseline value again. No warning. Callers almost never mean this: the natural reading (and what downstream wrappers document) is "from this date onward." This produced silently-wrong multi-year revenue estimates downstream (Open-ended reforms apply only to the start year in microsimulation (bare-date period keys) policyengine.py#453: a 10-year window scored the reform's start year correctly and exactly $0.00 for the nine later years).
The bare except: swallows everything from the first branch — not just period-parse failures but errors raised by parameter.update itself (bad value type, immutable parameter, etc.), which then re-enter the range-split path and surface as an unrelated secondary error (or, if the key happens to contain a ., silently take the wrong code path).
Preferred: treat a bare date key as open-ended (value applies from that instant onward), matching the scalar branch's forever semantics (period="year:2000:100") and the intuitive reading. If changing semantics is too risky for existing callers, emit a loud warning when a bare date is applied as a single instant.
Independently: narrow except: to the period-parse exception so parameter.update errors surface directly.
Downstream, policyengine.py now serialises explicit start.stop ranges for all reforms (PolicyEngine/policyengine.py#454), so this is hardening for direct core users and other wrappers.
Problem
Reform.from_dict(policyengine_core/reforms/reform.py) has two footguns in its period-key handling:Bare date keys apply for a single instant, silently.
{"param": {"2026-01-01": 0.39}}parses viaperiod_("2026-01-01")and updates the parameter only at that instant — every later year reads the baseline value again. No warning. Callers almost never mean this: the natural reading (and what downstream wrappers document) is "from this date onward." This produced silently-wrong multi-year revenue estimates downstream (Open-ended reforms apply only to the start year in microsimulation (bare-date period keys) policyengine.py#453: a 10-year window scored the reform's start year correctly and exactly $0.00 for the nine later years).The bare
except:swallows everything from the first branch — not just period-parse failures but errors raised byparameter.updateitself (bad value type, immutable parameter, etc.), which then re-enter the range-split path and surface as an unrelated secondary error (or, if the key happens to contain a., silently take the wrong code path).Repro (~30 s, no data)
Suggested fixes
period="year:2000:100") and the intuitive reading. If changing semantics is too risky for existing callers, emit a loud warning when a bare date is applied as a single instant.except:to the period-parse exception soparameter.updateerrors surface directly.Downstream, policyengine.py now serialises explicit
start.stopranges for all reforms (PolicyEngine/policyengine.py#454), so this is hardening for direct core users and other wrappers.