diff --git a/.scratch/backup/20260726-resolution-15.md b/.scratch/backup/20260726-resolution-15.md new file mode 100644 index 0000000..24ff1b1 --- /dev/null +++ b/.scratch/backup/20260726-resolution-15.md @@ -0,0 +1,99 @@ +## Ответ + +Сумма заказа живёт **на обеих сторонах**, и стороны законно расходятся: +клиентское событие несёт объявленную сумму (то, что сайт продиктовал трекеру +через dataLayer — до пересчёта на бэкенде), заказ бэкенда — итоговую правду +со статусами. Правило стенда: **поведение и атрибуцию считаем по трекеру, +деньги — по бэкенду**. Правило выучивается на конфликте: суммы у менти +не сойдутся, и он сам раскопает почему. + +### Клиентская сторона (широкое событие, рамка #18) + +- Таксономия торговых событий: `pageview` + `add_to_cart` + `purchase`. + Полный словарь Метрики (`detail`, `remove`, `impressions`) не берём: + механика та же, объём массивов большой, новых идей нет — остаётся теорией + в документе о реализме. +- `purchase` несёт `purchaseID/Revenue/Currency/Coupon` + массивы `product*` + состава заказа + сырую строку `ecommerce` (по образцу выгрузки Метрики, + фактура — #27). +- `add_to_cart` — массивы `product*` с одним товаром. Оживают мёртвые + счётчики `purchases`/`add_to_carts` в `v_utm_effectiveness` без правки витрины. + +### Сторона бэкенда (второй источник, рамка #18) + +- **Формат выгрузки: полный ежедневный слепок заказов в пределах окна + изменяемости K дней.** Запись = снапшот заказа: `order_id`, `user_id`, + времена, `status` (`created`/`paid`/`cancelled`), `updated_at`, итоговые + суммы (товары / скидка / доставка / итого) + **позиции вложенным JSON** + (`items: [{sku, qty, price}]`) — «родной» экспорт бэкенда, а не диалект + Метрики. +- Обоснование слепка (а не инкремента): очень частый боевой формат; + идемпотентный приём и самовосстановление (пропущенный день ничего не + ломает); дедуп до последней версии становится обязательным с первого дня. + Окно K делает объём защитимым (за окном заказ неизменяем — возить незачем) + и даёт границу пересчёта: выручка дня D «дышит» K дней, потом замерзает. +- Приём: дедуп через `ReplacingMergeTree`/`argMax`; разбор JSON-позиций — + **один раз** в трансформации ODS → DDS, дальше витрины работают с плоскими + массивами. Это единственный носитель навыка «вложенный JSON в ClickHouse» + в стенде. +- В `docs/generator-realism.md` — честный абзац trade-off «инкремент экономнее, + слепок надёжнее» и сноска про compacted topic как родной Kafka-паттерн + для состояния сущности. + +### Сверка + +- **Ключ: клиентский `purchaseID` = `order_id` бэкенда** (магазин знает номер + заказа на `/confirmation` — как `actionField.id` у Метрики). +- Конструируемые расхождения — перечислимый список из четырёх причин, + детерминированных от seed: + - **A. Отмена** — `purchase` есть, заказ дошёл до `status='cancelled'` + (бесплатно из статусов); + - **B. Потерянное событие** — заказ есть, `purchase` не доехал + (вероятность в генераторе); + - **C. Дельта суммы** — систематическая, по построению: клиент объявляет + сумму товаров до промокода и доставки, бэкенд — итог; менти может вывести + формулу связи; + - **D. Дубль события** — повторный `purchase` от обновления + `/confirmation` (вероятность). +- Пятый эффект бесплатно даёт формат доставки: **опоздание** (заказ впервые + появляется в слепке D+1/D+2) — «вчера не сходилось, сегодня сошлось». +- Не берём: сироту-фрод (дублирует B механически) и расхождение часов + клиент/сервер — это жители тумана «Грязь в данных», придут своим тикетом. + +### Слои и витрины + +- DDS: одна новая сущность `dds.order` (последняя версия заказа, позиции + разобраны в массивы). `purchase` отдельной сущности не получает — это + строка широкого события. +- DM, три роли: **выручка** (`v_revenue_daily`, по категориям через `dictGet` + каталога) — строится только от заказов; **сверка** + (`v_purchase_vs_orders`) — FULL OUTER JOIN по ключу с классификатором + расхождения; **атрибуция** (`v_utm_effectiveness`) — остаётся клиентской, + плюс объявленная выручка по UTM. Точный состав колонок — в спеку (#17). + +### Эталонный мир + +Пересборка артефакта неизбежна и уже оплачена решением #18; этот тикет +нагружает её смыслом: манифест расширяется контрольными числами заказной +стороны — заказы и выручка по дням, и ровно N потерянных / M дублей / +K отмен для самопроверки лабы сверки. Политика версионирования артефакта +здесь не решается (пункт тумана карты). + +### Ограничения исполнения и страховочные срезы + +Аудитория — джун после базовой программы: расхождения — перечислимый список, +не хаос. Если при сборке спеки (#17) суммарный объём испугает, резать в +порядке: (1) статусы сузить до `created`/`cancelled` — урок дедупа держится +на самом слепке; (2) расхождения вводить поэтапно — сначала A+C, потом B+D; +(3) окно K сделать константой мира, а не параметром. + +### Отклонено по дороге + +Событийный лог заказов (сборка автомата — дальше от типовой работы DE), +CDC-формат (имитация Debezium без Debezium), шапка+строки (воскрешает склейку +по ключам), отдельный поток возвратов, полный словарь торговых событий +Метрики, механика Sign/CollapsingMergeTree (остаётся кандидатом на потом, +её дом — клиентская сторона, поток визитов Метрики Про, не заказы). + +Фактура: `docs/research/2026-07-26-yandex-clickstream-format.md` (#27), +резолюция «Модель данных» (#18). diff --git a/.scratch/backup/20260726-tracker-snapshot.md b/.scratch/backup/20260726-tracker-snapshot.md new file mode 100644 index 0000000..f725e4d --- /dev/null +++ b/.scratch/backup/20260726-tracker-snapshot.md @@ -0,0 +1,213 @@ +# Слепок трекера на 2026-07-26 (аккаунт GitHub заблокирован) + +Страховочная копия wayfinder-карты и ключевых резолюций из GitHub Issues. +Снята из контекста сессии в момент блокировки аккаунта `dementev-dev` +(сразу после закрытия #15). Если аккаунт восстановят — файл можно удалить; +если нет — это источник для восстановления трекера на новом месте. + +**Хвост, не доехавший до GitHub:** строка про #15 в Decisions so far карты #10 +(текст — в разделе «Карта», помечен как НЕ ОПУБЛИКОВАНО). + +## Состояние issues (все, на момент блокировки) + +| # | Состояние | Метки | Название | +|---|---|---|---| +| 27 | CLOSED | wayfinder:research | Что отдаёт Яндекс как кликстрим: форма события и выгрузка | +| 24 | CLOSED | needs-triage | Вычистить прозаические тесты из test_world_dags_contract.py | +| 23 | CLOSED | ready-for-agent | Сверка цифр курса на живом стенде и финальная проверка | +| 22 | CLOSED | ready-for-agent | Лабы 07 (next-day) и 08 (continue) + метадокументы курса | +| 21 | CLOSED | ready-for-agent | Каркас курса и переобвязка уроков 0–6 под путь import | +| 20 | OPEN | wayfinder:task | Обновить мажорную версию airflow до версии 3 | +| 18 | CLOSED | wayfinder:grilling | Модель данных: широкое событие и второй источник | +| 17 | OPEN | wayfinder:task | Собрать спеку боевого реализма | +| 16 | OPEN | wayfinder:grilling | Анонимы и identity stitching: нужно ли и сколько | +| 15 | CLOSED (2026-07-26) | wayfinder:grilling | Purchase с выручкой: форма события и место в стенде | +| 14 | OPEN | wayfinder:grilling | Кластер: где живёт опыт менти и какая топология | +| 13 | CLOSED | wayfinder:research | Цена кластера для пайплайна | +| 12 | CLOSED | wayfinder:research | Ресурсный бюджет стенда на 16 ГБ | +| 11 | CLOSED | wayfinder:task | Реализм генератора: границы применимости стенда | +| 10 | OPEN | wayfinder:map | Карта: боевой реализм стенда | +| 9 | CLOSED | — | Редизайн пути менти: мир из артефакта и две ветки роста | +| 8 | CLOSED | wontfix | Техдолг: инкрементальный ETL вместо full_refresh на каждый день | +| 7 | CLOSED | ready-for-agent | Редизайн лаб курса под три режима менти | +| 6 | CLOSED | ready-for-agent | Один учебный профиль: daily-wave — учебный, ci — служебный | +| 5 | CLOSED | ready-for-agent | Инкрементальные счётчики manifest: next-day без перечитки всей Kafka | +| 4 | CLOSED | ready-for-agent | Поверхность DAG'ов: generator_control → world_init, беспараметрный world_next_day | +| 3 | CLOSED | ready-for-agent | Эталонный мир: 3-дневный артефакт в git и import по умолчанию | +| 2 | CLOSED | wontfix | Airflow Grid: всплывающая JS-ошибка при авто-обновлении | +| 1 | CLOSED | — | Быстрый разлогин в Airflow и Superset | + +Блокировки #15 (нативные dependencies): blocked_by #11, #18 — обе закрыты. + +## Карта #10 «Карта: боевой реализм стенда» (тело) + +### Destination + +Принятая спека в `docs/specs/` «Боевой реализм стенда»: где менти получает +кластерный опыт ClickHouse и в какой топологии; какие доработки реализма +данных генератора делаем; явные границы. Спека готова к разбиению через +`/to-tickets`. + +### Notes + +- Расчёт на ноутбук менти 16 ГБ RAM (у кого 8 ГБ — VDS за счёт менти). +- Исполнение доработок — после фичи «Редизайн пути менти» (#9); карта + решений может идти параллельно с ней. +- Изменения генератора тянут пересборку эталонного мира + (`data/startup_history/reference-world.json.xz`) и «поплывшие» числа + в лабах — учитывать в каждом решении. +- Скиллы: `/grilling` и `/domain-modeling` для тикетов-решений, + `/research` для тикетов-исследований. +- Рабочая гипотеза владельца: кластер здесь, облегчённо (2 шарда без + реплик), цель — Distributed и ON CLUSTER на живом потоке; «голый» + clickhouse-learning-cluster этого не даёт. +- Порядок: решение по модели данных предшествует кластерному — иначе + межшардовые джойны четырёх топиков придётся оплатить дважды. + +### Decisions so far + +- [Реализм генератора: границы применимости стенда](#11) — документ + `docs/generator-realism.md` (коммит 068d96f): честно как в бою — + схема/воронка/сессии/волна/обвязка; упрощено — четыре топика, только + pageview, клоны пользователей, нет «грязи», масштаб. +- [Ресурсный бюджет стенда на 16 ГБ](#12) — полный стенд в покое ≈3,4 ГБ; + 2×1 добавляет ≈0,6–0,8 ГБ (влезает свободно), 2×2 — ≈1,7–1,9 ГБ (влезает, + но впритык к дефолтному бюджету WSL2 ~8 ГБ); координатором брать + clickhouse-keeper, не ZooKeeper. +- [Цена кластера для пайплайна](#13) — объём средне-крупный (~15–20 файлов, + тяжёлое — SQL); главная боль — JOIN поверх Distributed и TRUNCATE в + трансформациях (риск для контрольных сумм), приём из Kafka требует одного + консьюмера + Distributed-цели; рекомендация исследования — опциональный + `make up-cluster`, не дефолт. +- [Что отдаёт Яндекс как кликстрим](#27) — плоское широкое ядро (~140 + колонок) плюс параллельные массивы для многозначного, вложенного JSON + нет (Яндекс ближе к Snowplow, чем к Segment); доставка батчем (Logs API, + TSV, лог доформировывается ~3 дня), поток только в «Метрике Про» через + Data Transfer с задержкой до 15 минут — Kafka у Яндекса нет, наша Kafka + учебная замена; детали в `docs/research/2026-07-26-yandex-clickstream-format.md` + (ветка `research/yandex-clickstream-format`, коммит `e6e34f2`). +- [Модель данных: широкое событие и второй источник](#18) — переходим на + одно широкое событие по образцу Яндекс Метрики (плоское ядро + + параллельные массивы + сырое поле `ecommerce`, таксономия event_type); + интеграционная ценность — заказы бэкенда той же Kafka, но пачками с + опозданиями и отменами (одна труба, два режима), каталог товаров — + словарь ClickHouse из файла; прямое чтение прод-Postgres и файловые + источники отклонены (файлы — зона Lakehouse-стенда); Kafka — учебная + замена батчевого Logs API, фиксируем в docs/generator-realism.md. +- **[НЕ ОПУБЛИКОВАНО — добавить при восстановлении доступа]** + [Purchase с выручкой: форма события и место в стенде](#15) — деньги на + обеих сторонах (клиент объявляет через dataLayer, бэкенд — итоговая + правда; атрибуция по трекеру, деньги по бэкенду); клиент: + `pageview`+`add_to_cart`+`purchase`; заказы — ежедневный полный слепок + окна изменяемости K со статусами и JSON-позициями, приём через + ReplacingMergeTree; сверка по `purchaseID`=`order_id` с четырьмя + конструируемыми расхождениями (отмена, потеря, дельта суммы, дубль); + выручка в DM — только от заказов. + +### Not yet specified + +- «Грязь» в данных: боты, дубли событий, опоздавшие мобильные батчи, + расхождение часов клиент/коллектор — вернуться после решений по + purchase и identity. +- Политика версионирования эталонного артефакта при изменениях + генератора (когда пересобирать, как жить лабам со сменой чисел). +- Как новые возможности лягут в лабы курса (после редизайна лаб, #7). + +### Out of scope + +- ~~Формат доставки событий: закрыть теорией~~ — решение отменено + 2026-07-22: исследование цены кластера показало, что четыре топика + несовместимы с шардированием без GLOBAL JOIN; вопрос вернулся в рамку + тикетом «Модель данных: широкое событие и второй источник». +- Редизайн лаб курса — отдельный issue #7. +- Инкрементальный ETL — отдельный issue #8; багфиксы — #1, #2. + +### Комментарии карты + +1. Решение 2026-07-23: дальнейшая работа карты пойдёт в новом + v2-репозитории. v1 замораживается как стабильный стенд для менти + (добить путь менти, баги #1/#2, лекции). v2 стартует пустым + репозиторием с осознанным первым коммитом (переносим только нужное; + генератор переписывается, переиспользуются идеи). Имя нового + репозитория выберем из решения о нише; карта и открытые тикеты + переедут туда после создания. +2. Рабочий кандидат имени v2-репозитория: **clickstream-data-platform** + (согласован 2026-07-23). Мотив: стенд перерастает классическое DWH — + потоковый приём, оркестрация, кластер, витрины; «data platform» + описывает целое. Финальное закрепление — при решении тикета о нише. + +## Тикет #15 «Purchase с выручкой: форма события и место в стенде» + +### Тело + +Part of #10 + +**Question:** Решить форму события покупки с суммой заказа: схема и носитель +(клиентское событие в широкой модели, источник заказов бэкенда или обе +стороны со сверкой — зависит от решения «Модель данных» #18), как ложится +в DDL/DM и дашборд, что делает с эталонным миром (пересборка артефакта +и чисел лаб). + +Комментарий владельца: решение #18 добавило вторую сущность — заказ бэкенда +(Kafka, пачками, с опозданиями и отменами). При решении формы purchase решить +и форму события заказа, и сюжет сверки «клиентский purchase против +бэкенд-заказа». Фактура по ecommerce Яндекса — в +docs/research/2026-07-26-yandex-clickstream-format.md. + +### Резолюция (опубликована 2026-07-26, тикет закрыт) + +Опубликованный текст: комментарий +`issues/15#issuecomment-5084445090`. Полная копия — в +`.scratch/backup/20260726-resolution-15.md` (соседний файл). + +## Тикет #18 «Модель данных: широкое событие и второй источник» (резолюция) + +**Да, переходим на широкое событие.** Четыре топика-осколка уходят; модель +одна, целевая (двух моделей «для дефолта и для кластера» не держим). Мотив — +реализм: менти должен узнавать в стенде тот кликстрим, с которым столкнётся +на работе. Кластер — побочный довод, не причина. + +**Форма события — по образцу Яндекс Метрики** (фактура — #27, +`docs/research/2026-07-26-yandex-clickstream-format.md`): плоское широкое +ядро плюс параллельные массивы для многозначного (товары, цели, свои +параметры) плюс одно сырое поле-строка `ecommerce`. Вложенных объектов в +стиле Segment/Amplitude не делаем. Таксономия `event_type` вместо «только +pageview». Точный состав полей — в спеку (#17), опора — таблица 53 полей +из исследования. + +**Интеграционная учебная ценность** (взамен склейки осколков): + +- **Второй источник — заказы бэкенда.** Вторая версия правды о покупке; + учебный сюжет — сверка клиентского purchase против заказа, расхождения, + отмены. Форма события заказа и сверка — тикет #15. +- **Транспорт заказов — та же Kafka, но пачками**: бэкенд выгружает заказы + раз в модельный день, с опозданиями и отменами. Одна труба, два режима — + как в бою, где батчи льют в брокер из удобства. Открывает темы, которых + у менти нет после курсовой airflow-greenplum: согласование потока и + батча, поздние данные, кросс-источниковые проверки, сенсоры/Datasets + (DAG слоя DDS ждёт дневной батч). +- **Каталог товаров — словарь ClickHouse из файла** (CSV в репозитории; + тот же файл использует генератор — расхождений нет по построению). + Даёт `dictGet` и политику обновления словаря. + +**Ландшафт итогом:** Kafka — единственная труба (кликстрим потоком, заказы +пачками); Postgres остаётся только служебной базой Airflow; смешанность +ландшафта выражена режимами и частотами, а не второй трубой. + +**Отклонено по дороге:** прямое чтение прод-базы магазина из ClickHouse +(анти-приём: нагрузка на прод и связность; в документе о реализме +зафиксировать как явный учебный пункт «в бою — реплика или выгрузка»); +файловые дропы как источник (зона Lakehouse-стенда; остаются теорией — +«настоящий Logs API — это скачанный TSV»); HTTP-сервис заказов и CDC +(Debezium) — цена выше учебной отдачи; каталог отдельным топиком Kafka — +выдумка, в бою так не делают. + +**Честность к Яндексу:** у Метрики кликстрим — батч (Logs API), потока в +общем доступе нет; наша Kafka — учебная замена, так и называем в +`docs/generator-realism.md`. + +**Что это открывает дальше:** #15 (purchase и форма заказа) и #14 (кластер: +с широким событием склейка осколков исчезает, ключ шардирования решается +там) разблокированы; новые приёмы стенда — ARRAY JOIN, словари, +сенсоры/Datasets, кросс-источниковый DQ; кандидат — версии записи через +Sign (механика CollapsingMergeTree из потока Метрики Про). diff --git a/.scratch/handoffs/20260726-2201-gitea-tracker-and-map-continuation.md b/.scratch/handoffs/20260726-2201-gitea-tracker-and-map-continuation.md new file mode 100644 index 0000000..fd7bc2d --- /dev/null +++ b/.scratch/handoffs/20260726-2201-gitea-tracker-and-map-continuation.md @@ -0,0 +1,92 @@ +# Handoff: трекер переехал на Gitea, карта живёт, GitHub в блоке + +Дата: 2026-07-26. Пишу по итогам сессии, в которой закрыли тикет #15 +карты «Боевой реализм стенда» и экстренно переносили репозиторий с +трекером на собственный Gitea после блокировки GitHub-аккаунта. + +## Что случилось и что сделано + +1. **Тикет #15 «Purchase с выручкой» решён и закрыт** (шесть решений, + полная резолюция — в тикете). Гист — в Decisions so far карты #10. +2. **GitHub-аккаунт dementev-dev заблокирован** (ToS violation, причина + не названа) — сразу после закрытия #15. Апелляция готовится, см. + «Хвосты» ниже. +3. **Репозиторий и весь трекер перенесены на Gitea:** + `https://git.dementev.space/ddmitry/clickstream-ch-kafka-superset-demo` + (remote `gitea`, ветка `main` запушена). Все 27 номеров issues + воссозданы 1:1 (на местах PR #19/#25/#26 — закрытые заглушки, чтобы + ссылки `#NN` в текстах не разъехались). +4. **Из транскриптов прошлых сессий субагентами восстановлены дословно:** + тела #18, #20, #27; резолюции #12, #13, #15, #18, #27; граф блокировок. + Тела #14, #16, #17 — реконструкции по памяти (помечены в самих тикетах). + Резолюция #11 отдельным текстом не нашлась — её содержание есть в + `docs/generator-realism.md`. + +## Состояние карты (#10 на Gitea) + +- Закрыты: #11, #12, #13, #15, #18, #27 — гисты в Decisions so far. +- **Фронтир:** #14 «Кластер: где живёт опыт менти и какая топология» + (все блокеры закрыты; для него готова фактура — резолюции #12 и #13) + и #16 «Анонимы и identity stitching» (разблокирован закрытием #15). +- #17 «Собрать спеку боевого реализма» ждёт #14 и #16. +- Блокировки записаны строками `Blocked by:` в телах тикетов — нативные + dependencies в этой инсталляции Gitea выключены (API отдаёт 404). + +## Как работать с Gitea (нюансы, стоившие времени) + +- Хост домашний — **в обход прокси**: `curl --noproxy '*'`, + `no_proxy=git.dementev.space git push gitea ...`. +- Токен — в `~/.git-credentials` (строка ddmitry). Скоупы: + `write:repository`, `write:issue`, `write:package`. Создание репо через + API недоступно (нужен `write:user`) — создавать в UI. +- Токен не подставлять в командную строку (блокируется классификатором) — + читать из файла в python/через конфиг curl. +- `docs/agents/issue-tracker.md` всё ещё описывает GitHub/gh — пока + GitHub в блоке, рабочий трекер де-факто Gitea (операции — через + `curl`/python по API, образцы: `upload.py` и `apply_updates.py` в + scratchpad прошлой сессии; проще написать заново по образцу из этого + handoff). + +## Хвосты (в порядке срочности) + +1. **Апелляция в GitHub.** SMS на номера РФ не доходят (шлюз GitHub не + шлёт в РФ), попытки смены номера упёрлись в rate limit. План: спустя + ~сутки — казахстанский номер (в выпадашке стран выбрать Kazakhstan); + параллельно тикет через «I can't sign in» на support.github.com + (без SMS, только почтовый код) с почты аккаунта. Черновик письма — + в прошлой сессии; суть: спросить причину, описать легитимное + использование (учебные репо, менти, Pages), упомянуть всплеск + API-активности через gh CLI как возможный триггер. Один тред, не + плодить дубли. Если ответят «multiple free accounts» — стандартный + выход: конвертировать менторскую учётку в организацию. +2. **При восстановлении GitHub:** донести в карту #10 строку про #15 + (на Gitea она уже есть, на GitHub — нет), затем решить, какой трекер + основной, и синхронизировать/заморозить второй. +3. **Решение о доме трекера и v2.** Подозрение владельца: собственный + git-сервер для менторской работы — не такая плохая идея (независимость + от блокировок). Против: доступность для менти (публичные ссылки из + роадмапа, GitHub Pages де-факто витрина), привычность GitHub в резюме + менти. Это решение стоит принять осознанно — возможно, грилингом, + и оно связано с запланированным v2-репозиторием (комментарии к карте + #10: v2 стартует пустым, кандидат имени clickstream-data-platform). +4. **Резервные копии:** `.scratch/backup/20260726-*.md` — слепок трекера + и резолюция #15 на момент блокировки. После стабилизации (GitHub или + окончательный переезд) — можно удалить. Этот handoff и backup пока + не закоммичены. + +## Suggested skills + +- `/wayfinder #10 #14` или `/wayfinder #10 #16` — продолжать карту + (следующий тикет по выбору владельца; для #14 фактура уже собрана + в резолюциях #12/#13). +- `/grilling` + `/domain-modeling` — внутри тикетов-решений; владелец + просил на развилках сначала веер гипотез (дивергенцию), потом + конвергенцию с рекомендацией. +- `/conventional-commits` — при коммите backup/handoff. + +## Ссылки + +- Трекер: https://git.dementev.space/ddmitry/clickstream-ch-kafka-superset-demo/issues +- Карта: issue #10 там же; резолюция #15 — комментарий в issue #15. +- Исследование формата Яндекса: `docs/research/2026-07-26-yandex-clickstream-format.md`. +- Границы реализма генератора: `docs/generator-realism.md`.