Spaces:
Running
Running
File size: 5,390 Bytes
fb35807 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | """
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
|