Spaces:
Running
Running
File size: 5,215 Bytes
a89fce0 | 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 | """Semantic data-quality gate β run between data load and liveboard creation.
Motivation (Yodeck, Aug 20 2026): the generator wrote every derived measure
(ARR, Net New MRR, NRR, collection/margin rates) as all-zero, and the liveboard
charted exactly those columns β 6 of 10 vizzes rendered nothing or $0. Nothing
in the pipeline checked that charted data meant anything: every gate was a
plumbing gate (DDL compiled, rows loaded, TML imported).
This module is the semantic gate:
* scan_dead_measures(database, schema): one aggregate query per table finds
numeric columns that are entirely NULL/zero ("dead") or constant.
* filter_model_columns(model_columns, dead_names): drops model MEASURE
columns whose physical column is dead, so question/KPI/viz generation can
only reach measures with real data.
Fail-open by design: any error here must never break a build β callers wrap in
try/except and proceed unfiltered with a warning.
"""
from __future__ import annotations
import re
from typing import Dict, List, Tuple
# Numeric columns that are join/surrogate keys, not measures β never scanned.
_KEY_SUFFIXES = ("_KEY", "_ID")
# Snowflake numeric types eligible for the dead-measure scan.
_NUMERIC_TYPES = ("NUMBER", "FLOAT", "FIXED", "REAL", "DECIMAL", "NUMERIC", "INT")
def _normalize(name: str) -> str:
"""Match model display names to physical columns: 'Net New Mrr' == NET_NEW_MRR."""
return re.sub(r"[^a-z0-9]", "", (name or "").lower())
def scan_dead_measures(database: str, schema: str, log=None) -> Dict:
"""Scan every base table in the schema for dead numeric measure columns.
Returns {
'dead': {table: [column, ...]}, # all rows NULL or zero
'constant': {table: [column, ...]}, # single repeated non-zero value
'dead_names': set of normalized dead column names (for model filtering),
'warnings': [str, ...],
}
Opens its own Snowflake connection (keypair auth via snowflake_auth).
"""
def _log(msg: str) -> None:
if log:
log(msg)
from snowflake_auth import get_snowflake_connection
out = {"dead": {}, "constant": {}, "dead_names": set(), "warnings": []}
conn = get_snowflake_connection()
try:
cur = conn.cursor()
cur.execute(
f"""
select c.table_name, c.column_name
from {database}.information_schema.columns c
join {database}.information_schema.tables t
on t.table_schema = c.table_schema and t.table_name = c.table_name
where c.table_schema = %s
and t.table_type = 'BASE TABLE'
and c.data_type in ({','.join("'" + t + "'" for t in _NUMERIC_TYPES)})
order by c.table_name, c.ordinal_position
""",
(schema,),
)
by_table: Dict[str, List[str]] = {}
for table, column in cur.fetchall():
if column.upper().endswith(_KEY_SUFFIXES):
continue
by_table.setdefault(table, []).append(column)
for table, cols in by_table.items():
# One aggregate pass per table: [nonzero-count, min, max] per column.
selects = []
for c in cols:
selects.append(f'count_if("{c}" is not null and "{c}" <> 0)')
selects.append(f'min("{c}")')
selects.append(f'max("{c}")')
cur.execute(
f'select count(*), {", ".join(selects)} from {database}."{schema}"."{table}"'
)
row = cur.fetchone()
total = row[0]
if not total:
continue
for i, c in enumerate(cols):
nonzero, cmin, cmax = row[1 + i * 3], row[2 + i * 3], row[3 + i * 3]
if nonzero == 0:
out["dead"].setdefault(table, []).append(c)
out["dead_names"].add(_normalize(c))
elif total > 1 and cmin == cmax:
out["constant"].setdefault(table, []).append(c)
for table, cols in out["dead"].items():
msg = f"DATA GATE: {table} has all-zero/all-null measures: {', '.join(cols)}"
out["warnings"].append(msg)
_log(f" [GATE] {msg}")
for table, cols in out["constant"].items():
msg = f"DATA GATE: {table} has constant-value measures: {', '.join(cols)}"
out["warnings"].append(msg)
_log(f" [GATE] {msg}")
if not out["dead"] and not out["constant"]:
_log(" [GATE] Data gate clean: no dead or constant measures")
finally:
conn.close()
return out
def filter_model_columns(model_columns: List[Dict], dead_names) -> Tuple[List[Dict], List[str]]:
"""Split model columns into (kept, excluded_names). Only MEASURE-typed
columns are eligible for exclusion β attributes/dates always pass."""
kept, excluded = [], []
for col in model_columns or []:
name = col.get("name") or ""
ctype = (col.get("type") or "").upper()
if ctype == "MEASURE" and _normalize(name) in dead_names:
excluded.append(name)
else:
kept.append(col)
return kept, excluded
|