Spaces:
Running
Running
File size: 6,198 Bytes
c9dd6cd | 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
Semantic (non-fatal) checks in the post-load quality gate.
The original gate only caught measures that loaded 100% zero/null — a
guaranteed blank tile. These checks catch data that *renders* but reads wrong,
which is worse in a demo because nobody notices until a prospect does:
- Tixr shipped TICKET_SELL_THROUGH_RATE = 1.96 (a 196% sell-through).
- Triumph's *_MONTH bucket columns held random days of the month.
- 500 fact rows spread over 24 months left most periods nearly empty.
These are warnings, not failures, by design — see _run_semantic_checks.
Run with: pytest tests/test_quality_gate_semantics.py -v
"""
import pytest
from demoprep_app.integrations.snowflake.quality_gate import (
MIN_ROWS_PER_MONTH_WARN,
bounded_range_for_column,
is_date_column,
is_month_bucket_column,
run_measure_quality_gate,
)
class TestClassifiers:
def test_rate_and_ratio_are_zero_to_one(self):
assert bounded_range_for_column("TICKET_SELL_THROUGH_RATE") == (0.0, 1.0)
assert bounded_range_for_column("WIN_RATIO") == (0.0, 1.0)
def test_percent_columns_are_zero_to_one_hundred(self):
assert bounded_range_for_column("BROKER_MARGIN_PCT") == (0.0, 100.0)
assert bounded_range_for_column("DISCOUNT_PERCENT") == (0.0, 100.0)
assert bounded_range_for_column("UPTIME_PERCENTAGE") == (0.0, 100.0)
def test_unbounded_columns_return_none(self):
assert bounded_range_for_column("TOTAL_REVENUE_USD") is None
assert bounded_range_for_column("ORDER_COUNT") is None
# "RATE" must be a suffix, not a substring — RATED_CAPACITY is not a rate.
assert bounded_range_for_column("RATED_CAPACITY") is None
def test_month_bucket_requires_date_type(self):
assert is_month_bucket_column("ORDER_MONTH", "DATE")
assert is_month_bucket_column("ORDER_MONTH", "TIMESTAMP_NTZ")
# A numeric *_MONTH is a month number, not a bucket date.
assert not is_month_bucket_column("FISCAL_MONTH", "NUMBER(2,0)")
assert not is_month_bucket_column("ORDER_DATE", "DATE")
def test_is_date_column(self):
assert is_date_column("DATE")
assert is_date_column("TIMESTAMP_LTZ")
assert not is_date_column("VARCHAR(10)")
assert not is_date_column("NUMBER")
class SemanticCursor:
"""Routes each query shape to a canned answer.
Distinguishes the four query shapes the gate issues: the column catalogue,
the per-table non-zero aggregate, bounds violations, month-start
violations, and the density probe.
"""
def __init__(self, columns_rows, aggregate, *, out_of_range=(0, None, None),
bad_month_rows=0, density=(1000, 12)):
self._columns_rows = columns_rows
self._aggregate = aggregate
self._out_of_range = out_of_range
self._bad_month_rows = bad_month_rows
self._density = density
self._result = None
def execute(self, sql, params=None):
if "INFORMATION_SCHEMA.COLUMNS" in sql:
self._result = self._columns_rows
elif "DATE_TRUNC" in sql:
self._result = self._density
elif "DAY(" in sql:
self._result = (self._bad_month_rows,)
elif "MIN(" in sql and "MAX(" in sql:
self._result = self._out_of_range
else:
self._result = self._aggregate
def fetchall(self):
return self._result
def fetchone(self):
return self._result
def close(self):
pass
class SemanticConnection:
def __init__(self, cursor):
self._cursor = cursor
def cursor(self):
return self._cursor
COLUMNS = [
("FACT_TICKET_SALE", "TICKET_ID", "NUMBER"),
("FACT_TICKET_SALE", "TICKETS_SOLD", "NUMBER"),
("FACT_TICKET_SALE", "TICKET_SELL_THROUGH_RATE", "FLOAT"),
("FACT_TICKET_SALE", "SALE_MONTH", "DATE"),
]
# (row_count, nonzero TICKETS_SOLD, nonzero RATE)
CLEAN_AGG = (1000, 1000, 1000)
def _run(cursor):
return run_measure_quality_gate(SemanticConnection(cursor), "DEMO_SCHEMA")
def test_out_of_range_rate_warns_but_does_not_fail():
cursor = SemanticCursor(COLUMNS, CLEAN_AGG, out_of_range=(7, 0.0, 1.96))
profile = _run(cursor)
warning = " ".join(profile["semantic_warnings"])
assert "TICKET_SELL_THROUGH_RATE" in warning
assert "7 row(s) outside expected range" in warning
assert "1.96" in warning
assert profile["failures"] == []
def test_non_month_start_dates_warn():
cursor = SemanticCursor(COLUMNS, CLEAN_AGG, bad_month_rows=412)
profile = _run(cursor)
warning = " ".join(profile["semantic_warnings"])
assert "SALE_MONTH" in warning
assert "412 row(s) are not month-start" in warning
def test_sparse_panel_warns():
"""500 rows over 24 months = ~21/month, below the density threshold."""
cursor = SemanticCursor(COLUMNS, CLEAN_AGG, density=(500, 24))
profile = _run(cursor)
warning = " ".join(profile["semantic_warnings"])
assert "FACT_TICKET_SALE is sparse over time" in warning
assert "20.8/month" in warning
def test_dense_panel_is_silent():
cursor = SemanticCursor(COLUMNS, CLEAN_AGG, density=(12000, 24))
profile = _run(cursor)
assert not any("sparse" in w for w in profile["semantic_warnings"])
def test_clean_data_produces_no_semantic_warnings():
cursor = SemanticCursor(COLUMNS, CLEAN_AGG)
profile = _run(cursor)
assert profile["semantic_warnings"] == []
def test_semantic_failure_cannot_break_the_run():
"""A broken semantic probe degrades to a note, never an exception."""
class ExplodingCursor(SemanticCursor):
def execute(self, sql, params=None):
if "INFORMATION_SCHEMA.COLUMNS" in sql:
self._result = self._columns_rows
elif "SUM(IFF" in sql:
self._result = self._aggregate
else:
raise RuntimeError("probe blew up")
profile = _run(ExplodingCursor(COLUMNS, CLEAN_AGG))
assert any("semantic checks skipped" in w for w in profile["warnings"])
assert profile["failures"] == []
def test_density_threshold_is_named_not_magic():
assert MIN_ROWS_PER_MONTH_WARN == 30
|