""" 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