18 Commits
Author SHA1 Message Date
ddadminandClaude Opus 5 35dc933d29 feat(airflow): пульт мира — работники, выключатель, контейнер
Зачем: модельный день прогонялся только руками, и владельцу нечем было
проверять процессы стенда вживую. Пульт нужен раньше этапа 3 и независимо
от него: он обкатывает то, что приёму заказов понадобится готовым — вызов
генератора из задачи Airflow.

Что: `dags/world_control.py` — три дага по ADR 0009. Работники
`world_next_day` (день пачкой, «сколько дней» параметром) и `world_live_day`
(день в темпе) живут без расписания и без паузы; выключатель `world_live`
создаётся на паузе, тикает раз в 25 минут и дёргает работника живого дня
с ожиданием конца. Генератор зовётся `DockerOperator` в каноническом
контейнере: сокет докера отдан планировщику, потому что при LocalExecutor
задачи исполняет он, а GID группы `docker` уехал в `.env` как локальная
настройка. Позицию на оси ведёт переменная `world_position` — её ставит
сыгравший день работник и только по успеху. Факты стенда — образ, сеть,
брокер, топик, размер стартового мира — даги получают окружением от compose;
внутри compose они названы по разу якорями, иначе разошлись бы с разовой
службой генератора. README получил раздел про пульт с названной вслух платой
за сокет.

Проверка: `make lint`, `make config-test`, `make smoke` (20/0), `make
check-services` (7/0), `make check-clickhouse` (9/9) — зелёные. На чистом
стенде: два прогона `world_next_day` подряд двигают позицию на два дня,
«дней = 3» — на три, все пять дней доехали в ODS; обрыв контейнера позицию
не двигает, повторный запуск играет тот же день с тем же счётом событий.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:09:14 +03:00
ddadminandClaude Opus 5 61bc156d6a docs(adr): решён пульт мира — работники, выключатель, контейнер
Зачем: модельный день прогоняется только руками, и владельцу нечем
проверять процессы стенда вживую. Этап 3 без дага не существует вовсе
(ADR 0008), поэтому форму пульта и способ вызова генератора надо решить
раньше кода.

Что: ADR 0009 — работники без расписания (`world_next_day` пачкой,
`world_live_day` в темпе) и выключатель `world_live` без своей работы,
ждущий дочерний прогон триггером, а не сенсором; генератор зовётся
`DockerOperator` в каноническом контейнере, потому что в образе Airflow ему
не жить (Python 3.14 против 3.13); позиция ставится как «последний сыгранный
+ 1» — правило вместо сторожа, и его мягкий худший исход назван. Плата за
сокет докера снята замером: доступ открывается добавлением GID в контейнер,
правка прав на хосте не нужна, а сам GID — локальная настройка в `.env`.
Пакетная автоматика отложена, не отвергнута. Расхождения внесены в спеку
генератора: позицию ставит сыгравший, у живого дня появился выключатель.
В словарь добавлен «пульт мира».

Проверка: `make config-test` зелёный; API Airflow 3.3 (пауза и ручной
запуск, ожидание триггера по идентификатору прогона, привязка сенсора к
логической дате, `DockerOperator`) сверено через MCP Context7; версия
Python в образе и права сокета — замером.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 12:35:30 +03:00
ddadminandClaude Opus 5 1fb219f836 docs(adr): решён приём заказов — пакетный забор слепка
- Зачем:
  - развилка этапа 3 стояла нерешённой прямо в разделе 7 мастер-спеки: нужен
    ли слепку слой сырья и как заказы попадают из топика в хранилище (#70).
- Что:
  - заведён ADR 0008 — байтовый чтец без матвью, слой сырья у заказов
    остаётся, в ods.order_snapshot пишет шаг Airflow заменой партиции; топик
    orders в одну партицию, чтец на clickhouse-01 без ON CLUSTER.
  - мастер-спека приведена в соответствие, разделы 6, 7, 9, 11, 12: сравнение
    двух приёмов переписано на «поток против слепка», сенсор дневного батча
    снят, первый даг переехал с этапа 5 на этап 3.
  - в CONTEXT.md заведены «слепок», «окно изменяемости», «пакетный забор».
- Проверка:
  - решение сверено по документации ClickHouse через MCP Context7 12 августа
    2026 года; что осталось замерить на стенде — списком в конце ADR 0008.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 21:57:37 +03:00
ddadmin 7c9eeedc40 feat(stand): make up наполняет стенд стартовым миром, опись сторожит его
Зачем
Стенд поднимался пустым, и всякая приёмка следующих этапов начиналась с
ручной заливки данных. Теперь `make up` сам приводит стенд к одному и тому
же состоянию, а в git лежит то, чем это состояние проверяется.

Что
- Опись мира `data/world-inventory.json`: паспорт (зерно, версия
  генератора, хеш каталога) и по строке на каждый из восьми дней — дата,
  число событий, хеш байтов. Собирается `make inventory`, свежесть сторожит
  `test_inventory.py` — тем же способом, что свежесть описания выгрузки.
- Разовая служба `world-init` вышла из-под профиля и играет в топик восемь
  дней при каждом подъёме; зависимый у неё — `airflow-init`, иначе `--wait`
  считает успешно отработавшую службу упавшей.
- `scripts/wait-for-world.sh` — вторая половина `make up`: приём
  асинхронный, поэтому ждать надо доезда до `ods.event`, а не завершения
  заливки. Ограниченный цикл опроса, не пауза наугад.
- Девятая проверка `make check-clickhouse`: подневный счёт событий против
  описи, рамка по датам стартового мира, счёт через `FINAL`. При
  расхождении называет, где искать, — в событиях или в браке.
- Порог «день ≤ 30 с» снят из спеки генератора в обоих местах: замер дал
  1,7 с, порог был выше факта в восемнадцать раз. На его месте — замеры с
  датой. Раздел 9 спеки закрыт: открытых вопросов не осталось.
- Слова: «манифест» стал описью мира, «зерновой мир» — стартовым миром
  (решение владельца). Оба заведены в словарь CONTEXT.md.

Проверка
`make clean && make up` с нуля — 2 м 50 с, доехало ровно 401 185 событий.
`make check-clickhouse` зелёный (8 с), `make smoke` зелёный (9 с),
`make test` — 407 тестов за 71 с, `make lint`, `make typecheck`,
`make config-test` зелёные.

Что проверка умеет краснеть, снято двумя поломками: снос партиции
2026-06-03 дал диагноз «не доехали до ODS», негодная строка в сырье —
«сломан разбор». Строки опыта убраны, день переигран, счёт вернулся.
Тест свежести проверен молчаливой правкой цены в каталоге: покраснел.

Ссылка: #42
2026-08-07 18:37:01 +03:00
ddadmin fbffeda5ea docs(generator): решения грилинга — корзина и уровень спроса товара
- Зачем:
  - приёмка #40 нашла в торговых данных два точных равенства, каких в живом
    магазине не бывает: событие корзины возникало ровно у визитов со
    страницей /cart, а внутри такого визита в корзину ложились все открытые
    карточки.
- Что:
  - в спеку генератора, раздел 9, добавлен блок «Решено при исполнении #40
    после приёмки»: корзина отвязана от страницы корзины, уровень спроса
    становится свойством товара, границы правки — вилки раздела 5.
  - записаны отклонённые варианты: свой процент у каждого sku и вывод
    склонности из цены.
  - в CONTEXT.md заведён термин «уровень спроса».
- Проверка:
  - git show --stat; текст решений читается без обращения к переписке.
2026-08-02 21:06:51 +03:00
ddadmin a7442765ff docs(generator): решения #40 — торговые события
- Зачем:
  - перед реализацией торговых событий грилинг закрыл шесть развилок; без
    записи решения и отклонённые варианты потерялись бы, а часть из них
    выходит за границы тикета и меняет постановку.
- Что:
  - в раздел 9 спеки генератора добавлен блок «Решено при исполнении #40»:
    корзина шире заказа, метка покупателя становится значимой, место
    торговых событий, номер заказа, промокод, единицы денег, разная длина
    массивов purchase* и product*, orjson, хвост покупки у границы суток.
  - в раздел 8 добавлены хвосты этапам 3 и 4: скидка по промокоду и
    намеренное расхождение сумм.
  - в словарь добавлены «покупатель» и «торговое событие».
- Проверка:
  - чтением: docs/specs/2026-08-01-generator.md, разделы 8 и 9; CONTEXT.md.
2026-08-02 17:17:31 +03:00
ddadminandClaude Opus 5 eb433ad023 feat(generator): день-функция — трафик, визиты и просмотры страниц
Зачем: план состава отдаёт дневную аудиторию, но событий у мира ещё не было.
День-функция превращает аудиторию в поток просмотров — на нём стоят лабы про
сборку визитов и про витрины, а следующий этап вешает на него торговые события.

Что:
- `day.py` — день как чистая функция зерна и номера дня: суточная волна в
  местном времени посетителя, визиты по документированным правилам нарезки,
  все 47 колонок выгрузки; шов для торговых событий — ряды `page` и `product`;
- `reference.py` — справочники-литералы: профили устройств, города Поволжья с
  настоящими гео-id Яндекса, источники трафика, карта сайта;
- `catalog.py` и `data/catalog/products.csv` — каталог на 180 позиций, общий у
  генератора и будущего словаря ClickHouse;
- `weights.py` — выбор по целым весам, один на план и на день;
- паспорт куки (устройство и город) переехал в план состава; броски приписаны
  последними, поэтому измеренные числа канонического мира не сдвинулись;
- словарь: «визит» закреплён за сессией, одноимённое понятие плана стало
  «днём активности»; статьи в `CONTEXT.md`;
- решения по ходу — в спеку генератора, раздел 9; наполнение
  `ParsedParamsKey1` отложено тикетом #47.

Проверка: `make lint`, `make typecheck`, `make test` — 353 passed (было 297).
Счётчики плана после правки те же: приток 3827,64/день, дневная аудитория
6235–7124, 68 119 посетителей за 14 дней, 170 двухкуковых пар. День 0 —
45 810 событий за 0,6 с, снимок 14 дней — 5,9 с при пороге 30 с на день.
Две слепые линии ревью, десять находок, все закрыты и перепроверены.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 16:12:17 +03:00
ddadminandClaude Opus 5 d17bd4e876 fix(generator): правки по линии ревью Кодекса — зёрна, словарь, сторожа
- Зачем:
  - третья линия ревью (Кодекс, другое семейство моделей) нашла дыру в
    стороже неизменности когорты, отставший словарь и тест, который
    сторожил не тот адрес дерева зёрен.
- Что:
  - когорта отдаётся видом на замороженный массив: флаг только для
    чтения вызывающий мог снять и испортить память, которой пользуются
    все дни окна. Граница защиты названа в докстроке — от случайности,
    не от умысла.
  - CONTEXT.md и комментарий `RETURN_TAIL_DAYS`: окно активности — от
    первого визита человека, общее на обе куки (спека это уже говорила,
    словарь отстал).
  - сторож предыстории сверял день −N с днём N, а сталкиваются −N и
    N−1; тем же классом слепоты страдали сторожа независимости состава
    и дня и различия компонентов — все три переписаны на сверку со всем
    куском адресов, куда подпоток мог бы попасть.
- Проверка:
  - make test (297), make lint, make typecheck;
  - батарея из 17 мутантов по plan/world/seeds — выживших нет; гоняется
    с PYTHONDONTWRITEBYTECODE=1: цикл правки и отката внутри одной
    секунды оставлял устаревший .pyc, и тесты шли по старому байт-коду.
- Отклонено с доводом:
  - перепроверка настаивала, что сторож неизменности не закрыт: через
    `.base` вида владелец данных размораживается. Верно фактически, но
    закрывающего состояния у находки нет — владелец памяти в numpy
    размораживается всегда, а копия когорты на каждый вызов меняет 2 мс
    на 16 МиБ копирования и убивает смысл запоминания.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 13:13:41 +03:00
ddadminandClaude Opus 5 2502f906a8 fix(generator): правки по двум линиям ревью плана состава
- Зачем:
  - ревью нашло два места, где код и документы говорили неправду, и
    несколько мест, где имена или комментарии вводили в заблуждение.
- Что:
  - обещание докстроки `seeds.py` подкреплено тестом: адрес в дереве
    даёт тот же подпоток, что цепочка `spawn`.
  - в тесте гарантии пар убран сторож-тавтология, вместо него проверка,
    что заказы назначены с двух разных кук.
  - `Cohort.visitors_on` — «кто пришёл в день D» спрашивается у когорты,
    а не собирается снаружи из четырёх её массивов.
  - имена: `CLIENT_ID_LIMIT`, `_RETURN_*_CUMULATIVE`, `first_of_day`,
    `WEEKLY_PROFILE_PERCENT` — профиль недели один на весь мир, по нему
    же пойдёт трафик дня-функции (#39).
  - спека: в дерево зёрен внесена ветвь предыстории; окно активности —
    от первого визита человека, общее на обе куки (иначе загляд назад
    ленивой формы удваивается); оценка накопленной аудитории больше не
    спорит с измерением.
  - CONTEXT.md: «подпоток» и «конфигурация мира».
- Проверка:
  - make test (296), make lint, make typecheck; мутации перепроверены
    после переноса среза дня в `Cohort`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 12:33:27 +03:00
ddadminandClaude Opus 5 abac6afe18 feat(generator): план состава мира — зерно, приток, двухкуковые пары
- Зачем:
  - тикет #38: состав мира должен быть чистой функцией зерна, а счётчики
    будущего манифеста — известны до генерации хоть одного события.
- Что:
  - `world.py` — конфигурация мира одним модулем чистых данных: приток,
    недельная волна, профиль возвратов, доли покупателей и пар, D0.
  - `seeds.py` — иерархия подпотоков на `SeedSequence`: состав мира
    (ось и предыстория) отдельно от дней и их компонентов.
  - `plan.py` — ленивый план состава: когорта дня, аудитория дня из
    окна возвратов, гарантированные заказы пар, счётчики горизонта.
    Случайность — только целыми числами.
  - спека, раздел 1: вторая кука пары рождается по затухающему профилю
    возвратов; раздел 9: измеренные числа канонического мира, оценка
    накопленной аудитории поправлена с ≈60 до 68 тыс.
  - CONTEXT.md: термин «когорта дня»; README генератора — новые модули.
- Проверка:
  - make test (295 тестов), make lint, make typecheck;
  - тесты проверены мутациями: 12 подмен в плане и конфигурации,
    каждая роняет ровно свой тест.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 12:20:48 +03:00
ddadminandClaude Opus 5 78ddb86817 docs(specs): решения #38 — ленивый план состава, числа притока, конфигурация
- Зачем:
  - тикет #38 требует решить и внести в спеку генератора числа притока,
    D0 и формат конфигурации мира до реализации плана состава.
- Что:
  - раздел 1 спеки: ленивый план по когортам дня, предыстория с полкой
    от D0, гарантия двухкуковых пар назначенными заказами (единица —
    человек), пять отклонённых вариантов с доводами.
  - раздел 9: приток 3 800 кук/день, окно активности 90 дней (решение
    владельца), доля покупателей 5% людей, D0 = 2026-06-01,
    конфигурация мира — модуль чистых данных; шапка Proposed → Accepted.
  - CONTEXT.md: термины «план состава», «приток», «хвост возвратов»,
    «предыстория»; «состав мира» уточнён, словарь очищен от решений.
- Проверка:
  - двойное слепое ревью правок (Codex + Claude), все 20 находок
    закрыты; арифметика чисел пересчитана ревьюерами независимо.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 11:47:59 +03:00
ddadminandClaude Opus 5 3ba40ffdf2 docs(context): «имя в DDS» вычеркнуто из избегаемых — правка владельца
- Зачем:
  - запрет был лишним: dds.v_event по разделу 7 мастер-спеки эти имена и
    берёт, так что синоним не всегда ошибка.
- Что:
  - в статье «Нормализованное имя» снята строка _Избегать_; различие
    с именем атрибута в модели данных осталось в теле статьи.
- Проверка:
  - вычитка.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:15:53 +03:00
ddadminandClaude Opus 5 24c8dd9b98 refactor(generator): нормализованное имя вместо имени в DDS, тесты на имена сняты
- Зачем:
  - контракт вёл себя как хозяин чужого слоя: поле называлось dds_name, в
    описании стоял столбец «Имя в DDS», а два теста прибивали имена
    гвоздями. Спека же задала вид имени (snake_case), а не список: имена
    атрибутов складывает модель данных DDS, и решать это не трекеру.
- Что:
  - поле контракта и столбец описания стали нормализованным именем: имя
    источника в нашем стиле. В описании и в докстринге сказано прямо, что
    слой DDS называет атрибуты по своей модели.
  - сняты оба теста на имена — копия имён DDS и конспект состава по
    мастер-спеке. Они не проверяли верность имени, только неизменность, а
    неизменность и так сторожит пересборка описания: молчаливой правки
    контракта не бывает, она всплывает диффом документа.
  - остались проверки формы: 47 колонок, уникальность, стили имён,
    заполненность, согласие типов numpy и ClickHouse, порядок групп.
  - спека генератора (раздел 3) и CONTEXT.md согласованы тем же
    коммитом: уточнение внесено как расхождение, найденное при исполнении.
- Проверка:
  - make test (248 тестов), make lint, make config-test;
  - make docs, затем git diff --exit-code docs/ — пусто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:01:04 +03:00
ddadminandClaude Opus 5 2890c7f9fb feat(generator): каркас проекта и контракт схемы события
- Зачем:
  - этап 2 начинается с формы: контракт схемы — источник истины и для
    генерации событий, и для DDL хранилища, а имена пакета и модулей
    задают границы всем следующим тикетам этапа.
- Что:
  - заведён uv-проект generator/ (pyproject.toml и uv.lock в git; numpy,
    pytest и ruff), пакет clickstream_generator.
  - schema.py — контракт: чистые данные о 47 колонках выгрузки (имя
    Метрики, тип ClickHouse, тип numpy, имя для DDS, группа); порядок
    несёт сам кортеж COLUMNS, отдельного поля с номером нет намеренно.
  - schema_doc.py собирает из контракта описание выгрузки
    docs/formats/clickstream-event.md — по нему пишется сторона
    хранилища; документ руками не правится.
  - тесты: инварианты контракта (состав, уникальность, заполненность,
    согласие типов и порядок групп) и свежесть описания выгрузки.
  - цели make lint, make test и make docs; README, AGENTS.md и
    CONTEXT.md дополнены генератором, форматами и словарной статьёй.
- Проверка:
  - make test (248 тестов), make lint, make config-test;
  - make docs, затем git diff --exit-code docs/ — пусто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 22:22:14 +03:00
ddadminandClaude Opus 5 41d4dee945 docs(context): DWH — законный синоним хранилища, из избегаемых убран
- Зачем:
  - владелец сознательно вывел DWH из списка избегаемых (синоним
    хранилища), а правка по холодному ревью вернула его по старому
    комментарию тикета #32 — откат к воле владельца.
- Что:
  - в статье «Хранилище» список «_Избегать_» снова: склад, склад данных.
- Проверка:
  - вычитка CONTEXT.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 21:01:58 +03:00
ddadminandClaude Opus 5 7df5e482b9 docs(specs): правки по холодному ревью — долг словаря и потерянные доводы
- Зачем:
  - холодное ревью (Fable, свежая сессия) нашло невыполненный хвост
    тикета #32 и места, где доводы резолюций сжались до непонятности.
- Что:
  - мастер-спека 1.1: «склад» заменён на «хранилище» (хвост #32);
    CONTEXT.md: DWH в избегаемых, отдельная статья «Пакетный режим».
  - спека: восстановлены доводы «на маке и в WSL тоже» и «менти упрётся
    в красный чек манифеста»; обещания про diff привязаны к манифесту;
    темп ×60 и расчёт порога согласованы с числами разделов; заголовок
    притока честен про затухание; выход за мандат оговорён в «Зачем».
  - раздел 9: добавлены числа притока и календарная дата-константа D0;
    заметка исследования: у Faker единицы «значений/с», не «строк/с».
- Проверка:
  - вычитка; решения развилок не пересматриваются, правки текстовые.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 21:00:18 +03:00
ddadminandClaude Opus 5 8c69397ccf docs(specs): приток посетителей и фейкеры без гвоздя — вычитка владельца
- Зачем:
  - вычитка спеки на приёмке нашла дыру: состав мира читался как
    замкнутая труппа без новых посетителей — неправдоподобный магазин.
- Что:
  - раздел 1: состав не замкнут — план задаёт календарь появления кук,
    доля одноразовых высока; приток — часть плана, не мутация.
  - раздел 6: «Faker» ослаблен до «посточные фейкеры (Faker, mimesis)»,
    выбор библиотеки — этапу 2; там же оговорка про таблицы-литералы.
  - раздел 9: к интерфейсу запуска добавлены вопросы «кто зовёт
    генератор (даги world_init/next_day)» и «в каком контейнере живёт».
  - CONTEXT.md: в «Состав мира» добавлен календарь появления.
- Проверка:
  - вычитка; правки точечные, решения развилок не пересматриваются.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 20:49:49 +03:00
ddadminandClaude Opus 5 f85626508f docs(specs): собрана спека генератора из решений развилок карты #26
- Зачем:
  - этап 2 нельзя нарезать на тикеты без единой картины генератора,
    а решения четырёх развилок карты #26 жили только в тикетах трекера.
- Что:
  - новая спека docs/specs/2026-08-01-generator.md: функциональный мир,
    детерминизм до байта (SeedSequence сверен через Context7), контракт
    схемы, канонический сериализатор, числа и порог производительности;
    отклонённые варианты записаны с доводами.
  - мастер-спека согласована тем же коммитом: 1.4 — data contract вместо
    автогенерации DDL, 8 — в git только манифест, 11 — числа вместо
    «зафиксировать требования»; мелкие согласования в 7 и 9.
  - CONTEXT.md пополнен терминами модели мира и вывода генератора.
- Проверка:
  - вычитка; относительные ссылки спек указывают на существующие файлы
    в docs/specs/ и docs/research/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 19:47:29 +03:00