docs(superset): спека редизайна дашборда и handoff
- Зачем:
- дашборд-эталон показывал две одинаковые KPI-плитки; нужен честный состав метрик
на полных данных, зафиксированный до реализации.
- Что:
- docs/specs/2026-06-06-...: KPI Events/Users/Avg per Visit/Conversion, Top Pages → Funnel, триаж чартов.
- .scratch/handoffs/2026-06-06-...: handoff для реализации в новой сессии.
- Проверка:
- числа спеки сверены с прямым запросом в ClickHouse на полном датасете.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# Редизайн 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/<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
|
||||
|
||||
- **Точная формула Conversion:** доля визитов с ≥1 pageview `/confirmation`, или
|
||||
просмотры `/confirmation` / просмотры `/home`? Влияет на число (~8%) и на то,
|
||||
что именно учим (визит-конверсия vs page-to-page).
|
||||
- **Events by Hour** — оставляем ли (зависит от проверки на пустоту на полных
|
||||
данных: разброс `event_ts` по часам).
|
||||
- **Требования к будущему генератору**, вытекающие из упёртостей этих данных
|
||||
(разнообразие `event_type`, возвраты пользователей → sessions>users,
|
||||
реалистичная воронка) — при возврате к генератору перенести в его
|
||||
`KNOWN_ISSUES.md`.
|
||||
Reference in New Issue
Block a user