docs(generator): уточнена мат-спека по итогам ревью, handoff для передачи
- Зачем:
- повторное адверсариальное ревью нашло ошибку в формуле межсессионной
паузы (двойной счёт кулдауна) и незакрытый контракт публикации
device/geo; дизайн-этап завершён, работа передаётся исполнителю.
- Что:
- исправлена формула паузы (баланс цикла, минус длительность визита);
зафиксированы каденция device/geo «на каждое событие, как в сиде»,
правило выбора возвращающегося, стартовое распределение страниц,
калибровка по медиане и среднему, инвариант потолков конфигурации,
компактное хранение профиля ссылкой на сид-сессию.
- уточнены критерии приёмки (среднее паузы вместо медианы, счёт шага
«товары», имена полей времени) в обеих спеках.
- добавлен handoff .scratch/handoffs/2026-06-10-generator-spec-to-codex.md
(передача на детальный план/реализацию), отработавший handoff от
2026-06-09 удалён.
- Проверка:
- цифры спеки сверены замерами по полным data/*.jsonl (стартовые страницы
58/24/17, шаги воронки 98>=94>=56>=35>=25, device 1000 строк / 99
уникальных); повторное ревью свежим агентом блокеров не оставило.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0b5fc64f6f
commit
bcca8f5127
@@ -1,52 +0,0 @@
|
||||
# Handoff: переработка генератора (steady-stream, иерархическая модель)
|
||||
|
||||
Дата: 2026-06-09
|
||||
Ветка: `feature/data-generator`
|
||||
Жанр: одноразовые леса́ (ADR-0003) — durable-рассуждение в ADR/спеке, не здесь.
|
||||
|
||||
> Место: `.scratch/handoffs/` по [ADR-0003](../../docs/adr/0003-handoffs-in-scratch.md)
|
||||
> и `AGENTS.md` (перекрывают generic-дефолт скилла «temp dir» — у владельца
|
||||
> мультимашинность + worktrees, где `/tmp` не переживает).
|
||||
|
||||
## Где остановились
|
||||
|
||||
Брейнсторм-сессия (`brainstorm-with-docs`) завершена **на этапе документов** —
|
||||
кода НЕ трогали. Зафиксировано направление переработки генератора и расчищена
|
||||
рамка под реализацию. Дальше — отдельная follow-up-спека по математике, затем
|
||||
реализация.
|
||||
|
||||
## Контекст и решения (ссылки, не пересказ)
|
||||
|
||||
- **Решение «почему» (генератор A, не реплей C):**
|
||||
[`docs/adr/0004-steady-stream-synthetic-generator.md`](../../docs/adr/0004-steady-stream-synthetic-generator.md)
|
||||
- **Форма доработки «что строим» (требования, без математики), статус Draft:**
|
||||
[`docs/specs/2026-06-09-generator-rework-hierarchical.md`](../../docs/specs/2026-06-09-generator-rework-hierarchical.md)
|
||||
— там же раздел **Open questions** (список вопросов для мат-спеки) и
|
||||
**Validation** (критерии приёмки). Не дублирую сюда.
|
||||
- **Глоссарий:** `CONTEXT.md` — добавлены «популяция пользователей» /
|
||||
«возвращающийся пользователь».
|
||||
- **Диагноз дефекта текущей реализации:** `generator/KNOWN_ISSUES.md`.
|
||||
|
||||
## Следующий шаг
|
||||
|
||||
**Написать follow-up-спеку по математике** генеративной модели. Перечень вопросов
|
||||
— в разделе *Open questions* спеки доработки (распределения, session-timeout,
|
||||
раскладка иерархии по тикам, персистентность популяции, `event_type` diversity).
|
||||
|
||||
## Ловушки (одной строкой; детали — в спеке)
|
||||
|
||||
- Воронка живёт в **`page_url_path`**, не в `event_type` (в сиде 100% `pageview`).
|
||||
- device/geo — **сессионный** контекст уровня `click`, не «свойство пользователя».
|
||||
- Модель интенсивности (`_calculate_events_count`, `_hour_factor`) — **сохраняем**;
|
||||
переписываем только модель сущностей (`generate_batch`). Текущую реализацию
|
||||
инкрементально не чиним.
|
||||
|
||||
## Suggested skills (для следующей сессии)
|
||||
|
||||
- **`conventional-commits`** — закоммитить готовые доки (см. git ниже).
|
||||
- **`brainstorm-with-docs`** (или `grill-with-docs`) — для follow-up-спеки по
|
||||
математике: продолжает тот же стиль (durable-доки + правка CONTEXT/ADR inline).
|
||||
- **`tdd`** — на этапе реализации rewrite (у генератора уже есть pytest-набор
|
||||
`generator/tests/`).
|
||||
- **`adversarial-review`** / **`code-review`** — ревью переписанной модели
|
||||
сущностей перед вливанием ветки.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Handoff: спеки генератора готовы — передача на детальный план и реализацию
|
||||
|
||||
Дата: 2026-06-10
|
||||
Ветка: `feature/data-generator`
|
||||
Жанр: одноразовые леса́ (ADR-0003) — durable-рассуждение в спеках/ADR/CONTEXT, не здесь.
|
||||
|
||||
> Место: `.scratch/handoffs/` по [ADR-0003](../../docs/adr/0003-handoffs-in-scratch.md)
|
||||
> (перекрывает generic-дефолт скилла «temp dir»: мультимашинность + worktrees).
|
||||
> Предыдущий handoff (2026-06-09-generator-rework.md) отработал и удалён.
|
||||
|
||||
## Где остановились
|
||||
|
||||
Дизайн-этап переработки генератора **завершён**. Спека математической модели
|
||||
написана, прошла **два** адверсариальных ревью свежими агентами (второе ревью
|
||||
ловило ошибки правок первого — практика себя оправдала), все находки закрыты,
|
||||
ключевые цифры перепроверены замерами по полному сиду. Кода по-прежнему
|
||||
не трогали.
|
||||
|
||||
Разделение ролей (см. память проекта): дизайн/спеки — Fable, **детальный план
|
||||
и реализация — Codex 5.5**. Следующая сессия — скорее всего, подготовка задачи
|
||||
для Кодекса или сама реализация.
|
||||
|
||||
## Документы (источники истины, не пересказываю)
|
||||
|
||||
- **Мат-модель (главный документ для исполнителя):**
|
||||
`docs/specs/2026-06-10-generator-math-model.md` — марковская цепочка по
|
||||
страницам, формула «популяция ↔ интенсивность ↔ пауза», кулдаун, правило
|
||||
30 минут на рестарт, критерии приёмки.
|
||||
- **Форма доработки:** `docs/specs/2026-06-09-generator-rework-hierarchical.md`
|
||||
(Open questions закрыты ссылкой на мат-спеку).
|
||||
- **Почему генератор, а не реплей:** `docs/adr/0004-steady-stream-synthetic-generator.md`.
|
||||
- **Профиль сид-датасета (опора калибровки):** `CONTEXT.md`, раздел
|
||||
«Профиль сид-датасета» — измерено по полным файлам 2026-06-10.
|
||||
- **Диагноз дефекта старого кода:** `generator/KNOWN_ISSUES.md`.
|
||||
|
||||
## Ловушки (одной строкой; детали — в спеках)
|
||||
|
||||
- Ранний «факт» **«1..7 событий на визит» был неверен** (срез файла); реально
|
||||
1..27, медиана 10. Если встретишь «1..7» где-то ещё в доках/коде — это
|
||||
остатки ошибки, чинить по профилю сида.
|
||||
- **Формула паузы в мат-спеке обязательна** при смене λ/популяции; кулдаун в
|
||||
неё не прибавляется (уже учтён балансом). Вторая ревизия исправляла именно
|
||||
двойной счёт — не откатить случайно.
|
||||
- **device/geo публикуются на каждое событие** (как в сиде) — это контракт с
|
||||
ETL, не деталь.
|
||||
- `event_timestamp` событий — **запланированное** время, не момент отправки
|
||||
тика (иначе метки прилипают к сетке тиков).
|
||||
- Модель интенсивности (`_calculate_events_count`) сохраняем; дефолт
|
||||
`GEN_LAMBDA_BASE_PER_MIN` меняется 200 → 30.
|
||||
|
||||
## Следующий шаг
|
||||
|
||||
Передать пару спек Кодексу: детальный план реализации → код. Уровень спек
|
||||
сознательно «решения и инварианты, без алгоритмов» — конкретику исполнитель
|
||||
достраивает сам. Возможная подготовка: оформить задачу в `.scratch/<feature>/`
|
||||
по `docs/agents/issue-tracker.md` (скилл `to-issues`, если план дробить).
|
||||
|
||||
## Suggested skills (для следующей сессии)
|
||||
|
||||
- **`to-issues`** — если решим дробить реализацию на задачи в локальном трекере.
|
||||
- **`tdd`** — для этапа реализации (pytest-набор `generator/tests/` существует,
|
||||
но писался под старую модель — пересмотр под новую неизбежен).
|
||||
- **`adversarial-review`** / **`code-review`** — ревью реализации против
|
||||
мат-спеки перед вливанием.
|
||||
- **`conventional-commits`** — коммиты по правилам репозитория.
|
||||
@@ -138,9 +138,10 @@
|
||||
|
||||
- `uniqExact(user_domain_id) < uniqExact(click_id) < count(event_id)` —
|
||||
пирамида не вырождена (есть возвраты и мультисобытийные сессии).
|
||||
- У `click_id` с несколькими событиями `event_ts` строго **возрастает**;
|
||||
мультисобытийные визиты составляют заметную долю потока (одностраничные
|
||||
визиты-отказы допустимы и правдоподобны).
|
||||
- У `click_id` с несколькими событиями время строго **возрастает**
|
||||
(`event_timestamp` в источнике, `event_ts` в DDS — проверять можно в любой
|
||||
точке); мультисобытийные визиты составляют заметную долю потока
|
||||
(одностраничные визиты-отказы допустимы и правдоподобны).
|
||||
- Один `click_id` принадлежит ровно одному `user_domain_id`; у пользователя
|
||||
бывает несколько `click_id`.
|
||||
- Воронка по `page_url_path` затухает от шага к шагу; доля визитов, доходящих
|
||||
|
||||
@@ -44,7 +44,10 @@
|
||||
3. **Популяция: постоянное ядро + медленная ротация.** Размер активной
|
||||
популяции ограничен (память не растёт), но новые пользователи понемногу
|
||||
приходят, давно неактивные — выбывают. Кумулятивное число уникальных
|
||||
пользователей растёт со временем — стенд выглядит живым.
|
||||
пользователей растёт со временем — стенд выглядит живым. «Медленно» — в
|
||||
масштабе наблюдения: на ориентирах по умолчанию ядро полностью сменяется
|
||||
примерно за 11 часов (это осознанный баланс: возвраты видны за вечер,
|
||||
рост uniques — за сутки).
|
||||
4. **Путь по страницам — марковская цепочка.** Для каждой страницы задана
|
||||
вероятность перехода на каждую другую страницу или ухода с сайта. Один
|
||||
механизм даёт сразу: длину визита, затухающую воронку, петли (возвраты на
|
||||
@@ -73,12 +76,19 @@
|
||||
device/geo-контекст одной случайной сид-сессии (включая `user_custom_id`;
|
||||
повторы email между пользователями допустимы — демо-данные) с заменой
|
||||
`user_domain_id` на свежий. Профиль используется во всех визитах пользователя
|
||||
(KISS: смену устройства не моделируем).
|
||||
(KISS: смену устройства не моделируем). В состоянии профиль хранится
|
||||
**ссылкой на сид-сессию** (её `click_id`) плюс свежие идентификаторы, а не
|
||||
копией всех полей — состояние остаётся компактным.
|
||||
|
||||
### Визиты (сессии)
|
||||
|
||||
- Визит = новый `click_id`, общий для всех его событий, с device/geo из профиля
|
||||
пользователя.
|
||||
- **Каденция публикации device/geo — как в сиде и у текущего генератора:**
|
||||
записи `device_events` и `geo_events` публикуются **на каждое событие**
|
||||
(в пределах визита дублируются с одинаковым содержимым; в сиде это
|
||||
проверено: 1000 строк при 99 уникальных по содержимому). Контракт данных с
|
||||
ETL/ODS не меняется — это требование, а не деталь реализации.
|
||||
- **Связка с моделью интенсивности.** `_calculate_events_count()` сохраняется и
|
||||
выдаёт на тик *бюджет событий*. Бюджет конвертируется в рождения визитов:
|
||||
ожидаемое число новых визитов за тик = бюджет ÷ средняя длина визита
|
||||
@@ -92,35 +102,47 @@
|
||||
- **Кулдаун возврата:** после завершения визита пользователь недоступен для
|
||||
нового минимум `GEN_MIN_RETURN_MINUTES` (ориентир 30 мин) — это гарантирует
|
||||
нижнюю границу межсессионной паузы (инвариант пауз).
|
||||
- **Выбор возвращающегося:** равновероятно среди **доступных** пользователей
|
||||
(не в кулдауне и без активного визита) — это даёт естественный разброс пауз.
|
||||
Если доступных нет (краевой случай при экстремальных параметрах) — визит
|
||||
достаётся новому пользователю.
|
||||
- **Межсессионные паузы — эмерджентная величина, не свободный параметр.**
|
||||
Пауза складывается из кулдауна и времени ожидания «своей очереди» и в среднем
|
||||
определяется балансом потока и популяции:
|
||||
В стационарном режиме полный цикл пользователя (визит + пауза) определяется
|
||||
балансом потока и популяции — кулдаун уже «сидит» внутри этого баланса и
|
||||
отдельно не прибавляется (он задаёт только нижнюю границу каждой паузы):
|
||||
|
||||
```
|
||||
средняя пауза ≈ кулдаун + GEN_POPULATION_MAX ÷ (λ ÷ L × (1 − GEN_P_NEW_USER))
|
||||
средняя пауза ≈ GEN_POPULATION_MAX ÷ (λ ÷ L × (1 − GEN_P_NEW_USER)) − средняя длительность визита
|
||||
где λ — событий/мин, L — средняя длина визита
|
||||
```
|
||||
|
||||
На ориентирах по умолчанию (λ=30, L≈10, популяция 300, p_new=0.15):
|
||||
возвратов ≈ 2.6/мин, пауза ≈ **~2 часа** — больше 30-минутного окна и при
|
||||
этом возвраты видны уже за один вечер работы стенда. **Кто меняет λ или
|
||||
размер популяции — обязан пересчитать паузу по формуле**: эти три величины
|
||||
связаны, их нельзя крутить независимо.
|
||||
возвратов ≈ 2.6/мин, цикл ≈ 118 мин, средняя пауза ≈ **~110 мин (~2 часа)**;
|
||||
медиана ниже среднего (распределение скошено вправо), порядка 1.5 ч — всё
|
||||
равно сильно больше 30-минутного окна, и возвраты видны уже за один вечер
|
||||
работы стенда. **Кто меняет λ или размер популяции — обязан пересчитать
|
||||
паузу по формуле**: эти величины связаны, их нельзя крутить независимо.
|
||||
Ориентиры здесь посчитаны при L=10; после калибровки таблицы переходов
|
||||
производные числа пересчитываются от измеренного L.
|
||||
|
||||
### События и путь по страницам
|
||||
|
||||
- **Марковская цепочка:** страницы — `/home`, `/product_a`, `/product_b`,
|
||||
`/cart`, `/payment`, `/confirmation` плюс исход «ушёл». Визит начинается с
|
||||
входной страницы (как правило `/home`), дальше каждый следующий шаг
|
||||
разыгрывается по таблице переходов текущей страницы. Петли разрешены
|
||||
(вернуться на главную, посмотреть оба товара, продолжить ходить после
|
||||
`/confirmation` — как в сиде). Защита от зацикливания — потолок длины визита
|
||||
`GEN_MAX_SESSION_EVENTS` (ориентир 30, как максимум в сиде).
|
||||
- **Калибровка по сиду:** конкретные значения таблицы подбирает исполнитель под
|
||||
два целевых показателя из профиля сида (см. `CONTEXT.md`): доля визитов,
|
||||
достигших `/confirmation`, ≈ 25%, и длина визита с медианой ~10 событий.
|
||||
Точного совпадения распределений не требуется — требуется сопоставимость,
|
||||
чтобы дашборд на потоке показывал привычные по сиду цифры.
|
||||
`/cart`, `/payment`, `/confirmation` плюс исход «ушёл». Визит начинается со
|
||||
страницы, разыгранной по **стартовому распределению** (оно — часть модели:
|
||||
в сиде `/home` — лишь ~59% входов, остальные входят на страницы товаров),
|
||||
дальше каждый следующий шаг разыгрывается по таблице переходов текущей
|
||||
страницы. Петли разрешены (вернуться на главную, посмотреть оба товара,
|
||||
продолжить ходить после `/confirmation` — как в сиде). Защита от
|
||||
зацикливания — потолок длины визита `GEN_MAX_SESSION_EVENTS` (ориентир 30,
|
||||
как максимум в сиде).
|
||||
- **Калибровка по сиду:** стартовое распределение и значения таблицы подбирает
|
||||
исполнитель под целевые показатели из профиля сида (см. `CONTEXT.md`): доля
|
||||
визитов, достигших `/confirmation`, ≈ 25%, длина визита с медианой ~10 и
|
||||
**средним ~10** (в сиде среднее ≈ медиане; у цепочки с геометрическим
|
||||
хвостом среднее легко уезжает выше — следить за обоими). Точного совпадения
|
||||
распределений не требуется — требуется сопоставимость, чтобы дашборд на
|
||||
потоке показывал привычные по сиду цифры.
|
||||
- **Паузы внутри визита:** интервал между соседними событиями — величина
|
||||
масштаба *секунд—минут* (например, лог-нормальное: большинство пауз короткие,
|
||||
изредка длинные). Ориентир: медиана десятки секунд. Форма распределения и
|
||||
@@ -152,7 +174,10 @@
|
||||
Число одновременно активных визитов ограничено (`GEN_MAX_ACTIVE_SESSIONS`) —
|
||||
защита памяти; при достижении потолка новые рождения в этот тик пропускаются
|
||||
(бюджет не копится). На ориентирах по умолчанию стационарно активны ~15–30
|
||||
визитов — потолок с большим запасом.
|
||||
визитов — потолок с большим запасом. **Инвариант конфигурации:**
|
||||
`GEN_MAX_ACTIVE_SESSIONS < GEN_POPULATION_MAX` — иначе правило вытеснения
|
||||
(«без активного визита») может не найти кандидата; валидировать при старте,
|
||||
как существующие проверки `Config`.
|
||||
|
||||
## Персистентность через рестарты
|
||||
|
||||
@@ -212,15 +237,16 @@
|
||||
- Средняя интенсивность событий за длинное окно (час и больше) соответствует
|
||||
целевой `λ × часовой коэффициент` с разумным отклонением.
|
||||
- Паузы внутри `click_id`: 95-й перцентиль — единицы минут, максимум < 30 минут.
|
||||
- Межсессионные паузы одного пользователя: минимум ≥ кулдауна, медиана —
|
||||
порядка расчётной по формуле (~2 ч на дефолтах).
|
||||
- Межсессионные паузы одного пользователя: минимум ≥ кулдауна, **среднее** —
|
||||
порядка расчётного по формуле (~2 ч на дефолтах; медиана ниже, ~1.5 ч).
|
||||
- Доля визитов новых пользователей за длинное окно ≈ `GEN_P_NEW_USER`;
|
||||
кумулятивное число уникальных `user_domain_id` растёт со временем, размер
|
||||
состояния генератора — нет.
|
||||
- Длина визита (медиана ~10, максимум ≤ потолка) и доля дошедших до
|
||||
`/confirmation` (~25%) сопоставимы с профилем сида из `CONTEXT.md`.
|
||||
- Воронка затухает по шагам: число визитов, посетивших страницу, монотонно
|
||||
убывает вдоль `/home → товары → /cart → /payment → /confirmation`.
|
||||
- Воронка затухает по шагам: число визитов, посетивших шаг, монотонно убывает
|
||||
вдоль `/home → товары → /cart → /payment → /confirmation` (шаг «товары» —
|
||||
визит посетил хотя бы одну из страниц товаров; так же считает дашборд).
|
||||
- После рестарта с простоем ≤ 30 минут активные визиты продолжаются (нет
|
||||
скачка «все пользователи новые»); после долгого простоя закрываются только
|
||||
просроченные визиты, популяция сохраняется.
|
||||
@@ -236,6 +262,8 @@
|
||||
на визит» (по полному замеру — 1..27, медиана 10).
|
||||
- Урок 7 «Система живёт» (пишется на этапе реализации): марковская таблица
|
||||
переходов — учебная «ручка» урока (поменяй вероятность → увидь сдвиг
|
||||
конверсии на дашборде).
|
||||
конверсии на дашборде). Оговорка скоупа: «увидеть на дашборде» предполагает,
|
||||
что поток доезжает до витрин — это зависимость урока 7 от инкрементальной
|
||||
загрузки ETL (план v2), а не требование к генератору.
|
||||
- `generator/README.md` — обновляется при реализации; туда же — верхнеуровневый
|
||||
обзор «как работает генератор» (два контура: интенсивность и сущности).
|
||||
|
||||
Reference in New Issue
Block a user