# Редизайн KPI-полосы и состава чартов дашборда «E-commerce Analytics» Дата: 2026-06-06 Статус: Accepted (не реализовано — код `superset/create_dashboard.py` правится отдельным заходом на реализацию) Связано: [`CONTEXT.md`](../../CONTEXT.md), [ADR-0002](../adr/0002-specs-as-durable-design-docs.md), урок [`docs/course/lessons/06_superset_bi.md`](../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 становится наглядным. ## Recommended approach **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//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 - **Точная формула Conversion:** доля визитов с ≥1 pageview `/confirmation`, или просмотры `/confirmation` / просмотры `/home`? Влияет на число (~8%) и на то, что именно учим (визит-конверсия vs page-to-page). - **Events by Hour** — оставляем ли (зависит от проверки на пустоту на полных данных: разброс `event_ts` по часам). - **Требования к будущему генератору**, вытекающие из упёртостей этих данных (разнообразие `event_type`, возвраты пользователей → sessions>users, реалистичная воронка) — при возврате к генератору перенести в его `KNOWN_ISSUES.md`.