From 281d9d25c9a063e1f57d9cb1884c731476181ef3 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sat, 6 Jun 2026 17:48:43 +0300 Subject: [PATCH] =?UTF-8?q?feat(superset):=20=D1=87=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20row-lineage=20=D0=BF=D0=BE=20=D1=81=D0=BB?= =?UTF-8?q?=D0=BE=D1=8F=D0=BC=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=BD=D0=BE=D0=B9=20DQ-=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - чарт «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, консоль без ошибок. --- sql/dm/40_dds_to_dm.sql | 19 +++++++++++------ superset/create_dashboard.py | 41 +++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/sql/dm/40_dds_to_dm.sql b/sql/dm/40_dds_to_dm.sql index 8b70592..d1e9c87 100644 --- a/sql/dm/40_dds_to_dm.sql +++ b/sql/dm/40_dds_to_dm.sql @@ -2,7 +2,7 @@ -- 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. -- -- Что делает: @@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS dm.dq_summary ( check_date Date, -- Дата проверки - layer LowCardinality(String), -- Слой: stg, ods, dds + layer LowCardinality(String), -- Слой: stg, ods, dds, dm table_name LowCardinality(String), -- Имя таблицы check_name LowCardinality(String), -- Тип проверки: total_rows, rows_with_errors и т.д. check_value UInt64 -- Значение метрики @@ -102,7 +102,14 @@ UNION ALL SELECT today(), 'dds', 'click', 'total_rows', count() FROM dds.click UNION ALL -- Считаем "осиротевшие" события (есть click_id, но нет такого click в dds.click) -SELECT today(), 'dds', 'event_without_click', 'orphan_events', count() -FROM dds.event -WHERE click_id IS NOT NULL - AND click_id NOT IN (SELECT click_id FROM dds.click); +SELECT today(), 'dds', 'event_without_click', 'orphan_events', count() +FROM dds.event +WHERE click_id IS NOT NULL + 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; diff --git a/superset/create_dashboard.py b/superset/create_dashboard.py index 5bdbd88..2517b5b 100644 --- a/superset/create_dashboard.py +++ b/superset/create_dashboard.py @@ -212,28 +212,53 @@ CHARTS_CONFIG = [ "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", "dataset_name": "dq_summary", "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": [ - {"expressionType": "SQL", "sqlExpression": "SUM(check_value)", "label": "Row Count"} + {"expressionType": "SQL", "sqlExpression": "SUM(check_value)", "label": "Rows"} ], "adhoc_filters": [ { "clause": "WHERE", "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, "operator": None, "comparator": None } ], + "order_bars": True, "row_limit": 100, "time_range": "No filter", + "y_axis_format": ",d", "show_legend": False } } @@ -242,7 +267,7 @@ CHARTS_CONFIG = [ # Конфигурация дашборда DASHBOARD_CONFIG = { "dashboard_title": "🛒 E-commerce Analytics Dashboard", - "description": "Аналитический дашборд для e-commerce кликстрима: трафик, конверсии, география и качество данных.", + "description": "Аналитический дашборд для e-commerce кликстрима: трафик, конверсии, география и прохождение строк по слоям.", "published": True, "slug": "ecommerce-analytics", } @@ -261,8 +286,8 @@ DASHBOARD_ROWS = [ [("📅 Events by Hour", 8), ("📱 Traffic by Device", 4)], # География + эффективность маркетинговых каналов [("🌍 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 (одинаковая для всех чартов строки —