- Зачем:
- follow-up задачи после ревью 2026-06-14 лежали без триажа; решения по
артефакту, громкому отказу и интерфейсу приняты 2026-07-04 и должны
попасть в задачи до передачи исполнителю.
- Что:
- задачи 07 (миграция курса) и 08 (артефакт) поменяны местами — номера
отражают порядок; ссылки обновлены.
- 07 (артефакт + runbook) дооформлен: импорт строго через Kafka (напрямую
в ClickHouse не пишет), громкий отказ при несовместимом state с правкой
спеки, граница runbook «использование, не устройство»; ready-for-agent.
- 08 дооформлен: устройство генератора вне пути менти, реальный объём
(make data во всех уроках 00-05), демо вне скоупа; ready-for-agent.
- новые задачи: 11 глаголы/длительность/профили (после 07, до 12),
12 Airflow-DAG как пульт (приоритет поднят), 13 доливка (после 09).
- задачи 01-06 переведены в done (стояли ошибочные ready-for-human);
PRD фичи дополнен списком задач 7-13 с порядком и зависимостями.
- Проверка:
- head -1 .scratch/generator-model-time-startup-history/issues/*.md;
grep по старым именам файлов ничего не находит вне handoff.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
219 lines
9.3 KiB
Markdown
219 lines
9.3 KiB
Markdown
Status: done
|
||
|
||
# Стартовая история до ClickHouse
|
||
|
||
## Parent
|
||
|
||
`.scratch/generator-model-time-startup-history/PRD.md`
|
||
|
||
## What to build
|
||
|
||
Сделать промотку прошлого: генератор быстро проходит от `T0` до `T_end`, создаёт
|
||
события за этот отрезок, сохраняет слепок состояния и манифест стартовой
|
||
истории. Эту историю нужно загрузить в ClickHouse и доказать, что живой поток
|
||
может продолжить её с `T_end`.
|
||
|
||
Это главный срез стартовой истории. Он должен проверять не только факт наличия
|
||
данных, но и форму данных: пирамиду, возвраты, длину визита, воронку и стык
|
||
между прошлым и живым продолжением.
|
||
|
||
## Acceptance criteria
|
||
|
||
- [x] Промотка прошлого создаёт события за `[T0, T_end)`, слепок состояния и
|
||
манифест с контрольными данными.
|
||
- [x] При одинаковых `GEN_SEED`, `T0`, `T_end` и настройках артефакт промотки
|
||
прошлого повторяем точно; проверки в ClickHouse следуют правилам допуска из
|
||
контракта задачи 1.
|
||
- [x] История загружается в ClickHouse штатной или явно описанной командой.
|
||
- [x] SQL-проверка показывает здоровую пирамиду: пользователей меньше, чем
|
||
визитов, визитов меньше, чем событий.
|
||
- [x] SQL-проверка показывает возвраты: у части пользователей больше одного
|
||
визита.
|
||
- [x] SQL-проверка длины визита проверяет форму, а не только среднее: долю
|
||
коротких визитов, медиану и долю срезов о потолок.
|
||
- [x] SQL-проверка воронки `/home -> товары -> /cart -> /payment ->
|
||
/confirmation` монотонно убывает, а доля дошедших до `/confirmation` в
|
||
согласованном коридоре.
|
||
- [x] Живое продолжение после `T_end` не создаёт дублей на границе и не выглядит
|
||
как независимый второй мир.
|
||
- [x] Визиты, которые переходят через `T_end`, остаются однородными: контекст
|
||
визита не меняется на стыке стартовой истории и живого продолжения.
|
||
- [x] Подготовлены данные, команды и SQL-проверки, достаточные для внешнего
|
||
review gate по распределениям и двум путям генерации из `PRD.md`.
|
||
- [x] Worker даёт промежуточный статус, если промотка, загрузка в ClickHouse или
|
||
распределительные проверки занимают заметное время.
|
||
|
||
## Blocked by
|
||
|
||
- `.scratch/generator-model-time-startup-history/issues/04-state-v2-model-resume.md`
|
||
|
||
## Фактический прогон worker-а
|
||
|
||
Дата проверки: 2026-06-14.
|
||
|
||
Настройки стенда:
|
||
|
||
- `GEN_SEED=4242`
|
||
- `GEN_MODEL_T0=2026-01-01T00:00:00+00:00`
|
||
- `GEN_MODEL_T_END=2026-01-01T06:00:00+00:00`
|
||
- `GEN_MODEL_TIMEZONE=UTC`
|
||
- `GEN_MODEL_TIME_SPEED=1`
|
||
- `GEN_TICK_SECONDS=60`
|
||
- `GEN_LAMBDA_BASE_PER_MIN=120`
|
||
- `GEN_JITTER_PCT=0`
|
||
- `GEN_MIN_EVENTS_PER_TICK=1`
|
||
- `GEN_MAX_EVENTS_PER_TICK=1000`
|
||
|
||
Команды проверки:
|
||
|
||
```bash
|
||
make clean
|
||
docker compose up -d clickhouse kafka
|
||
make ddl
|
||
docker compose build generator
|
||
|
||
docker compose run --rm --no-deps \
|
||
-e GEN_RUN_MODE=backfill \
|
||
-e GEN_STATE_RESET=true \
|
||
-e GEN_SEED=4242 \
|
||
-e GEN_MODEL_T0=2026-01-01T00:00:00+00:00 \
|
||
-e GEN_MODEL_T_END=2026-01-01T06:00:00+00:00 \
|
||
-e GEN_MODEL_TIMEZONE=UTC \
|
||
-e GEN_MODEL_TIME_SPEED=1 \
|
||
-e GEN_TICK_SECONDS=60 \
|
||
-e GEN_LAMBDA_BASE_PER_MIN=120 \
|
||
-e GEN_JITTER_PCT=0 \
|
||
-e GEN_MIN_EVENTS_PER_TICK=1 \
|
||
-e GEN_MAX_EVENTS_PER_TICK=1000 \
|
||
generator
|
||
|
||
sleep 10
|
||
bash scripts/run_batch.sh
|
||
```
|
||
|
||
Повторяемость проверена двумя чистыми прогонами с полным сбросом
|
||
ClickHouse/Kafka/state через `make clean`. В обоих прогонах manifest и ClickHouse
|
||
дали одинаковые контрольные числа:
|
||
|
||
- manifest `browser_events.checksum_sha256`:
|
||
`3b1946bd7fd3d669e2461fcbae88d8aed6980bb5f126779fb4e7480ce15a3269`
|
||
- manifest `location_events.checksum_sha256`:
|
||
`370816c8d0b0311592282b9b75372e18862db9ef4984f7032885e935f9436522`
|
||
- manifest `device_events.checksum_sha256`:
|
||
`37e157ac7d5a3203c1c27b2a11abc1b9cb9fc7f74f79c77645faa09412e08b33`
|
||
- manifest `geo_events.checksum_sha256`:
|
||
`ec0e36e4613e05b9a5e72490c7565cf7e5f767d39a7a58b383e56e2eb191fc20`
|
||
- ClickHouse digest:
|
||
`CC1A73E65E897D1F1FC982CFA4237A07`
|
||
|
||
Backfill в ClickHouse:
|
||
|
||
- `events=31825`
|
||
- `unique_events=31825`
|
||
- `visits=3020`
|
||
- `users=1048`
|
||
- `min_event_ts=2026-01-01 00:00:00.000000`
|
||
- `max_event_ts=2026-01-01 05:59:59.521599`
|
||
- `pyramid_ok=1`
|
||
- `half_open_ok=1`
|
||
|
||
Проверка возвратов:
|
||
|
||
- `users=1048`
|
||
- `returning_users=708`
|
||
- `returning_share=0.6755725190839694`
|
||
- `max_visits_per_user=11`
|
||
|
||
Форма длины визита:
|
||
|
||
- `visits=3020`
|
||
- `short_visit_share=0.16490066225165562`
|
||
- `median_events_per_visit=8`
|
||
- `avg_events_per_visit=10.538079470198676`
|
||
- `capped_visit_share=0.0619205298013245`
|
||
- `median_duration_sec=202`
|
||
- `p95_duration_sec=823`
|
||
- `max_events_per_visit=30`
|
||
|
||
Воронка:
|
||
|
||
- строгая упорядоченная:
|
||
`home=2743`, `products=1626`, `cart=995`, `payment=673`,
|
||
`confirmation=394`, `monotonic_ok=1`,
|
||
`ordered_confirmation_share=0.14363835216915785`;
|
||
- калибровочная по наличию страницы в визите:
|
||
`home=2743`, `products=2706`, `cart=2003`, `payment=1368`,
|
||
`confirmation=824`, `monotonic_ok=1`,
|
||
`confirmation_share_all_visits=0.2728476821192053`,
|
||
`confirmation_share_from_home=0.3004010207801677`.
|
||
|
||
Live-продолжение:
|
||
|
||
```bash
|
||
docker compose run -d --name issue05-live --no-deps \
|
||
-e GEN_RUN_MODE=live \
|
||
-e GEN_STATE_RESET=false \
|
||
-e GEN_SEED=4242 \
|
||
-e GEN_MODEL_T0=2026-01-01T00:00:00+00:00 \
|
||
-e GEN_MODEL_T_END=2026-01-01T06:00:00+00:00 \
|
||
-e GEN_MODEL_TIMEZONE=UTC \
|
||
-e GEN_MODEL_TIME_SPEED=1 \
|
||
-e GEN_TICK_SECONDS=60 \
|
||
-e GEN_LAMBDA_BASE_PER_MIN=120 \
|
||
-e GEN_JITTER_PCT=0 \
|
||
-e GEN_MIN_EVENTS_PER_TICK=1 \
|
||
-e GEN_MAX_EVENTS_PER_TICK=1000 \
|
||
generator
|
||
```
|
||
|
||
Лог live-запуска подтвердил восстановление из стартовой истории:
|
||
`model_time=2026-01-01T06:00:00+00:00`, затем прошли тики `361`-`364`.
|
||
|
||
После live и повторного `bash scripts/run_batch.sh`:
|
||
|
||
- `users=1079`
|
||
- `visits=3069`
|
||
- `events=32145`
|
||
- `min_event_ts=2026-01-01 00:00:00.000000`
|
||
- `max_event_ts=2026-01-01 06:03:00.000000`
|
||
- `history_events=31825`
|
||
- `live_events=320`
|
||
- `duplicate_events=0`
|
||
- `boundary_events=13`
|
||
|
||
`boundary_events=13` — это live-события ровно на `T_end`; они не входили в
|
||
backfill `[T0, T_end)`.
|
||
|
||
Визиты через `T_end`:
|
||
|
||
- `crossing_visits=36`
|
||
- `visits_with_both_sides=36`
|
||
- `homogeneous_visits=36`
|
||
- `context_ok=1`
|
||
- `min_before_events=2`
|
||
- `min_after_events=1`
|
||
- `max_after_events=10`
|
||
|
||
## Риски для review gate
|
||
|
||
- Manifest, state и события связаны метаданными (`GEN_SEED`, `T0`, `T_end`,
|
||
настройки генерации, `last_batch_id`) и checksum событий по топикам. Полной
|
||
криптографической связки `state + events + manifest` пока нет.
|
||
- Для воронки есть две SQL-формы. Строгая ordered-форма проверяет порядок
|
||
`/home -> товары -> /cart -> /payment -> /confirmation`. Калибровочная
|
||
contains-форма проверяет, была ли страница в визите. Для review gate
|
||
`confirmation_share` сравнивается с коридором мат-модели по contains-форме.
|
||
|
||
## Review gate
|
||
|
||
- Саморевью worker-а нашло блокирующий риск: backfill не должен сохранять
|
||
state/manifest при частичной публикации. Исправлено fail-fast поведением и
|
||
тестом.
|
||
- Первый reviewer gate нашёл два блокера: startup-history state мог остаться без
|
||
manifest и восстановиться как live-state, а startup-history restore не сверял
|
||
поля самого state. Оба пункта исправлены до коммита.
|
||
- Повторный reviewer gate: `gate pass`, новых блокирующих находок нет.
|
||
- Проверки после исправлений: `make generator-test` — 134 passed.
|
||
- Остаточный риск процесса: reviewer был со свежим контекстом, но той же
|
||
родословной, а не внешней моделью.
|