docs(generator): зафиксировано независимое ревью и задачи на потом
- Зачем:
- после coordinator-loop нужно независимое ревью результатов модельного
времени и стартовой истории; пропущенный внешний review gate после задачи 5
закрыт другой родословной.
- Что:
- добавлен verification-handoff: что проверено независимо, дефект стыка
(issue 09) и открытые пробелы (×K, crash recovery, коридоры мат-спеки,
воспроизводимость, review gate задачи 3).
- заведены issues 08 (портативный артефакт + runbook + идеи интерфейса),
09 (баг браузерной фактуры на стыке), 10 (читаемость гео-карты).
- в docs/course/PRD.md §7 — открытый вопрос «генератор как скрытая
инфраструктура vs отдельный урок».
- Проверка:
- git show --stat HEAD
- чтение .scratch/handoffs/2026-06-14-generator-model-time-verification-review.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+96
@@ -0,0 +1,96 @@
|
||||
Status: needs-triage
|
||||
|
||||
# Портативный артефакт стартовой истории и runbook по стенду
|
||||
|
||||
## Parent
|
||||
|
||||
`.scratch/generator-model-time-startup-history/PRD.md`
|
||||
|
||||
## Why
|
||||
|
||||
Сейчас стартовая история персистится только в Kafka compact-топиках
|
||||
(`generator_state`, `generator_startup_history_manifest`) и в ClickHouse. Чистая
|
||||
пересборка стенда (`make generated-history-analytics` делает `down -v`) каждый раз
|
||||
**заново генерирует** backfill. Портативного файла-артефакта, который можно
|
||||
сгенерировать один раз и быстро залить на чистый ClickHouse без запуска
|
||||
генератора, нет.
|
||||
|
||||
Из-за этого неудобно: раздать готовое демо, мгновенно сбросить стенд, держать
|
||||
длинную стартовую историю (2+ суток, чтобы суточная волна повторялась на графике)
|
||||
без повторной генерации. Сейчас «дёшево» только live-возобновление из слепка и
|
||||
перезапуск без `down -v`; полный сброс требует регенерации.
|
||||
|
||||
Этот пункт работает на главную цель: если стенд поднимается одной командой и есть
|
||||
короткий runbook, генератор становится скрытой инфраструктурой и менти не нужно
|
||||
знать его устройство. Связано с открытым вопросом курса про отдельный урок по
|
||||
генератору (см. `docs/course/PRD.md`, §7).
|
||||
|
||||
## What to build
|
||||
|
||||
- Экспорт стартовой истории в портативный файл-артефакт: события плюс слепок
|
||||
состояния плюс манифест — один связный набор, чтобы не смешать `GEN_SEED`,
|
||||
`T0`, `T_end` и настройки генерации.
|
||||
- Импорт: залить артефакт на чистый ClickHouse и Kafka без прогона генерации;
|
||||
live-режим продолжает с `T_end`.
|
||||
- Runbook «как пользоваться стендом на генерации»: как сгенерировать, сохранить,
|
||||
восстановить, выбрать длительность стартовой истории; что дёшево
|
||||
(live-возобновление, перезапуск без чистки), а что требует регенерации.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Есть команда экспорта: backfill -> портативный файл-артефакт
|
||||
(события + слепок + манифест).
|
||||
- [ ] Есть команда импорта: артефакт -> чистый ClickHouse без запуска генератора;
|
||||
контрольные числа манифеста и ClickHouse совпадают с исходной генерацией.
|
||||
- [ ] Сохранено антисмешивание: импорт отвергает артефакт, несовместимый по
|
||||
манифесту (`GEN_SEED`, `T0`, `T_end`, настройки генерации, версия state).
|
||||
- [ ] Runbook описывает генерацию один раз, дешёвое восстановление, выбор
|
||||
длительности и то, что переживает перезапуск, а что требует регенерации.
|
||||
- [ ] Документы запуска (`README.md`, `docs/OPERATIONS.md`,
|
||||
`generator/README.md`) ссылаются на runbook.
|
||||
|
||||
## Notes
|
||||
|
||||
- Опирается на спеку `docs/specs/2026-06-14-generator-model-time-and-startup-history.md`,
|
||||
разделы «Манифест стартовой истории» и «Повторяемая проверка в ClickHouse».
|
||||
- Спека уже упоминала будущий runbook «проверка генератора на стенде» — этот
|
||||
issue его и закрывает, расширяя до полного цикла «генерация — сохранение —
|
||||
восстановление».
|
||||
- Связано с `07-migrate-course-from-archive-seed.md`: удобный стенд упрощает выбор
|
||||
«адаптировать уроки», а не писать тяжёлый урок про генератор.
|
||||
|
||||
## Идеи интерфейса (на будущее, не решено)
|
||||
|
||||
Запуск сейчас недружелюбный: поведение собирается из ~10 связанных env-переменных,
|
||||
`T_end` задаётся абсолютной меткой вместо длительности, а несовпадение настроек при
|
||||
live-продолжении даёт тихий «свежий старт» (warning в лог, общее сообщение, без
|
||||
указания разошедшегося поля — `service.py:217-220`). Идеи, как сделать удобнее:
|
||||
|
||||
- **Глаголы вместо матрицы флагов:** явные `backfill` / `continue` / `reset`, а не
|
||||
комбинация `GEN_RUN_MODE` + `GEN_STATE_RESET`.
|
||||
- **Длительность как длительность и профили:** `HISTORY_DURATION=2d` вместо ручного
|
||||
расчёта `T_end`; именованные профили вместо повторения блока из ~10 переменных.
|
||||
- **Громкий и адресный отказ при несовпадении (пересмотр решения спеки).** Сейчас
|
||||
при `GEN_STATE_RESET=false` несовместимый по настройкам state молча ведёт к чистому
|
||||
старту (`service.py:217-220`) — это сознательный выбор спеки ради устойчивости.
|
||||
Предложение: различать два случая. Нет состояния или оно повреждено -> чистый старт
|
||||
с предупреждением (как сейчас, оставить). Состояние есть и читается, но настройки
|
||||
несовместимы при `GEN_STATE_RESET=false` (оператор намерен продолжить) -> **жёсткое
|
||||
падение** с указанием разошедшихся полей (`seed`/`T0`/`timezone`/`speed`) и подсказкой
|
||||
выставить `GEN_STATE_RESET=true`, если новый мир нужен осознанно. Меняет правило
|
||||
спеки «несовместимо -> чистый старт», поэтому правка идёт вместе с обновлением
|
||||
`docs/specs/2026-06-14-generator-model-time-and-startup-history.md`.
|
||||
- **Доливка прошлого кусочком:** backfill, продолжающий слепок от `T_end` (сейчас
|
||||
backfill всегда стартует с чистого состояния от `T0`, `service.py:178-185`).
|
||||
- **Airflow DAG как пульт запуска (идея пользователя, 2026-06-14):** обернуть операции
|
||||
генератора в параметризованный DAG (params: режим, `T0`, длительность, скорость,
|
||||
seed) — UI, валидация настроек против манифеста до запуска, повторные попытки,
|
||||
наглядность. Хорошо ложится на ограниченный backfill/доливку (конечная задача);
|
||||
непрерывный live — это долгоживущий сервис compose, DAG его скорее стартует/останавливает,
|
||||
чем держит внутри таска. Бонус: такой DAG сам по себе учебный (тема урока 4 —
|
||||
оркестрация Airflow), что ближе к цели курса, чем устройство генератора.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- `.scratch/generator-model-time-startup-history/issues/05-startup-history-backfill-to-clickhouse.md`
|
||||
- `.scratch/generator-model-time-startup-history/issues/06-generated-history-as-analytics-source.md`
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
Status: needs-triage
|
||||
|
||||
# Браузерная фактура не сохраняется на стыке восстановления визита
|
||||
|
||||
## Parent
|
||||
|
||||
`.scratch/generator-model-time-startup-history/PRD.md`
|
||||
|
||||
## Что нашли
|
||||
|
||||
Визит, переживший стык backfill->live (активен на `T_end` и продолжается живьём),
|
||||
меняет браузерную фактуру в live-продолжении. Внутри одного `click_id` человек как
|
||||
будто «пересел» с одного браузера на другой посреди сессии.
|
||||
|
||||
Эмпирика. Чистый стенд, 2 суток backfill + 6 live-тиков за `T_end`,
|
||||
`GEN_SEED=4242`, `GEN_MODEL_T0=2026-01-01T00:00:00+00:00`,
|
||||
`GEN_MODEL_T_END=2026-01-03T00:00:00+00:00`, `speed=1`:
|
||||
|
||||
- `crossing_visits=22`; `browser_name` отличается у `17/22`, `browser_language`
|
||||
у `22/22`; каждая сторона внутренне постоянна (`22/22`).
|
||||
- Чисто-исторические визиты (>=3 событий, целиком до `T_end`): браузер постоянен
|
||||
у `0/14556`.
|
||||
- device / os / geo на стыке НЕ расходятся: `0/22` конфликтов на уровне ODS.
|
||||
- `duplicate_events=0`: дублей на границе нет.
|
||||
- Пример `click_id`: в истории `Chrome / or_IN`, в live `Firefox / el_CY`.
|
||||
|
||||
## Почему это дефект
|
||||
|
||||
Нарушает заявленный критерий задач 04 и 05: «визит, переживший восстановление,
|
||||
остаётся однородным; контекст визита не меняется на стыке и не создаёт второй путь
|
||||
генерации внутри одного `click_id`». Браузер — часть контекста визита.
|
||||
|
||||
Критерий был помечен выполненным, но проверялся неполно: саморевью и ревьюер
|
||||
смотрели только поля `dds.click` (user / device / geo), которые по одной строке на
|
||||
`click_id` и потому однородны структурно. Per-event браузерные поля в `dds.event` не
|
||||
проверялись. Это ровно слепая зона «двух путей генерации», под которую PRD требовал
|
||||
внешний review gate другой родословной после задачи 5 — а он был выполнен той же
|
||||
родословной, и дефект прошёл.
|
||||
|
||||
## Причина (гипотеза по коду)
|
||||
|
||||
Конкретные браузерные строки визита (`ActiveVisit.batch`) в state не сериализуются:
|
||||
хранятся `seed_click_id`, `page_url_paths`, `timestamps`, `next_index` (`state.py`).
|
||||
При восстановлении live-путь пересобирает фактуру
|
||||
(`runtime.py._compact_visit_batch`: `browser_by_click_id[seed_click_id]`, индекс
|
||||
`event_index % len`). device и geo переживают восстановление, потому что привязаны к
|
||||
`UserProfile` (`user.device`, `user.geo`) и не зависят от индекса события; браузер
|
||||
зависит от выравнивания `event_index` между уже выпущенной частью и восстановленной —
|
||||
вероятен рассинхрон индекса. Точную точку уточнить при фиксе.
|
||||
|
||||
## Масштаб и важность
|
||||
|
||||
- Затрагивает только визиты, активные ровно на `T_end` (здесь 22 из 17397 визитов,
|
||||
~0.13%). Чистый backfill и чистый live — без дефекта.
|
||||
- Тот же механизм (фактура пересобирается при restore) вероятно затрагивает и
|
||||
восстановление после сбоя (задача 04), не только backfill->live; проверка задачи 04
|
||||
его не ловила, потому что смотрела только `dds.click`.
|
||||
- Для учебного демо урон низкий, но это реальное нарушение стыковой однородности и
|
||||
сигнал, что браузерная фактура не входит в восстановимое состояние визита.
|
||||
|
||||
## Направление фикса
|
||||
|
||||
- Либо сериализовать браузерную фактуру визита в state рядом с
|
||||
`page_url_paths`/`timestamps`, чтобы восстановленная часть точно продолжила
|
||||
выпущенную.
|
||||
- Либо выбирать браузер по абсолютному `event_index` визита, а не по позиции в
|
||||
остатке, чтобы restore воспроизводил те же строки.
|
||||
- В проверку стыка добавить per-event браузерные поля (`dds.event`), а не только
|
||||
`dds.click`, чтобы регресс ловился автоматически.
|
||||
|
||||
## Blocked by
|
||||
|
||||
- Нет. Это bug по результатам независимой проверки задач 05 и 06.
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
Status: needs-triage
|
||||
|
||||
# Гео-карта на дашборде нечитаема
|
||||
|
||||
## Parent
|
||||
|
||||
`.scratch/generator-model-time-startup-history/PRD.md` (раздел «Финальная ручная
|
||||
приёмка»: если панелей не хватает для просмотра — отдельная задача на панель).
|
||||
|
||||
## Что нашли
|
||||
|
||||
На финальной ручной приёмке (шаг 2 спеки) виджет «🗺️ Geography Map» показывает
|
||||
что-то, но прочитать нельзя:
|
||||
|
||||
- нет всплывающих подсказок (tooltip) — навёл на страну, значения не видно;
|
||||
- нет легенды и подписи шкалы — непонятно, что кодирует цвет и в каких единицах;
|
||||
- цветовая шкала неочевидна — подсвечено мало стран, остальное пусто.
|
||||
|
||||
Виз отрисовывается, но смысл непрозрачен: нельзя сказать, что именно он показывает.
|
||||
|
||||
## Две разные причины (не путать)
|
||||
|
||||
1. **Читаемость визуализации (эта задача).** Настройка чарта: включить tooltip,
|
||||
добавить легенду и подпись метрики/единиц, выбрать понятную цветовую шкалу,
|
||||
при необходимости — заменить мировую choropleth на более читаемую форму
|
||||
(например, топ-N стран баром), раз распределение сильно разрежено.
|
||||
2. **Перекос гео-данных (отдельно, ADR-0006).** Сама гео-фактура берётся из
|
||||
статического сида (`geo_by_click_id`) с перекошенным распределением стран. Это
|
||||
«временная опора на сид» до синтеза фактуры — лечится не настройкой чарта, а
|
||||
генерацией гео. Здесь не решаем, только отмечаем как смежную причину.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] У гео-виза есть tooltip со значением по стране.
|
||||
- [ ] Есть легенда и подпись: какая метрика и в каких единицах кодируется цветом.
|
||||
- [ ] Цветовая шкала читаема на текущем (разреженном) распределении, либо выбран
|
||||
более подходящий тип визуализации.
|
||||
- [ ] Зафиксировано, что перекос распределения стран — это вопрос гео-фактуры
|
||||
(ADR-0006), а не настройки чарта.
|
||||
|
||||
## Notes
|
||||
|
||||
- Всплыло на просмотре дашборда на данных генерации (2 суток backfill).
|
||||
- Смежные документы: `superset/create_dashboard.py` (определение чарта),
|
||||
`docs/SUPERSET_DASHBOARD.md`.
|
||||
- Возможно, стоит расширить до общего прохода по читаемости дашборда (легенды и
|
||||
подсказки у других виджетов), но базово задача — про гео-карту.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Handoff: независимое ревью модельного времени и стартовой истории
|
||||
|
||||
Дата: 2026-06-14
|
||||
Жанр: одноразовый handoff по ADR-0003.
|
||||
|
||||
## Контекст
|
||||
|
||||
После прогона coordinator-loop по фичам `feature-data-generator` и
|
||||
`generator-model-time-startup-history` (issues 01–06, реализация Codex) проведено
|
||||
независимое ревью результатов — другой родословной (Claude). Это в том числе
|
||||
закрыло пропущенный внешний review gate после задачи 5 из PRD: он был выполнен той
|
||||
же родословной, что и worker, а PRD требовал другую.
|
||||
|
||||
## Что проверили независимо (не по отчётам)
|
||||
|
||||
- `make generator-test` — `134 passed` (своим прогоном).
|
||||
- Код = «Рабочий контракт реализации» спеки буква в букву: настройки
|
||||
`GEN_MODEL_*`, state v2 (`model_timestamp/wall_timestamp/model_time_speed/…`),
|
||||
формула live-возобновления, полуоткрытая граница `[T0, T_end)` с
|
||||
`drain_until(include_boundary=False)`, манифест и антисмешивание.
|
||||
- Данные в ClickHouse реальные и сходятся с отчётами до цифры: 6 ч → 16 054;
|
||||
перепрогнали 1 сутки → 93 952; 2 суток → 187 087 событий / 17 397 визитов /
|
||||
2 765 пользователей.
|
||||
- Пирамида (users < visits < events), возвраты, монотонность обеих воронок,
|
||||
отсутствие архивного сида (все строки 2026 года).
|
||||
- Повторяемость суточной волны на 2 сутках: часовые числа day1≈day2
|
||||
(отношение в основном 0.9–1.1), форма «ночь–день» повторяется.
|
||||
- Стык backfill→live (live-доливка от `T_end=2026-01-03`): дублей 0, граница
|
||||
держится, device/os/гео на стыке однородны.
|
||||
- Дашборд: визуальная приёмка (шаг 2 спеки) — человеком и через Playwright.
|
||||
«Rows by Layer» (4 равных столбца = 187 312) корректен: пайплайн проносит
|
||||
событие 1:1, потерь нет, дедуп at-least-once на чистом backfill не возникает.
|
||||
|
||||
## Что нашли
|
||||
|
||||
- **issue 09 — реальный дефект стыка.** Браузерная фактура не переживает
|
||||
восстановление визита: у визитов, активных ровно на `T_end`, в live-продолжении
|
||||
меняется `browser_name`/`browser_language` (17/22 и 22/22), хотя внутри чистого
|
||||
backfill браузер постоянен (0/14556). device/гео не задеты. Нарушает заявленный
|
||||
критерий однородности задач 04/05; проверялось неполно (смотрели только поля
|
||||
`dds.click`, а не per-event браузер в `dds.event`). Масштаб ~0.13% визитов.
|
||||
- Гео-карта на дашборде нечитаема (нет легенды/подсказок/понятной шкалы) — issue 10.
|
||||
- Стартовый сид по умолчанию — 6 часов (быстрый профиль для CI); суточная волна на
|
||||
нём не видна, для просмотра нужен ≥2-суточный профиль через env.
|
||||
|
||||
## Открытые пробелы проверки (НЕ закрыты)
|
||||
|
||||
Ревью прошло по корректности кода, форме данных, стыку и дашборду, но НЕ трогало:
|
||||
|
||||
1. **×K не гоняли.** Все прогоны на `speed=1`. Ускорение модельного времени,
|
||||
событийный бюджет по модельной длительности и смена дневного коэффициента при
|
||||
×K проверены только в коде/по отчёту issue 03.
|
||||
2. **Live-crash recovery не воспроизводили.** Формула возобновления с настенной
|
||||
дельтой и закрытие просроченных визитов — только в коде. Гипотеза: дефект
|
||||
issue 09 бьёт и сюда (тот же restore-механизм), эмпирически не подтверждено.
|
||||
3. **Распределения не сверяли с коридорами мат-спеки** (`2026-06-10-generator-math-model.md`).
|
||||
Подтвердили форму (монотонность, возвраты, длина), но не попадание
|
||||
`confirmation_share` и доли коротких визитов в спроектированные коридоры.
|
||||
4. **Воспроизводимость сами не перепрогоняли** — идентичность checksum при
|
||||
повторном чистом прогоне взята из отчётов issue 03/05.
|
||||
5. **Второй review gate (после задачи 3) не делали** — систематический sweep на
|
||||
утечку настенных часов в расчёт интенсивности и сохранение состояния.
|
||||
|
||||
Помельче: не проверяли обработку повреждённого/старого state (fresh-start с
|
||||
предупреждением), `browser_user_agent` (тот же механизм, что issue 09), сам процесс
|
||||
coordinator-loop по правилам PRD.
|
||||
|
||||
## Зафиксированные follow-up (все needs-triage, на потом)
|
||||
|
||||
- `issues/07-migrate-course-from-archive-seed.md` — миграция уроков.
|
||||
- `issues/08-startup-history-portable-artifact-and-usage-docs.md` — портативный
|
||||
артефакт стартовой истории + runbook + идеи интерфейса (глаголы вместо флагов,
|
||||
длительность/профили, громкий отказ при несовпадении, доливка кусочком,
|
||||
Airflow-DAG как пульт).
|
||||
- `issues/09-seam-browser-fixture-not-preserved.md` — дефект браузерной фактуры.
|
||||
- `issues/10-dashboard-geo-map-readability.md` — читаемость гео-карты.
|
||||
- `docs/course/PRD.md` §7 — развилка «генератор как скрытая инфраструктура vs
|
||||
отдельный урок про генератор» (не грузить менти марковскими цепями).
|
||||
|
||||
## Состояние стенда
|
||||
|
||||
Поднят на 2 сутках генерации (+ несколько live-тиков от seam-проверки): ClickHouse,
|
||||
Kafka, Superset, postgres-metadata. Дашборд:
|
||||
`http://localhost:8088/superset/dashboard/ecommerce-analytics/`.
|
||||
|
||||
## Suggested skills
|
||||
|
||||
- `tdd` — для фикса issue 09 (один поведенческий тест на однородность браузера через
|
||||
стык, затем минимальная правка).
|
||||
- `conventional-commits` — перед коммитами.
|
||||
- `claude-team-review` / внешний reviewer другой родословной — на пробелы 1–5.
|
||||
|
||||
## Следующий шаг
|
||||
|
||||
Сегодня только фиксация, без правок. Дальше — приоритизировать issue 09 (баг) против
|
||||
08/10 (удобство/виз) и закрыть пробелы проверки 1–5 (особенно ×K и crash recovery).
|
||||
Reference in New Issue
Block a user