From 79481a93514b2f53db2153682dff2334017926e6 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sat, 6 Jun 2026 16:12:10 +0300 Subject: [PATCH] =?UTF-8?q?docs(context):=20=D0=B3=D0=BB=D0=BE=D1=81=D1=81?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B9=20=D0=B4=D0=BE=D0=BC=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D0=BA=D0=BB=D0=B8=D0=BA=D1=81=D1=82=D1=80=D0=B8=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - зафиксировать доменный язык, чтобы метрики дашборда и урок 6 опирались на единые термины. - Что: - добавлен CONTEXT.md: пользователь/визит-сессия/событие, иерархия, почему на демо Users == Sessions. - Проверка: - термины сверены с sql/ddl/dds/30_dds.sql и sql/ddl/dm/40_dm.sql. --- CONTEXT.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 CONTEXT.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..1f5b847 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,68 @@ +# CONTEXT.md — глоссарий домена + +Доменный язык стенда: как мы называем сущности кликстрима и что под ними +понимаем. Один термин — одно имя. Ведётся вместе с `docs/adr/` (см. +[ADR-0001](./docs/adr/0001-spec-adr-issue-layout.md)). + +## Сущности + +### Пользователь (user) + +`user_domain_id` (`UUID`) — постоянный идентификатор пользователя (cookie-уровень), +переживает отдельные визиты. Источник — `device_events`, привязан к `click_id`. +В витринах: `uniqExact(user_domain_id)`. + +### Визит / сессия (visit / session) + +`click_id` (`UUID`) — **визит**: envelope из одного и более событий с общим +контекстом device/geo. В DDL `dds.click` описана как «контекст сессии +пользователя» (см. [`sql/ddl/dds/30_dds.sql`](./sql/ddl/dds/30_dds.sql)). + +Важно: в источнике **нет отдельного session-таймаута** (как Snowplow +`domain_sessionid`). Поэтому в этой модели **сессия ≡ визит ≡ `click_id`** — +группа событий под одним идентификатором, а не окно активности по тайм-ауту. +Если в будущем понадобится «настоящая» сессия по 30-минутному окну неактивности — +это отдельная производная над `user_domain_id` + `event_ts`; на текущих данных +она дала бы те же группы (разброс времени внутри `click_id` ≤ 1 мин). + +«Сессию» и «визит» используем как синонимы; в UI предпочитаем «визит», когда важно +подчеркнуть, что это не сессия-по-тайм-ауту. + +### Событие (event) + +`event_id` (`UUID`) — отдельное действие пользователя в рамках визита: `pageview`, +`click`, `purchase`, `add_to_cart` и т.п. (поле `event_type`). Источник — +`browser_events` + `location_events`. В витринах: `count()`. + +## Иерархия + +``` +user_domain_id (пользователь, постоянный) + └── click_id (визит/сессия — 1..N событий с общим device/geo) + └── event_id (событие: pageview / click / purchase ...) +``` + +Здоровые данные дают пирамиду `users ≤ sessions ≤ events`. + +## Почему на демо `Unique Users == Unique Sessions` + +В демо-датасете (`data/*.jsonl`) каждый пользователь имеет **ровно один** +`click_id` — связь user↔визит строго 1:1 (проверено: 99 пользователей = +99 `click_id` в полном файле). Поэтому `uniqExact(user_domain_id)` и +`uniqExact(click_id)` дают одинаковое число. + +Это **свойство конкретного датасета, а не закон модели и не баг расчёта.** В +реальных данных пользователь возвращается несколькими визитами, и числа +расходятся. Поэтому на дашборде мы **не** показываем две одинаковые KPI-плитки +(это читалось бы как ошибка) — вместо дубля идут производные метрики +(события на визит, доля визитов с одним событием), а различие «пользователь vs +сессия» объясняется текстом урока. + +Синтетический генератор (ветка `feature/data-generator`) проблему не решает, а +усугубляет: он штампует свежий `click_id` на каждое событие, и `click_id` +вырождается в «событие». См. `generator/KNOWN_ISSUES.md` на той ветке. + +## Слои данных + +`STG → ODS → DDS → DM` — см. [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md). +Витрины (`dm.*`) и дашборд Superset строятся на статическом сиде, не на потоке.