- Зачем:
- закрыть 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>
8.9 KiB
Генератор: известные проблемы и контекст для доработки
Статус (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() (стр. ~232–260):
- Poisson-процесс прихода событий: λ на минуту → λ на тик → розыгрыш Пуассона.
- Дневной коэффициент
_hour_factor(): день (9–18) ×1.2, ночь (0–5) ×0.7. - Jitter ±
GEN_JITTER_PCT% и границыmin/max_events_per_tick.
Это адекватная модель интенсивности во времени. Претензий к ней нет.
Корневой дефект: модель сущностей в generate_batch()
Файл generator/generator.py, generate_batch() (стр. ~262–330). На каждое
событие в батче:
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 наследуется из сида
Три уровня иерархии (пользователь → сессия → событие) схлопываются в плоскую выборку:
click_idсвежий на каждое событие → один визит = одно событие. Визит как группа из нескольких событий никогда не формируется.user_domain_idпереиспользуется из сид-пула (~99 значений), но без привязки к сессиям — события одного пользователя разлетаются отдельнымиclick_id. Иерархию «пользователь → его сессии → события сессии» из потока не восстановить.- Время у всех событий батча ≈
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 несёт осмысленную семантику визита. Генератор её ломает.
Что перепроверить и переделать перед использованием
Чинить нужно генеративную модель сущностей, а не математику интенсивности:
- Иерархическая генерация вместо плоской выборки:
- поддерживать популяцию пользователей с постоянным
user_domain_id; - пользователь со временем открывает 1..N сессий (новый
click_idна сессию, с межсессионными паузами — модель «вернувшегося пользователя»); - сессия порождает последовательность из 1..M событий, разделяющих один
click_idи общий device/geo, упорядоченных по времени (правдоподобный путь по страницам).
- поддерживать популяцию пользователей с постоянным
- Распределения, требующие проверки математики:
- события на сессию (например, geometric/NB — длина визита);
- сессии на пользователя за период (возвраты);
- межсессионные интервалы (тайм-аут неактивности как граница сессии).
- Время событий внутри сессии должно расти монотонно, а не быть
now()для всего батча. event_id/click_idуже всегда новые (uuid4) — при иерархической моделиclick_idдолжен переиспользоваться внутри сессии, а не на каждое событие (см. оговорку вREADME.md, раздел State Recovery).
После такой переделки на потоке естественно получится здоровая пирамида
users < sessions < events, и дашборд сможет показывать разницу
«пользователь vs сессия» честными числами.
Ссылки
- Код:
generator/generator.py—_calculate_events_count()(стр. ~232–260),generate_batch()(стр. ~262–330). - Доменная модель:
sql/ddl/dds/30_dds.sql,sql/ddl/dm/40_dm.sql. - Контекст обсуждения: ветка
docs/advanced-clickstream-course, дизайн Superset-дашборда (урок 6 курса).