Files
clickstream-ch-kafka-supers…/docs/specs/2026-06-06-superset-dashboard-redesign.md
T
ddadmin 2563c79082 docs(superset): синхронизация доков и урока 6 под row-lineage
- Зачем:
  - после смены чарта на row-lineage пользовательская дока, README и урок 6
    описывали несуществующий «Data Quality Summary» и старый состав KPI;
    термин «зерно (grain)» использовался без пояснения.
- Что:
  - SUPERSET_DASHBOARD.md, README, урок 6 описывают «Rows by Layer (event)»,
    выровнен состав KPI/чартов; термин «зерно» поясняется в уроке простыми
    словами с якорем из данных (события 1000 / визиты 99).
  - термин выровнен на «визит» по CONTEXT.md (click_id = визит/сессия).
  - в спеку редизайна добавлена секция «Пересмотр после приёмки» как след решения.
- Проверка:
  - grep по «Data Quality Summary»/«Row Count» вне handoffs пуст.
  - визуальная вычитка изменённых разделов.
2026-06-06 17:49:01 +03:00

12 KiB
Raw Blame History

Редизайн KPI-полосы и состава чартов дашборда «E-commerce Analytics»

Дата: 2026-06-06 Статус: Implemented (superset/create_dashboard.py, docs/SUPERSET_DASHBOARD.md, docs/course/lessons/06_superset_bi.md) Связано: CONTEXT.md, ADR-0002, урок docs/course/lessons/06_superset_bi.md

Problem

Дашборд-эталон показывает две KPI-плитки — Unique Users и Unique Sessions — с одинаковым числом (26 на срезе, 99 на полных данных), потому что в датасете user_domain_id ↔ click_id строго 1:1. Две одинаковые цифры читаются как баг расчёта и ничему не учат менти, который копирует дашборд как образец. Нужен честный, копируемый состав KPI и чартов на полных данных.

Context

  • Дашборд — учебный эталон для менти. Требований обратной совместимости нет: состав можно менять свободно, критерий — учебная ценность.
  • Доменные термины зафиксированы в CONTEXT.md: пользователь = user_domain_id, визит/сессия = click_id, событие = event_id.
  • Источник данных — статический сид data/*.jsonl. Синтетический генератор (ветка feature/data-generator) источником не является и семантику ломает (см. generator/KNOWN_ISSUES.md на той ветке).

Goals

  • KPI-полоса из 4 различающихся честных метрик (без дубля одинаковых чисел).
  • Превратить слабые места данных в учебные объекты, а не маскировать их.
  • Консистентность с CONTEXT.md и уроком 6.

Non-goals

  • Не модернизируем типы виджетов (pie/world_map/dist_bar → ECharts) — отдельный косметический заход.
  • Не трогаем эмодзи в тайтлах, секции-заголовки, языковой винегрет.
  • Не трогаем генератор.
  • Не правим код create_dashboard.py в рамках этой спеки — это реализация.

Current state

Полный датасет (make data без LIMIT):

события: 1000 | визиты (click_id): 99 | пользователи (user_domain_id): 99 (1:1)
avg событий на визит: 10.10 | bounce (визиты с 1 событием): 4/99 = 4.0%
event_type: 100% pageview (других типов НЕТ)
page_url_path — воронка: /home 426 → /product_a 236 + /product_b 157 →
                          /cart 93 → /payment 53 → /confirmation 35 (~8% в конец)
geo_country: 40 | device_type: 2 (Mobile/Computer) | utm_source: 5 | utm_medium: 4

Текущие чарты и триаж по учебной ценности:

Чарт Тип Вердикт
Total Events, Unique Users big_number_total оставить (KPI)
Unique Sessions big_number_total убрать (дубль Users)
Avg Events/Session big_number_total оставить, переименовать /Visit
Top Pages dist_bar апгрейд в Funnel (центральный учебный объект)
UTM Effectiveness table оставить; выкинуть колонки purchases/add_to_cart (всегда 0)
Geography Map world_map оставить (40 стран; тип модернизировать отдельно)
Traffic by Device pie оставить
Events by Hour line проверить на пустоту, иначе дропнуть
Data Quality Summary dist_bar оставить (⚠️ позже пересмотрено — см. ниже)

Options considered

  • A — честная воронка вовлечённости (стандартный KPI-strip). 4 плитки объём→охват→глубина→качество; различие user/session уходит в текст.
  • B — оставить обе плитки + Markdown-панель, объясняющая равенство 1:1.
  • C — strip как форма модели: воронка Events → Visits → Users вместо бизнес-KPI; вырожденное 1:1 становится наглядным.

A для KPI-полосы + идея C отдельным чартом-воронкой. KPI-полоса — стандартная и чистая (копируемый паттерн); иерархия модели подаётся отдельным чартом, а Top Pages превращается в Funnel по страницам.

KPI-полоса:

📊 Total Events 1000 · 👤 Unique Users 99 · 📈 Avg Events/Visit 10.1 · 🎯 Conversion to /confirmation ~8%

Rationale

  • Конверсия через page_url_path честнее блёклого bounce (4%) и превращает «всё pageview» из слабости в сильный урок: воронка реально затухает.
  • Различие user/session учим текстом (CONTEXT.md + урок 6), а не двумя одинаковыми числами — это убирает сигнал «баг».
  • Стандартный KPI-strip ценнее для копирования, чем нестандартный вариант C; но идею C сохраняем как отдельный наглядный чарт.

Decisions and rejected alternatives

  • Данные: полный датасет и для дашборда, и для урока 6. Срез LIMIT=50 отклонён: числа эталона разойдутся с тем, что менти получит у себя. Урок 6 — убрать LIMIT=50 make data. (Срез LIMIT оставить в доке как явный приём отладки, не как основной прогон. Замечание: ограничение head -n исходно возникло, чтобы агентский контекст не давился полным jsonl — это ограничение для агента, не для пайплайна.)
  • Убрать Unique Sessions из KPI — дубль Unique Users при 1:1.
  • 4-я KPI = Conversion, не bounce. Bounce 4% отклонён как блёклый.
  • Top Pages → Funnel. Воронка по страницам — главный учебный объект.
  • UTM-таблица: убрать колонки purchases/add_to_cart — всегда 0, т.к. event_type только pageview.
  • Гео/device/DQ не удаляем — данные оправдывают ценность; модернизация их типов виджетов — отдельно (non-goal здесь).
  • «Снести всё legacy» на практике = убрать дубль-KPI + мёртвые колонки + проверить Events by Hour. Инсайты не вырезаем — у большинства есть данные.

Risks and mitigations

  • Funnel в Superset 4.1.2 — проверить доступность viz_type воронки до реализации; при отсутствии — упорядоченный bar.
  • Формула Conversion — определить точно и валидировать SQL (см. Open questions).
  • Идемпотентность create_dashboard.py при смене состава (чарты по slice_name, дашборд по slug) — переименование Unique Sessions/Top Pages не должно плодить дубли; проверить апдейт на месте.

Validation

  • KPI-полоса: 4 разных числа, дубля нет.
  • GET /api/v1/dashboard/<id>/datasets → 200; DQ Summary без Columns missing in datasource.
  • Визуальная проверка через playwright-cli (скриншот в /tmp).
  • Числа на дашборде сходятся с прямым запросом в ClickHouse.

Documentation impact

  • docs/SUPERSET_DASHBOARD.md — обновить раздел «Структура дашборда» (состав чартов/KPI/фильтров) в момент реализации, чтобы пользовательская дока не описывала несуществующие чарты.
  • docs/course/lessons/06_superset_bi.md — убрать LIMIT=50, синхронизировать состав чартов и скриншоты.
  • CONTEXT.md — уже отражает user/visit/event; правок не требует.

Open questions

  • Resolved: точная формула Conversion. Используем page-funnel conversion: countIf(page_url_path = '/confirmation') / countIf(page_url_path = '/home'). На полном датасете это 35 / 426 = 8.2%. Визитовая формула даёт 25.3% и отклонена, потому что KPI должен совпадать с логикой chart Page Funnel.
  • Resolved: Events by Hour. Оставляем: на полном датасете есть два часовых бакета (20 → 256, 21 → 744), график не пустой.
  • Resolved: Funnel в Superset 4.1.2. MCP Context7 по /apache/superset не дал точной строки viz_type; установленный Superset 4.1.2 проверен по bundled example Featured Charts/Funnel.yaml и frontend assets. Используем viz_type: funnel.
  • Требования к будущему генератору, вытекающие из упёртостей этих данных (разнообразие event_type, возвраты пользователей → sessions>users, реалистичная воронка) — при возврате к генератору перенести в его KNOWN_ISSUES.md.

Пересмотр после приёмки (2026-06-06)

Решение «Data Quality Summary | dist_bar | оставить» пересмотрено при визуальной проверке дашборда. Чарт суммировал total_rows по всем таблицам слоя, складывая таблицы разного зерна (события 1000 + визиты 99 + пустые error-таблицы) в один столбец, и рисовал убывающую «воронку потерь» (stg≈4250 → ods≈2198 → dds≈1099), которой в данных нет. Для учебного стенда это активно вводит в заблуждение.

Чарт переделан в честный row-lineage одного event-зерна (browser_raw → browser_event → event → v_events_enriched) и переименован в 🧱 Rows by Layer (event). Теперь убывание настоящее: шаг 1050 → 1000 — это дедупликация at-least-once потока по event_id в ODS (ReplacingMergeTree). В dm.dq_summary добавлена строка для слоя dm (sql/dm/40_dds_to_dm.sql), чтобы цепочка замыкалась до витрины. Настоящие сигналы качества (rows_with_errors, orphan_events) на чистых демо-данных = 0 и остаются отдельными check_name.