diff --git a/.scratch/handoffs/2026-06-06-superset-dashboard-redesign.md b/.scratch/handoffs/2026-06-06-superset-dashboard-redesign.md new file mode 100644 index 0000000..2db995a --- /dev/null +++ b/.scratch/handoffs/2026-06-06-superset-dashboard-redesign.md @@ -0,0 +1,61 @@ +# Handoff: реализация редизайна Superset-дашборда + +Дата: 2026-06-06 · Язык сессии: русский · Понять-режим был включён (можно не продолжать) + +## Источник истины + +**Сначала прочитать спеку:** [`docs/specs/2026-06-06-superset-dashboard-redesign.md`](../../docs/specs/2026-06-06-superset-dashboard-redesign.md) +— там весь дизайн (проблема, числа данных, триаж чартов, состав KPI, решения, риски, +критерии проверки). Этот handoff — только «как возобновить», не дублирует дизайн. +Доменные термины — [`CONTEXT.md`](../../CONTEXT.md). + +## Решение одной строкой + +KPI-полоса = `Total Events · Unique Users · Avg Events/Visit · Conversion to /confirmation` +(дубль «Unique Sessions» убрать); `Top Pages → Funnel`-чарт по страницам; различие +user/session — текстом, не двумя одинаковыми цифрами; мёртвые колонки purchases — выкинуть. + +## Сделать ПЕРВЫМ делом + +1. **Снять open questions из спеки** (без них реализация буксует): + - точная формула Conversion (доля визитов с ≥1 pageview `/confirmation`?); + - поддерживает ли Superset **4.1.2** `viz_type` воронки (иначе — упорядоченный bar); + - судьба `Events by Hour` (проверить, не пустой ли на полных данных). +2. **Перегрузить стенд на ПОЛНЫЕ данные** — сейчас в ClickHouse отладочный срез + (50 событий). Нужно: `make data` (без `LIMIT`) + `make transform`. На полных + данных: 1000 событий, 99 визитов, 99 пользователей. + +## Где править и как прогонять + +- Единственный файл реализации: `superset/create_dashboard.py` + (`CHARTS_CONFIG` / `DASHBOARD_ROWS` / `ROW_HEIGHTS`). +- Прогон: `make superset-dashboard` — идемпотентно (чарты по `slice_name`, дашборд + по `slug`, обновляются на месте). При переименовании чартов следить, чтобы не + плодились дубли. + +## Проверка + +- `GET /api/v1/dashboard//datasets` → 200; DQ Summary без `Columns missing in datasource`. +- Визуально: `playwright-cli` — логин формой `admin`/`admin` на `http://localhost:8088/login/`, + затем `goto .../superset/dashboard/ecommerce-analytics/`, `screenshot --filename=/tmp/x.png` (читать через Read). + Скриншоты — в `/tmp`. `.playwright-cli/` НЕ коммитить. +- Сверка чисел с ClickHouse: креды в `configs/default_user.xml` (default / `123456`), + `docker exec clickstream-ch-kafka-superset-demo-clickhouse-1 clickhouse-client --password 123456 -q "..."`. + (Через `docker exec printenv` креды НЕ дёргать — классификатор блокирует.) + +## Синхронизировать доки ПРИ реализации + +- `docs/SUPERSET_DASHBOARD.md` — раздел «Структура дашборда» (новый состав чартов/KPI). +- `docs/course/lessons/06_superset_bi.md` — убрать `LIMIT=50 make data`, синхронизировать состав/скриншоты. + +## Git-гигиена + +- Ветка `docs/advanced-clickstream-course`, на ней **параллельно пишет Codex** — + git строго **аддитивно**, не amend/rebase/reset чужих коммитов. `git add` только своих файлов. +- Артефакты этой сессии (если ещё не закоммичены): `CONTEXT.md`, `docs/adr/0001` (правка), + `docs/adr/0002`, `docs/adr/0003`, `docs/specs/2026-06-06-...`, `AGENTS.md` (правка), этот handoff. + +## Suggested skills + +`conventional-commits` (любой коммит) · `playwright-cli` (визуальная проверка) · +`diagnose` (если чарт/датасет отвалится). diff --git a/docs/specs/2026-06-06-superset-dashboard-redesign.md b/docs/specs/2026-06-06-superset-dashboard-redesign.md new file mode 100644 index 0000000..461008c --- /dev/null +++ b/docs/specs/2026-06-06-superset-dashboard-redesign.md @@ -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//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`.