Compare commits
4
Commits
1fccaf8599
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd467bf43 | ||
|
|
dda4f3aeb5 | ||
|
|
05e4cdea4d | ||
|
|
9d3a8053d3 |
@@ -1,78 +0,0 @@
|
||||
# Handoff: спека «Боевой реализм стенда (v2)» — ревью и приёмка (#17)
|
||||
|
||||
Дата: 2026-07-30 10:03. Сессия работала над issue #17 (Gitea, CLI `tea`):
|
||||
сборка спеки из резолюций карты #10.
|
||||
|
||||
## Состояние
|
||||
|
||||
- Спека: `docs/specs/2026-07-30-stand-v2-realism.md` (Draft).
|
||||
**Файл untracked** — при сбое машины git его не защитит; владельцу
|
||||
предложен WIP-коммит.
|
||||
- Источники истины: резолюции в комментариях issues #14, #15, #16, #18;
|
||||
карта #10 (там же решение: v2 — новый пустой репозиторий, рабочее имя
|
||||
`clickstream-data-platform`, v1 замораживается). Фактура:
|
||||
`docs/research/2026-07-26-yandex-clickstream-format.md`.
|
||||
|
||||
## Что сделано
|
||||
|
||||
1. Спека собрана и прошла четыре волны правок (всё уже в файле):
|
||||
владельца (v2 — новый репозиторий; доки переосмыслить, не копировать;
|
||||
лабы с нуля, вне скоупа; генератор с нуля), вычитки (термины, `K` только
|
||||
как окно), сверки с резолюциями (12 правок: бюджет 16 ГБ, «ландшафт
|
||||
итогом», потерянные отклонения, помеченные отступления) и грилинга
|
||||
(17 решений: расщеплённый дедуп заказов, пересборка K+1 партиций,
|
||||
сессии по границе суток, обязательный заказ с каждой куки двухкукового,
|
||||
сверка по сравнимой базе, строгий приём JSON, `dds.v_event`,
|
||||
`SAMPLE BY`, «схема как контракт», объём ~80–110 файлов, этапы слиты
|
||||
и дополнены этапом Airflow).
|
||||
2. Решение владельца по генератору: Python с производительной
|
||||
архитектурой; компилируемый язык — только если замеры не пройдут
|
||||
(в спеке, раздел 11).
|
||||
|
||||
## Открытые вопросы владельцу (спека их пометила как отступления)
|
||||
|
||||
- Порядок страховочных срезов: #15 предписывал 1→2→3, спека применяет
|
||||
3 и 2, срез 1 (статусы) держит в резерве.
|
||||
- `Sign` на хитах: исследование советовало взять колонку, спека не берёт.
|
||||
- `VisitID` оставлен в событии как эталон самопроверки лабы сессий.
|
||||
|
||||
## В полёте на момент записки
|
||||
|
||||
- Sonnet-агент довносил 17 правок грилинга: маркеры всех правок в файле
|
||||
уже есть; свежей сессии — убедиться, что файл цел (маркеры:
|
||||
`skip_unknown_fields`, `_ingested_at`, `dds.v_event`, `SAMPLE BY`,
|
||||
«сравнимой базе», «80–110», «Схема как контракт», «каждой куки»).
|
||||
- Слепое ревью спеки Кодексом подготовлено, НЕ запущено. Промпт собран в
|
||||
`$SCRATCH/spec-review/review-prompt.md` (скретчпад сессии в /tmp — мог
|
||||
не пережить сбой). Пересборка: параметрический блок (роль: ревью
|
||||
постановки, не реализации; префикс находок SOL; вне скоупа — стиль и
|
||||
вкусовщина) + `references/prompts/review-boilerplate.md` из скилла
|
||||
`claude-subagent-playbook`. Запуск — по ветке «ревьюер»
|
||||
`references/codex-exec.md`: `codex exec`, модель `gpt-5.6-sol`, effort
|
||||
high, фоновым Bash, вердикт в `-o`-файл. Песочница проверена — жива.
|
||||
Предупреждение владельца: у Сола плохой вкус и тяга к деталям
|
||||
реализации раньше времени — рамка в промпте это прижимает, остаток
|
||||
фильтровать на триаже.
|
||||
|
||||
## Дальше (по порядку)
|
||||
|
||||
1. Проверить целостность 17 правок; финальное сквозное чтение спеки.
|
||||
2. Запустить ревью Сола, триаж находок (вкусовые — отклонять), правки.
|
||||
3. Вердикт владельца по трём открытым вопросам → статус Accepted.
|
||||
4. Коммит (`/conventional-commits`), комментарий-резолюция в #17.
|
||||
5. Разбиение через /to-tickets — уже отдельным заходом (спека и карта
|
||||
переедут в v2-репозиторий после его создания).
|
||||
|
||||
## Грабли сессии
|
||||
|
||||
- Дважды 529 Overloaded на Opus-субагентах: правки терялись ДО записи.
|
||||
Рецепт: проверять маркеры в файле, затем возобновлять агента
|
||||
SendMessage'ем; после второго падения — свежий Sonnet-агент с
|
||||
самодостаточным списком правок (сработало).
|
||||
|
||||
## Suggested skills
|
||||
|
||||
- `/claude-subagent-playbook` — продолжить конвейер ревью (этапы 4–6).
|
||||
- `/conventional-commits` — при коммите спеки.
|
||||
- `/grilling` — если владелец захочет добить три открытых вопроса.
|
||||
- Контракт трекера — `docs/agents/issue-tracker.md` (Gitea, `tea`).
|
||||
@@ -3,6 +3,13 @@
|
||||
[](./docker-compose.yml)
|
||||
[](./docs/ARCHITECTURE.md)
|
||||
|
||||
> **Стенд заморожен для новых фич.** Он остаётся стабильным учебным стендом:
|
||||
> что здесь работает, то работает и дальше — курс и лабы живут тут.
|
||||
> Развитие переехало в
|
||||
> [clickstream-data-platform](https://git.dementev.space/ddmitry/clickstream-data-platform):
|
||||
> там одно широкое событие кликстрима вместо четырёх топиков, заказы бэкенда
|
||||
> вторым источником и ClickHouse кластером.
|
||||
|
||||
Живой стек для работы с кликстримом: Kafka, ClickHouse, Airflow, Superset и мониторинг
|
||||
(Prometheus с Grafana) поднимаются в Docker одной командой. На этом стенде можно учиться
|
||||
по курсу или просто поднять его у себя и поэкспериментировать с потоковой загрузкой и
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# Боевой реализм стенда (v2): широкое событие, заказы, кластер, анонимность
|
||||
|
||||
Статус: Draft — на приёмку владельцу.
|
||||
Статус: Accepted (2026-07-30). Три помеченных отступления подтверждены
|
||||
владельцем на приёмке: порядок страховочных срезов (раздел 9), `Sign` как
|
||||
колонка без механики (раздел 1.1), `VisitID` как эталон самопроверки (1.2).
|
||||
Дата: 2026-07-30. Тикет: #17 (сборка карты #10).
|
||||
Источник истины переехал в v2:
|
||||
https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/specs/2026-07-30-stand-v2-realism.md
|
||||
Источники: резолюции #18 (модель данных), #15 (`purchase` и заказы),
|
||||
#14 (кластер), #13 (цена кластера), #16 (анонимность и склейка); исследование
|
||||
[формата кликстрима Яндекса](../research/2026-07-26-yandex-clickstream-format.md);
|
||||
@@ -64,10 +68,13 @@ JSON-поле `ecommerce`. Сессий в потоке нет — их мент
|
||||
тоже не берём, но это наш выбор, а не запрет источника: исследование
|
||||
запрещало только выдавать это поле за формат Яндекса, оставить разрешало.
|
||||
Разбор строки user agent — не урок этого стенда.
|
||||
- **Механику версий записи (`Sign`/`HitVersion`) не берём сейчас**: по #15 это
|
||||
кандидат на потом, и её дом — клиентская сторона (поток визитов Метрики
|
||||
Про). Это помеченное отступление от рекомендации исследования («`Sign` на
|
||||
хитах полезен»): колонка без механики — мёртвый вес.
|
||||
- **`Sign` берём как колонку формата, без механики** (решение владельца на
|
||||
приёмке спеки): генератор всегда пишет `Sign = 1`, исправлений записей не
|
||||
шлёт — движки и запросы не меняются. Сама механика версий
|
||||
(CollapsingMergeTree, пара `HitVersion`) — кандидат на потом, по #15.
|
||||
Честность: комментарий в DDL и абзац в документе о реализме («в бою здесь
|
||||
бывают −1/+1, считают через `sum(Sign)`»); в лекции — крючок про
|
||||
CollapsingMergeTree (частый вопрос на собеседованиях).
|
||||
- **Не берём** `ClientEventTime` (в выгрузке Метрики нет клиентской метки;
|
||||
расхождение часов — тема тумана «грязь»), `Params` (второй сырой JSON не
|
||||
нужен: этот навык уже несут заказы), `LastSearchEngineRoot`, `IsPageView`,
|
||||
@@ -78,7 +85,7 @@ JSON-поле `ecommerce`. Сессий в потоке нет — их мент
|
||||
- **Наша честная добавка** — `EventType`: у Метрики такого поля нет
|
||||
(там `isPageView` + `productEventType`), стенду таксономия нужна явно.
|
||||
|
||||
### 1.2 Состав полей (46 колонок)
|
||||
### 1.2 Состав полей (47 колонок)
|
||||
|
||||
Идентификаторы и время:
|
||||
|
||||
@@ -92,6 +99,7 @@ JSON-поле `ecommerce`. Сессий в потоке нет — их мент
|
||||
| `UTCEventTime` | DateTime | единственная метка времени, как у Метрики |
|
||||
| `ClientTimeZone` | Int16 | смещение пояса клиента в минутах |
|
||||
| `EventType` | LowCardinality(String) | `pageview` / `add_to_cart` / `purchase` |
|
||||
| `Sign` | Int8 | всегда 1: колонка формата, механика исправлений не реализована (см. 1.1) |
|
||||
|
||||
Правила резки визитов в генераторе документируются и совпадают с лабной
|
||||
логикой (30-минутный таймаут).
|
||||
@@ -155,7 +163,7 @@ Ecommerce (заполнены только у торговых событий):
|
||||
### 1.4 Схема как контракт
|
||||
|
||||
Одно машинное описание схемы события (python-модуль или YAML) — источник
|
||||
истины: из него выводятся DDL и валидация генератора, а не наоборот. 46
|
||||
истины: из него выводятся DDL и валидация генератора, а не наоборот. 47
|
||||
колонок повторяются примерно в семи местах (генератор, DDL, SELECT матвью,
|
||||
трансформации, витрины, манифест, доки) — без контракта они расходятся
|
||||
молча. Заодно это учебный артефакт: менти видит на живом примере, что такое
|
||||
@@ -196,7 +204,9 @@ Ecommerce (заполнены только у торговых событий):
|
||||
|
||||
Пропущенный день ничего не ломает, следующий слепок самовосстанавливает.
|
||||
- Разбор JSON-позиций — **один раз**, в трансформации ODS → DDS; дальше
|
||||
витрины работают с плоскими массивами. Это единственный носитель навыка
|
||||
витрины работают с плоскими массивами `dds.order`: `item_sku`
|
||||
Array(String), `item_qty` Array(UInt64), `item_price` Array(Decimal(18,2))
|
||||
— одной длины, порядок как в JSON. Это единственный носитель навыка
|
||||
«вложенный JSON в ClickHouse» на стенде.
|
||||
- Статусы держим все три: смена `created` → `paid` и есть причина «дыхания»
|
||||
выручки внутри окна; сужение до двух — резервный срез 1.
|
||||
@@ -212,7 +222,10 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
||||
## 4. Сверка `purchase` против заказов
|
||||
|
||||
Ключ: клиентский `purchaseID` = `order_id` бэкенда (магазин знает номер
|
||||
заказа на `/confirmation`). Расхождения — перечислимый список,
|
||||
заказа на `/confirmation`). У события `purchase` массив `purchaseID` несёт
|
||||
ровно один элемент (одно подтверждение — один заказ), сверка соединяет по
|
||||
`purchaseID[1]`; правило зафиксировать комментарием в SQL сверки.
|
||||
Расхождения — перечислимый список,
|
||||
детерминированный от seed, не хаос:
|
||||
|
||||
| | Расхождение | Механика в генераторе | Ориентир доли |
|
||||
@@ -235,7 +248,10 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
||||
механически дублирует B.
|
||||
|
||||
Правило стенда: **поведение и атрибуцию считаем по трекеру, деньги — по
|
||||
бэкенду**. Оно выучивается на конфликте: суммы не сойдутся, менти сам
|
||||
бэкенду**. Единственное разрешённое исключение — клиентская оценка выручки
|
||||
под именем `declared_*` там, где атрибуция без трекера невозможна (UTM);
|
||||
слово `declared` в имени — сигнал «это заявка клиента, не деньги
|
||||
отчётности». Оно выучивается на конфликте: суммы не сойдутся, менти сам
|
||||
раскопает почему (Float64 против Decimal, промокод, доставка, отмены).
|
||||
|
||||
## 5. Анонимность и склейка идентичностей
|
||||
@@ -286,7 +302,10 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate
|
||||
`cityHash64(сырой строки)`. Урок: «какая нода читала топик — меняется между
|
||||
прогонами, куда легли данные — нет».
|
||||
- **Приём строгий**: `input_format_skip_unknown_fields = 0`, обязательные
|
||||
поля — без значений по умолчанию. Несовпадение имени поля — громкая
|
||||
поля — без значений по умолчанию. Контракт присутствия: генератор выдаёт
|
||||
**все 47 полей в каждом событии**; «пусто» — пустой массив, пустая строка
|
||||
или 0, а не отсутствие ключа в JSON. Так строгий приём уживается с
|
||||
полями, пустыми по смыслу (ecommerce у `pageview`, UTM у прямого захода). Несовпадение имени поля — громкая
|
||||
ошибка в `*_errors`, а не молчаливые нули: имена CamelCase регистрозависимы,
|
||||
опечатка иначе не падает.
|
||||
- **Политика соединений**: по ключу ко-локации — обычное соединение с
|
||||
@@ -370,8 +389,11 @@ Kafka переобработка возможна только из эталон
|
||||
в порядке приоритета — классы пересекаются, побеждает более ранний).
|
||||
`match` — большинство строк; `amount_delta` — только необъяснённый остаток
|
||||
после приведения к сравнимой базе (округления Float64, ~1–2% заказов).
|
||||
Строка «`purchase` без заказа» внутри живого окна — это опоздание, ждущее
|
||||
слепка, а не отдельный класс расхождения.
|
||||
Строка «`purchase` без заказа» внутри живого окна — опоздание, ждущее
|
||||
слепка, а не расхождение: она получает служебный класс `awaiting_order`
|
||||
(шестое значение `mismatch_class`, вне приоритетов расхождений). После
|
||||
закрытия окна K таких строк не остаётся — сироты исключены построением
|
||||
(раздел 4).
|
||||
- **`v_utm_effectiveness`** — остаётся клиентской (атрибуция по трекеру);
|
||||
счётчики `purchases`/`add_to_carts` оживают из таксономии, добавляется
|
||||
`declared_revenue` по UTM.
|
||||
@@ -398,7 +420,7 @@ Kafka переобработка возможна только из эталон
|
||||
Манифест расширяется контрольными числами:
|
||||
|
||||
- заказная сторона: заказы и выручка по дням; манифест хранит точные
|
||||
счётчики по каждому классу расхождений (отмены, потери, дубли) —
|
||||
счётчики по каждому классу расхождений (отмены, потери, дубли, дельты сумм) —
|
||||
самопроверка лабы сверки;
|
||||
- идентичность: uniq кук, uniq известных пользователей, число двухкуковых
|
||||
покупателей — лаба склейки получает самопроверку.
|
||||
@@ -469,7 +491,8 @@ v2 стартует пустым, поэтому объём ниже — это
|
||||
9. Мониторинг и runbook «keeper упал / DDL повис в очереди».
|
||||
|
||||
Критерий приёмки этапа — честный: `make up` работает и проходят
|
||||
smoke-проверки, а не «дашборд зелёный». Документация правится в PR этапа
|
||||
smoke-проверки, а не «дашборд зелёный». Это минимальная планка; свои
|
||||
наблюдаемые критерии каждый этап получает при разбиении в /to-tickets. Документация правится в PR этапа
|
||||
(правило AGENTS.md).
|
||||
|
||||
## 10. Границы: чего не делаем
|
||||
@@ -486,8 +509,8 @@ smoke-проверки, а не «дашборд зелёный». Докуме
|
||||
- Реплики (2×2), HAProxy, репликационная эксплуатация — в лекцию, не в стенд.
|
||||
- Полный словарь торговых событий Метрики (detail, remove, impressions),
|
||||
пять уровней категорий, блоки `purchasedProduct*`/`impressions*`.
|
||||
- `Sign`/CollapsingMergeTree — кандидат на потом (дом — поток визитов
|
||||
Метрики Про).
|
||||
- Механика `Sign`/CollapsingMergeTree — кандидат на потом (дом — поток
|
||||
визитов Метрики Про); сама колонка `Sign` уже в схеме, статикой (см. 1.1).
|
||||
- Событийный лог заказов, CDC/Debezium, HTTP-сервис заказов, шапка+строки,
|
||||
отдельный поток возвратов — отклонены в #15/#18.
|
||||
- Файловые дропы как источник — зона следующего стенда (Lakehouse), у нас
|
||||
@@ -532,7 +555,8 @@ smoke-проверки, а не «дашборд зелёный». Докуме
|
||||
Logs API — это скачанный TSV»); trade-off «инкремент экономнее, слепок
|
||||
надёжнее» + сноска про compacted topic; identity stitching перестаёт быть
|
||||
чистой теорией; «прямое чтение прод-базы — анти-приём, в бою — реплика или
|
||||
выгрузка»; полный словарь торговых событий — теория.
|
||||
выгрузка»; колонка `Sign` без механики — честное ограничение стенда
|
||||
(в бою −1/+1 и `sum(Sign)`); полный словарь торговых событий — теория.
|
||||
|
||||
В v1 при заморозке — указатель на v2 в README (форму решить при рождении
|
||||
v2, этап 0).
|
||||
@@ -549,6 +573,11 @@ v2, этап 0).
|
||||
- сцена «разные consumer groups → дубли»;
|
||||
- словарь регионов из CSV той же машинерией, что каталог товаров (оживляет
|
||||
`RegionCityID`);
|
||||
- лаба сессий: менти сначала собирает сессии сам, и только после — рассказ,
|
||||
что с октября 2025 Метрика отдаёт `VisitID` прямо в хитах; частично
|
||||
синтетическая постановка — осознанный приём;
|
||||
- лекция «`Sign` и CollapsingMergeTree»: почему на стенде `sum(Sign)` =
|
||||
`count()`, а в бою — нет; частый вопрос на собеседованиях;
|
||||
- лекция про идентичность «как в бою»: `setUserID` и first-party id,
|
||||
детерминированная против вероятностной склейки, identity graph,
|
||||
кросс-девайс, CDP — с рамкой «мы склеили через транзакции, потому что трекер
|
||||
|
||||
Reference in New Issue
Block a user