Files
clickstream-ch-kafka-supers…/generator/KNOWN_ISSUES.md
T
Dmitry DementievandClaude Fable 5 6efa031023 docs(generator): добавлена мат-спека модели и исправлен профиль сида
- Зачем:
  - закрыть Open questions спеки формы доработки перед передачей на
    реализацию; адверсариальное ревью показало, что прежние ориентиры
    (популяция/паузы/интенсивность) взаимно несовместимы, а документы
    опираются на неверный факт о сиде («1..7 событий на визит»).
- Что:
  - добавлена docs/specs/2026-06-10-generator-math-model.md: марковская
    цепочка по страницам, формула связи «популяция-интенсивность-пауза»
    (λ по умолчанию 30/мин), кулдаун возврата, правило 30 минут на рестарт,
    критерии приёмки.
  - в CONTEXT.md добавлен профиль сид-датасета (полный замер: длины визитов
    1..27, медиана 10, конверсия 25%, петли и события после /confirmation)
    и исправлено ложное «разброс времени внутри click_id <= 1 мин».
  - исправлен факт «1..7 событий» в KNOWN_ISSUES.md и ADR-0004; критерии
    Validation спеки формы доработки приведены к фактам сида.
- Проверка:
  - перекрёстные ссылки между спеками/ADR/CONTEXT.md открываются; цифры
    профиля сида воспроизводятся скриптом подсчёта по полным data/*.jsonl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:32:47 +03:00

123 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Генератор: известные проблемы и контекст для доработки
> **Статус (2026-06-06):** ветка `feature/data-generator` **не влита** в `main`.
> Генератор **не используется** как источник данных для витрин DM/дашборда.
> Витрины и Superset-дашборд строятся на **статическом сиде** (`data/*.jsonl`).
> Причина — ниже. Это не «сырой код по мелочи», а концептуальный дефект
> генеративной модели, который надо осознанно чинить перед использованием.
Заметка написана при дизайне Superset-дашборда (ветка
`docs/advanced-clickstream-course`): разбирались, почему на дашборде
`Unique Users == Unique Sessions`, и по ходу вскрылось, что генератор
семантику сессии не чинит, а ломает сильнее. Чтобы при возвращении к
генератору не переоткрывать это заново — фиксирую понимание целиком.
## TL;DR
- **Модель интенсивности потока (сколько событий и когда) — нормальная.**
Poisson по тикам + дневной коэффициент + jitter. Её можно оставить.
- **Генеративная модель сущностей (кто, какая сессия, какое событие) — неверная.**
На каждое событие штампуется свежий `click_id`, а атрибуты копируются из
случайной сид-строки. Это уничтожает понятие визита/сессии.
- **Вывод:** прежде чем использовать генератор как источник, переделать именно
модель сущностей (иерархия пользователь → сессия → событие). Математику
интенсивности трогать не обязательно.
## Доменная модель (как задумано в DDL)
В этой модели сущности связаны иерархически:
```
user_domain_id (постоянный пользователь, cookie)
└── click_id (визит/сессия — envelope из 1..N событий с общим device/geo)
└── event_id (отдельное событие: pageview, click, purchase ...)
```
- `click_id` — это **визит**, а не одиночный клик: в статическом сиде один
`click_id` честно группирует 1..27 событий, медиана 10 (`dds.click` =
«контекст сессии пользователя», см. `sql/ddl/dds/30_dds.sql`; точные цифры —
профиль сид-датасета в `CONTEXT.md`, ранняя оценка «1..7» была замером по
срезу файла).
- `user_domain_id` живёт в `device_events`, привязан к `click_id`.
## Что генератор делает правильно
Файл `generator/generator.py`, `_calculate_events_count()` (стр. ~232260):
- **Poisson-процесс** прихода событий: λ на минуту → λ на тик → розыгрыш Пуассона.
- **Дневной коэффициент** `_hour_factor()`: день (918) ×1.2, ночь (05) ×0.7.
- **Jitter** ±`GEN_JITTER_PCT`% и границы `min/max_events_per_tick`.
Это адекватная модель *интенсивности во времени*. Претензий к ней нет.
## Корневой дефект: модель сущностей в `generate_batch()`
Файл `generator/generator.py`, `generate_batch()` (стр. ~262–330). На каждое
событие в батче:
```python
base_browser = self.rng.choice(self.dictionary.browser_events) # случайная сид-строка
...
new_event_id = self._new_uuid()
new_click_id = self._new_uuid() # СВЕЖИЙ click_id на КАЖДОЕ событие
new_timestamp = self._current_timestamp() # ~now() для всех событий батча
...
device_event = {**base_device, "click_id": new_click_id} # user_domain_id наследуется из сида
```
Три уровня иерархии (пользователь → сессия → событие) схлопываются в плоскую
выборку:
1. **`click_id` свежий на каждое событие** → один визит = одно событие. Визит как
группа из нескольких событий **никогда не формируется**.
2. **`user_domain_id` переиспользуется** из сид-пула (~99 значений), но без
привязки к сессиям — события одного пользователя разлетаются отдельными
`click_id`. Иерархию «пользователь → его сессии → события сессии» из потока
**не восстановить**.
3. **Время** у всех событий батча ≈ `now()` — даже временно́й структуры визита
(последовательность событий внутри сессии) нет.
### Что это даёт в данных
| | Статический сид (`data/*.jsonl`) | Поток из генератора |
|---|---|---|
| `click_id` | визит-envelope (1..27 событий, медиана 10) | свежий на событие → `click_id` ≈ событие |
| `user_domain_id` | 1:1 с `click_id` | переиспользуется (потолок ~99) |
| Семантика | `Sessions == Users` (вырождено по пользователю) | `Sessions == Events` (сессия = одно событие) |
Парадокс: **статический сид как учебная основа лучше**, потому что на нём
`click_id` несёт осмысленную семантику визита. Генератор её ломает.
## Что перепроверить и переделать перед использованием
Чинить нужно **генеративную модель сущностей**, а не математику интенсивности:
1. **Иерархическая генерация вместо плоской выборки:**
- поддерживать популяцию пользователей с *постоянным* `user_domain_id`;
- пользователь со временем открывает 1..N **сессий** (новый `click_id` на
сессию, с межсессионными паузами — модель «вернувшегося пользователя»);
- сессия порождает последовательность из 1..M **событий**, разделяющих один
`click_id` и общий device/geo, упорядоченных по времени (правдоподобный путь
по страницам).
2. **Распределения, требующие проверки математики:**
- события на сессию (например, geometric/NB — длина визита);
- сессии на пользователя за период (возвраты);
- межсессионные интервалы (тайм-аут неактивности как граница сессии).
3. **Время событий** внутри сессии должно расти монотонно, а не быть `now()` для
всего батча.
4. **`event_id`/`click_id`** уже всегда новые (`uuid4`) — при иерархической
модели `click_id` должен переиспользоваться внутри сессии, а не на каждое
событие (см. оговорку в `README.md`, раздел State Recovery).
После такой переделки на потоке естественно получится здоровая пирамида
`users < sessions < events`, и дашборд сможет показывать разницу
«пользователь vs сессия» честными числами.
## Ссылки
- Код: `generator/generator.py``_calculate_events_count()` (стр. ~232260),
`generate_batch()` (стр. ~262330).
- Доменная модель: `sql/ddl/dds/30_dds.sql`, `sql/ddl/dm/40_dm.sql`.
- Контекст обсуждения: ветка `docs/advanced-clickstream-course`, дизайн
Superset-дашборда (урок 6 курса).