feat(superset): честный row-lineage по слоям вместо ложной DQ-воронки
- Зачем:
- чарт «Data Quality Summary» суммировал total_rows по всем таблицам слоя,
складывал таблицы разного зерна (события 1000 + визиты 99 + error-таблицы 0)
и рисовал убывающую «воронку потерь» (stg≈4250→ods≈2198→dds≈1099), которой
в данных нет. На учебном стенде это активно вводит в заблуждение.
- Что:
- чарт переделан в row-lineage одного event-зерна и переименован в
«🧱 Rows by Layer (event)»; rename идемпотентный через previous_slice_names.
- чарт берёт по одной канонической таблице на слой
(browser_raw→browser_event→event→v_events_enriched), порядок слоёв задан
числовым префиксом в groupby + order_bars.
- в dm.dq_summary добавлена строка total_rows для слоя dm, чтобы цепочка
замыкалась до витрины.
- описание дашборда обновлено под новый смысл.
- Проверка:
- python3 -m py_compile superset/create_dashboard.py.
- make transform / прогон sql/dm/40_dds_to_dm.sql; в dq_summary есть строка dm.
- make superset-dashboard (идемпотентно, 10 чартов, дублей нет).
- визуально через playwright-cli: 4 столбца 1·stg→2·ods→3·dds→4·dm,
видимый шаг дедупликации 1050→1000, консоль без ошибок.
This commit is contained in:
+10
-3
@@ -2,7 +2,7 @@
|
|||||||
-- Batch-трансформация: DDS → DM (Data Quality summary)
|
-- Batch-трансформация: DDS → DM (Data Quality summary)
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- Поток данных:
|
-- Поток данных:
|
||||||
-- dds.click + dds.event + ods.* + stg.* → dm.dq_summary (сводка по слоям)
|
-- stg.* + ods.* + dds.* + dm.v_events_enriched → dm.dq_summary (сводка по слоям)
|
||||||
-- Сами витрины (dm.v_*) — это VIEW поверх DDS, создаются в sql/ddl/dm/40_dm.sql.
|
-- Сами витрины (dm.v_*) — это VIEW поверх DDS, создаются в sql/ddl/dm/40_dm.sql.
|
||||||
--
|
--
|
||||||
-- Что делает:
|
-- Что делает:
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
CREATE TABLE IF NOT EXISTS dm.dq_summary
|
CREATE TABLE IF NOT EXISTS dm.dq_summary
|
||||||
(
|
(
|
||||||
check_date Date, -- Дата проверки
|
check_date Date, -- Дата проверки
|
||||||
layer LowCardinality(String), -- Слой: stg, ods, dds
|
layer LowCardinality(String), -- Слой: stg, ods, dds, dm
|
||||||
table_name LowCardinality(String), -- Имя таблицы
|
table_name LowCardinality(String), -- Имя таблицы
|
||||||
check_name LowCardinality(String), -- Тип проверки: total_rows, rows_with_errors и т.д.
|
check_name LowCardinality(String), -- Тип проверки: total_rows, rows_with_errors и т.д.
|
||||||
check_value UInt64 -- Значение метрики
|
check_value UInt64 -- Значение метрики
|
||||||
@@ -105,4 +105,11 @@ UNION ALL
|
|||||||
SELECT today(), 'dds', 'event_without_click', 'orphan_events', count()
|
SELECT today(), 'dds', 'event_without_click', 'orphan_events', count()
|
||||||
FROM dds.event
|
FROM dds.event
|
||||||
WHERE click_id IS NOT NULL
|
WHERE click_id IS NOT NULL
|
||||||
AND click_id NOT IN (SELECT click_id FROM dds.click);
|
AND click_id NOT IN (SELECT click_id FROM dds.click)
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
-- DM-слой: финальная витрина событий (VIEW поверх dds.event).
|
||||||
|
-- Нужна, чтобы lineage-чарт замыкал цепочку stg→ods→dds→dm на одном (event) зерне.
|
||||||
|
-- VIEW создаётся в DDL (sql/ddl/dm/40_dm.sql) до трансформаций, а этот шаг идёт
|
||||||
|
-- после наполнения dds.event — поэтому count() здесь корректен.
|
||||||
|
SELECT today(), 'dm', 'v_events_enriched', 'total_rows', count() FROM dm.v_events_enriched;
|
||||||
|
|||||||
@@ -212,28 +212,53 @@ CHARTS_CONFIG = [
|
|||||||
"show_tooltip_labels": True
|
"show_tooltip_labels": True
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# Качество данных
|
# Прохождение строк по слоям (lineage одного event-зерна)
|
||||||
{
|
{
|
||||||
"slice_name": "🔍 Data Quality Summary",
|
"slice_name": "🧱 Rows by Layer (event)",
|
||||||
|
"previous_slice_names": ["🔍 Data Quality Summary"],
|
||||||
|
# Честный row-lineage одного event-зерна через слои stg→ods→dds→dm.
|
||||||
|
# Берём ПО ОДНОЙ канонической таблице на слой (browser_raw → browser_event
|
||||||
|
# → event → v_events_enriched). Прежний вариант суммировал total_rows по
|
||||||
|
# ВСЕМ таблицам слоя — таблицы разного зерна (события 1000 + визиты 99 +
|
||||||
|
# error-таблицы 0) складывались в один столбец и рисовали ложную «воронку
|
||||||
|
# потерь», которой нет. На одном зерне убывание становится настоящим:
|
||||||
|
# видимый шаг 1050→1000 — это дедупликация at-least-once потока по
|
||||||
|
# event_id в ODS (ReplacingMergeTree), а не потеря данных.
|
||||||
|
# Префикс "N · " в groupby задаёт порядок слоёв (order_bars сортирует по
|
||||||
|
# подписи), иначе бары встают по убыванию значения, а не по конвейеру.
|
||||||
"viz_type": "dist_bar",
|
"viz_type": "dist_bar",
|
||||||
"dataset_name": "dq_summary",
|
"dataset_name": "dq_summary",
|
||||||
"params": {
|
"params": {
|
||||||
"groupby": ["layer"],
|
"groupby": [
|
||||||
|
{
|
||||||
|
"expressionType": "SQL",
|
||||||
|
"sqlExpression": (
|
||||||
|
"multiIf(layer = 'stg', '1 · stg', layer = 'ods', '2 · ods', "
|
||||||
|
"layer = 'dds', '3 · dds', '4 · dm')"
|
||||||
|
),
|
||||||
|
"label": "Layer"
|
||||||
|
}
|
||||||
|
],
|
||||||
"metrics": [
|
"metrics": [
|
||||||
{"expressionType": "SQL", "sqlExpression": "SUM(check_value)", "label": "Row Count"}
|
{"expressionType": "SQL", "sqlExpression": "SUM(check_value)", "label": "Rows"}
|
||||||
],
|
],
|
||||||
"adhoc_filters": [
|
"adhoc_filters": [
|
||||||
{
|
{
|
||||||
"clause": "WHERE",
|
"clause": "WHERE",
|
||||||
"expressionType": "SQL",
|
"expressionType": "SQL",
|
||||||
"sqlExpression": "check_name = 'total_rows'",
|
"sqlExpression": (
|
||||||
|
"check_name = 'total_rows' AND table_name IN "
|
||||||
|
"('browser_raw', 'browser_event', 'event', 'v_events_enriched')"
|
||||||
|
),
|
||||||
"subject": None,
|
"subject": None,
|
||||||
"operator": None,
|
"operator": None,
|
||||||
"comparator": None
|
"comparator": None
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"order_bars": True,
|
||||||
"row_limit": 100,
|
"row_limit": 100,
|
||||||
"time_range": "No filter",
|
"time_range": "No filter",
|
||||||
|
"y_axis_format": ",d",
|
||||||
"show_legend": False
|
"show_legend": False
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,7 +267,7 @@ CHARTS_CONFIG = [
|
|||||||
# Конфигурация дашборда
|
# Конфигурация дашборда
|
||||||
DASHBOARD_CONFIG = {
|
DASHBOARD_CONFIG = {
|
||||||
"dashboard_title": "🛒 E-commerce Analytics Dashboard",
|
"dashboard_title": "🛒 E-commerce Analytics Dashboard",
|
||||||
"description": "Аналитический дашборд для e-commerce кликстрима: трафик, конверсии, география и качество данных.",
|
"description": "Аналитический дашборд для e-commerce кликстрима: трафик, конверсии, география и прохождение строк по слоям.",
|
||||||
"published": True,
|
"published": True,
|
||||||
"slug": "ecommerce-analytics",
|
"slug": "ecommerce-analytics",
|
||||||
}
|
}
|
||||||
@@ -261,8 +286,8 @@ DASHBOARD_ROWS = [
|
|||||||
[("📅 Events by Hour", 8), ("📱 Traffic by Device", 4)],
|
[("📅 Events by Hour", 8), ("📱 Traffic by Device", 4)],
|
||||||
# География + эффективность маркетинговых каналов
|
# География + эффективность маркетинговых каналов
|
||||||
[("🌍 Geography Map", 6), ("🔗 UTM Effectiveness Table", 6)],
|
[("🌍 Geography Map", 6), ("🔗 UTM Effectiveness Table", 6)],
|
||||||
# Популярные страницы + качество данных
|
# Популярные страницы + прохождение строк по слоям
|
||||||
[("🪜 Page Funnel", 6), ("🔍 Data Quality Summary", 6)],
|
[("🪜 Page Funnel", 6), ("🧱 Rows by Layer (event)", 6)],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Высота строки в grid-units Superset (одинаковая для всех чартов строки —
|
# Высота строки в grid-units Superset (одинаковая для всех чартов строки —
|
||||||
|
|||||||
Reference in New Issue
Block a user