mcp / tests /test_formula_validation.py
mikeboone's picture
fix(dataset): derived formulas fail loudly instead of generating zeros
fb35807
Raw
History Blame Contribute Delete
5.39 kB
"""
Derived-measure formulas must fail loudly, not generate zeros.
Regression: `_eval_formula` caught every exception and returned 0.0, and
`_eval_node` resolved an unknown identifier to 0.0. So a formula whose column
name did not match — `REVENUE - COGS` when the measures are actually
TOTAL_REVENUE / TOTAL_COGS — evaluated to `0 - 0 = 0` for every single row.
The column loaded 100% zero, the liveboard tile rendered blank, and the run
died much later in the post-load quality gate with no indication of WHICH
formula was wrong. Yodeck failed that gate with 12/12 dead derived columns and
no retry — the run just ended.
Formulas are now validated before a single row is generated, and every problem
is reported at once.
Run with: pytest tests/test_formula_validation.py -v
"""
import pytest
from demoprep_app.dataset.engine import (
FormulaError,
_eval_formula,
validate_fact_formulas,
)
from demoprep_app.scenario.blueprint import BlueprintMeasure, FactSpec
def fact(*measures, name="FACT_SALES"):
return FactSpec(name=name, grain="one row per order",
dimension_names=[], measures=list(measures))
def base(name):
return BlueprintMeasure(name=name, kind="base")
def derived(name, formula):
return BlueprintMeasure(name=name, kind="derived", formula=formula)
class TestTheYodeckCondition:
def test_typod_measure_name_is_reported_not_zeroed(self):
"""The exact defect: formula names don't match the measures."""
spec = fact(
base("TOTAL_REVENUE"),
base("TOTAL_COGS"),
derived("GROSS_MARGIN", "REVENUE - COGS"),
)
with pytest.raises(FormulaError) as exc:
validate_fact_formulas(spec)
msg = str(exc.value)
assert "GROSS_MARGIN" in msg
assert "'REVENUE'" in msg
assert "unknown" in msg
def test_suggests_the_intended_measure(self):
spec = fact(
base("TOTAL_REVENUE"),
derived("NET", "TOTAL_REVENUEE * 2"),
)
with pytest.raises(FormulaError) as exc:
validate_fact_formulas(spec)
assert "did you mean 'TOTAL_REVENUE'" in str(exc.value)
def test_reports_every_problem_at_once(self):
"""Five typos must not cost five regenerate cycles."""
spec = fact(
base("A"),
derived("D1", "NOPE1 + A"),
derived("D2", "NOPE2 - A"),
derived("D3", "NOPE3 * A"),
)
with pytest.raises(FormulaError) as exc:
validate_fact_formulas(spec)
msg = str(exc.value)
assert "3 unusable derived measure formula(s)" in msg
for name in ("NOPE1", "NOPE2", "NOPE3"):
assert name in msg
class TestOtherStructuralFaults:
def test_derived_without_formula(self):
spec = fact(base("A"), derived("D", ""))
with pytest.raises(FormulaError, match="declared derived but has no formula"):
validate_fact_formulas(spec)
def test_unparseable_formula(self):
spec = fact(base("A"), derived("D", "A +"))
with pytest.raises(FormulaError, match="does not parse"):
validate_fact_formulas(spec)
def test_function_call_rejected(self):
spec = fact(base("A"), derived("D", "SUM(A)"))
with pytest.raises(FormulaError, match="calls a function"):
validate_fact_formulas(spec)
def test_unsupported_operator_rejected(self):
spec = fact(base("A"), derived("D", "A % 2"))
with pytest.raises(FormulaError, match="unsupported operator"):
validate_fact_formulas(spec)
def test_comparison_rejected(self):
spec = fact(base("A"), base("B"), derived("D", "A > B"))
with pytest.raises(FormulaError, match="unsupported expression"):
validate_fact_formulas(spec)
class TestValidFormulasPass:
def test_simple_arithmetic(self):
spec = fact(
base("NET_SALES"),
base("COST_AMOUNT"),
derived("MARGIN", "NET_SALES - COST_AMOUNT"),
)
validate_fact_formulas(spec) # must not raise
def test_derived_referencing_derived(self):
spec = fact(
base("REVENUE"),
base("COGS"),
derived("MARGIN", "REVENUE - COGS"),
derived("MARGIN_RATE", "MARGIN / REVENUE"),
)
validate_fact_formulas(spec)
def test_constants_and_parens(self):
spec = fact(base("A"), derived("D", "(A * 100) / 2 + 1"))
validate_fact_formulas(spec)
def test_base_measures_are_not_checked(self):
"""A base measure with a stray formula string is not a derived defect."""
m = BlueprintMeasure(name="A", kind="base", formula="WHATEVER")
validate_fact_formulas(fact(m))
class TestPerRowEvaluation:
def test_division_by_zero_is_still_a_legitimate_zero(self):
"""Per-row arithmetic edge cases must stay tolerant."""
assert _eval_formula("REVENUE / ORDERS", {"REVENUE": 100.0, "ORDERS": 0.0}) == 0.0
def test_unknown_name_at_eval_time_raises(self):
with pytest.raises(FormulaError, match="not an available measure"):
_eval_formula("REVENUE - MISSING", {"REVENUE": 100.0})
def test_normal_arithmetic(self):
assert _eval_formula("A - B", {"A": 10.0, "B": 4.0}) == 6.0
assert _eval_formula("-A", {"A": 3.0}) == -3.0