Skip to content

Commit 80db97d

Browse files
MaxGhenisclaude
andauthored
Upgrade breakdown enum mismatch from WARNING to ERROR (#449)
Parameters with keys not in the breakdown variable's possible values now raise ValueError instead of silently logging a warning. This prevents data loss when parameter YAML files use keys that don't match the breakdown enum (e.g., using snap_utility_region keys with a state_code breakdown). Partial coverage (enum values missing from YAML) remains allowed. Closes #444 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ff93ef4 commit 80db97d

File tree

3 files changed

+115
-10
lines changed

3 files changed

+115
-10
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade breakdown enum mismatch from WARNING to ValueError for early detection of parameter key errors.

policyengine_core/parameters/operations/homogenize_parameters.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,24 @@ def homogenize_parameter_node(
9696
{"0000-01-01": default_value, "2040-01-01": default_value},
9797
),
9898
)
99+
possible_values_str = {str(v) for v in possible_values}
100+
extra_children = []
101+
for child in node.children:
102+
child_key = child.split(".")[-1]
103+
if (
104+
child_key not in possible_values_str
105+
and str(child_key) not in possible_values_str
106+
):
107+
extra_children.append(child_key)
108+
if extra_children:
109+
raise ValueError(
110+
f"Parameter {node.name} has children {extra_children} "
111+
f"that are not in the possible values of the breakdown "
112+
f"variable '{first_breakdown}'. Check that the breakdown "
113+
f"metadata references the correct variable and that all "
114+
f"parameter keys are valid enum values."
115+
)
99116
for child in node.children:
100-
if child.split(".")[-1] not in possible_values:
101-
try:
102-
int(child)
103-
is_int = True
104-
except:
105-
is_int = False
106-
if not is_int or str(child) not in node.children:
107-
logging.warning(
108-
f"Parameter {node.name} has a child {child} that is not in the possible values of {first_breakdown}, ignoring."
109-
)
110117
if further_breakdown:
111118
node.children[child] = homogenize_parameter_node(
112119
node.children[child], breakdown[1:], variables, default_value

tests/core/parameters/operations/test_nesting.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import pytest
2+
3+
14
def test_parameter_homogenization():
25
import numpy as np
36

@@ -90,3 +93,97 @@ class family_size(Variable):
9093
]
9194
== [1, 0, 0]
9295
).all()
96+
97+
98+
def test_breakdown_mismatch_raises_error():
99+
"""Extra parameter keys not in the breakdown enum should raise ValueError."""
100+
from policyengine_core.entities import Entity
101+
from policyengine_core.model_api import ETERNITY, Enum, Variable
102+
from policyengine_core.parameters import (
103+
ParameterNode,
104+
homogenize_parameter_structures,
105+
)
106+
from policyengine_core.taxbenefitsystems import TaxBenefitSystem
107+
108+
Person = Entity("person", "people", "Person", "A person")
109+
110+
class Color(Enum):
111+
RED = "Red"
112+
BLUE = "Blue"
113+
114+
class color(Variable):
115+
value_type = Enum
116+
entity = Person
117+
definition_period = ETERNITY
118+
possible_values = Color
119+
default_value = Color.RED
120+
label = "color"
121+
122+
# "GREEN" is not in the Color enum — should raise ValueError
123+
root = ParameterNode(
124+
data={
125+
"value_by_color": {
126+
"RED": {"2021-01-01": 1},
127+
"GREEN": {"2021-01-01": 2},
128+
"metadata": {
129+
"breakdown": ["color"],
130+
},
131+
}
132+
}
133+
)
134+
135+
system = TaxBenefitSystem([Person])
136+
system.add_variables(color)
137+
system.parameters = root
138+
139+
with pytest.raises(ValueError, match="GREEN"):
140+
homogenize_parameter_structures(
141+
system.parameters, system.variables, default_value=0
142+
)
143+
144+
145+
def test_breakdown_partial_coverage_is_ok():
146+
"""Missing enum values in parameter YAML should NOT raise an error."""
147+
from policyengine_core.entities import Entity
148+
from policyengine_core.model_api import ETERNITY, Enum, Variable
149+
from policyengine_core.parameters import (
150+
ParameterNode,
151+
homogenize_parameter_structures,
152+
)
153+
from policyengine_core.taxbenefitsystems import TaxBenefitSystem
154+
155+
Person = Entity("person", "people", "Person", "A person")
156+
157+
class Color(Enum):
158+
RED = "Red"
159+
BLUE = "Blue"
160+
GREEN = "Green"
161+
162+
class color(Variable):
163+
value_type = Enum
164+
entity = Person
165+
definition_period = ETERNITY
166+
possible_values = Color
167+
default_value = Color.RED
168+
label = "color"
169+
170+
# Only RED is present — BLUE and GREEN are missing but that's fine
171+
root = ParameterNode(
172+
data={
173+
"value_by_color": {
174+
"RED": {"2021-01-01": 1},
175+
"metadata": {
176+
"breakdown": ["color"],
177+
},
178+
}
179+
}
180+
)
181+
182+
system = TaxBenefitSystem([Person])
183+
system.add_variables(color)
184+
system.parameters = root
185+
186+
# Should not raise — partial coverage is allowed
187+
homogenize_parameter_structures(
188+
system.parameters, system.variables, default_value=0
189+
)

0 commit comments

Comments
 (0)