Files
clickstream-ch-kafka-supers…/docs/specs/2026-06-06-superset-dashboard-redesign.md
T
ddadmin f8b419d84d docs(generator): закрыты находки финального ревью цепочки
- Зачем:
  - финальный review должен видеть согласованные PRD, issue, курс, Superset и архитектурные документы.
- Что:
  - обновлены PRD, чекбоксы закрытых issue и журнал coordinator-loop.
  - синхронизированы архитектура, карта репозитория, CONTEXT и курс со startup-history-путём.
  - убраны старые маркеры Superset-геокарты после перехода на Top Countries.
- Проверка:
  - rg-проверки финального review по PRD, issue и Superset-маркерам.
  - git diff --cached --check.
2026-07-04 23:09:15 +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/геовизуализация/dist_bar → ECharts) — отдельный косметический заход. Геоблок позже заменён задачей 10 generator-model-time-startup-history.
  • Не трогаем эмодзи в тайтлах, секции-заголовки, языковой винегрет.
  • Не трогаем генератор.
  • Не правим код 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 прежняя геовизуализация позже заменена на Top Countries by Events
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.