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 строятся на статическом сиде, не на потоке.