Commit Graph
111 Commits
Author SHA1 Message Date
ddadminandClaude Fable 5 7972847601 docs(dds): спроектирована модель заказа в DDS и заведён набор доков слоя
- Зачем:
  - развилка #130 карты #127: заказу нужен объект в DDS — зерно, состав,
    движок и судьба служебных колонок; резолюции этапа 4 нужен дом в git.
- Что:
  - создан набор docs/architecture/dds/ по ADR 0011: README с картой
    модели и правилами слоя, order.md с моделью заказа — зерно «заказ в
    текущем состоянии», позиции массивами, MergeTree с заменой партиций,
    свои _load_id/_load_ts вместо ярлыков ODS (закрывает наследство #85);
  - указатели на модель добавлены в orders/README.md, orders/ingestion.md
    и storage.md.
- Проверка:
  - только документы; относительные ссылки сверены с деревом docs/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 12:03:26 -04:00
ddadminandClaude Opus 5 45360b6bcf docs(ods): вычитаны комментарии и документация починки раскладки
- Зачем:
  - правки #137 дважды рассказывали одно и то же: механика капкана жила и в
    storage.md, и у вставок, а цифры замеров — и в прозе, и в леджере;
    у читателя это спрашивает больше внимания, чем даёт.
- Что:
  - комментарии у вставок order_parse.sql сжаты до довода с указателем на
    storage.md; из капкана в storage.md цифры ушли в «Что проверено»;
    в ingestion.md замеры перенесены из прозы в свой раздел «Что проверено».
- Проверка:
  - рендер order_parse.sql по-прежнему делится ровно на два запроса
    (из комментария убрана точка с запятой, по которой оператор делит файл).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 04:35:41 -04:00
ddadminandClaude Opus 5 94a1ffeee2 docs(ods): описана семантика snapshot_date после схлопывания версий
- Зачем:
  - проверка #137 показала: у заказа, не менявшегося несколько дней, версию
    выбирает только updated_at, и какая из одинаковых строк переживёт
    схлопывание — не обещано; snapshot_date выжившей строки означал не то,
    что обещала документация (у 86 заказов стартового мира выжил серединный
    день наблюдения).
- Что:
  - в docs/architecture/orders/ingestion.md записано: snapshot_date, _load_id
    и _load_ts выжившей строки — координаты одного из наблюдений версии, не
    обязательно последнего; допущение «уточнение поднимает updated_at»
    названо допущением с границей применимости.
  - та же оговорка — в комментарии DDL ods.order_rep.
- Проверка:
  - замеры на живом стенде: во всех 3034 парах строк с равным updated_at
    деловые поля совпали побайтово; монотонность updated_at по слепкам —
    без нарушений; ods.order_v сошёлся с эталоном argMax из сырья построчно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 04:28:51 -04:00
ddadminandClaude Opus 5 8c76a7a0d8 fix(ods): заказы разложены по своему ключу шардирования
- Зачем:
  - заводское parallel_distributed_insert_select = 2 исполняло вставку из
    Distributed в Distributed локально на каждом шарде, минуя ключ цели:
    1189 из 1662 заказов задвоились на обоих шардах, а ReplacingMergeTree
    через границу шарда дубли не схлопывает.
- Что:
  - в обе вставки sql/ods/order_parse.sql добавлено явное
    parallel_distributed_insert_select = 0 с доводом комментарием.
  - в docs/architecture/storage.md описан капкан «INSERT из Distributed в
    Distributed при несовпадающих ключах» и записана проверка (#137).
- Проверка:
  - межшардовые дубли по ods.order_rep дают 0 (было 1189), каждый шард
    держит только свои ключи; uniqExact(order_id) не изменился.
  - после прожитого дага world_next_day новый слепок принят, дублей
    по-прежнему 0.
  - make smoke, make check-clickhouse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 04:02:52 -04:00
ddadmin 20b0666d28 fix(airflow): добавлен повтор запуска DAG после индексации
- Зачем:
  - при холодном старте GET уже видел DAG, пока POST запуска ещё считал его устаревшим.
- Что:
  - точный стартовый ответ 404 повторяется до двадцати раз с паузой три секунды.
  - прочие ответы и сетевые ошибки завершают команду без риска двойного запуска.
  - граница повторов объяснена в коде и документации проверок.
- Проверка:
  - выполнены `bash -n scripts/run-dag.sh`, `make config-test` и изолированные сценарии ответов API.
  - выполнены холодный `make up`, `make lint` и `make smoke` (21/21).
2026-08-22 02:20:01 -04:00
ddadmin 8d82679bc1 feat(airflow): подключён официальный провайдер ClickHouse
- Зачем:
  - нужен единый штатный путь исполнения SQL и программных запросов ClickHouse из Airflow.
- Что:
  - добавлен официальный провайдер и подключения типа `clickhouse`.
  - SQL-файлы переданы `SQLExecuteQueryOperator`, а программные запросы — `ClickHouseHook`.
  - разбор заказов собран в одном файле, DDL разделён по задачам, документация и смоук обновлены.
- Проверка:
  - выполнены `make lint` и `make config-test`.
  - собран образ, выполнены `pip check`, `make rebuild-storage`, `make smoke` (21/21), `make check-clickhouse` (10/10) и `make check-services` (7/7).
2026-08-22 01:40:56 -04:00
ddadmin bca1ffa379 fix(world): устранены расхождения после переноса на main
- Зачем:
  - свежий main добавил мониторинг и проявил устаревшие доводы, мёртвые настройки и слишком короткий тайм-аут DDL.
- Что:
  - документация приведена к текущему жизненному циклу мира и составу стенда;
  - удалены лишняя проверка и неиспользуемый YAML-якорь;
  - ожидание ClickHouse возвращено к 300 секундам, а отказы и параметры DockerOperator пояснены.
- Проверка:
  - `make config-test`;
  - `make lint`;
  - `make smoke` — 21/21.
2026-08-21 23:38:45 +03:00
ddadmin 7e6554507a docs(world): описан жизненный цикл прикладного мира
- Зачем:
  - границы владения Compose и Airflow должны быть видны менти из одного источника истины.
- Что:
  - добавлены спека и ADR жизненного цикла мира;
  - обновлены README, архитектурные справочники и прежние решения;
  - правило полной очистки записано как явное и проверяемое требование к агенту.
- Проверка:
  - документация вычитана через `ai-text-lint` и `writing-for-agents`;
  - выполнена литературная вычитка русского текста;
  - `git diff --check`.
2026-08-21 23:02:15 +03:00
ddadmin 48bec014cf fix(monitoring): доводы приведены к замерам, пары нод больше не расходятся
- Зачем:
  - второй круг ревью нашёл, что текст спорит с собственной приёмкой: окно
    держалось доводом «слияния за минуту-две», а замер даёт двадцать секунд;
    довод панели ошибок опирался на отказ по памяти, которого ADR 0004 на этом
    стенде не обещает.
- Что:
  - «Части» сортируются по таблице: сортировка по числу частей разводила две
    ноды одной таблицы, как только между ними вставала чужая.
  - у «Слияний» столбик шириной в минуту, иначе редкие события выходили
    волосяной линией; из заголовка убран «слой» — фильтр отсекает журналы
    сервера, а не выбирает слои.
  - в cluster.md исправлены довод за окно, ложное «времени в system.parts нет»,
    счёт панелей без нод (их две, не одна) и разрешение, в котором снят замер
    процессора.
  - панель ошибок и её запись в ADR 0002 переписаны под проверенное: отказ на
    шарде воспроизведён принудительным пределом памяти.
  - из queries.md убран абзац про SELECT 1 — он объяснял пол удалённой панели.
- Проверка:
  - make config-test; make smoke 21/21; make check-clickhouse 10/10.
  - все десять панелей отвечают через /api/ds/query в окнах на полчаса, час и
    шесть часов; шаг столбика слияний проверен отдельным запросом.
2026-08-21 15:28:03 -04:00
ddadmin 42075ff9fb fix(monitoring): панели уточнены по ревью, приёмка снята под нагрузкой
- Зачем:
  - слияния в верхних строках показывали только журналы самого сервера, а
    предел в 15 строк рвал пару нод пополам: панели отвечали не на свой вопрос.
- Что:
  - «Слияния» стали столбиками по слоям: журналы сервера отделены, ряды ods и
    stg видны своими.
  - «Части» показывают все таблицы слоёв целиком, без предела и без обрезанных
    пар.
  - у «Запросов» убран «Поток запросов» — его пол составляли пробники
    состояния; у «Кто спрашивает» убрана лишняя колонка.
  - ряд «коробка» назван «отведено» — как в заголовке панели.
  - доводы дописаны в cluster.md и queries.md, отступления от ADR 0002
    записаны в сам ADR.
- Проверка:
  - make config-test; make smoke 21/21; make check-clickhouse 10/10.
  - все десять панелей отвечают через /api/ds/query в окнах на полчаса, час и
    шесть часов.
2026-08-21 15:11:21 -04:00
ddadmin 7ffb4606f1 feat(monitoring): собраны дашборды «Кластер» и «Запросы»
- Зачем:
  - ADR 0002 обещает три дашборда; собран был один, а симптом инфраструктуры
    как следствие решения человека показать было негде.
- Что:
  - cluster.json — семь панелей: процессор, память с пределом коробки, диски,
    части по таблицам, слияния, keeper, очередь распределённых DDL.
  - queries.json — четыре панели поверх system.query_log: тяжёлые запросы,
    поток запросов, аппетит по пользователям, ошибки.
  - системные таблицы читаются через clusterAllReplicas; очередь DDL — без
    обёртки, она живёт в keeper и у обеих нод одна.
  - устройство обоих дашбордов записано в docs/architecture/monitoring/,
    правило про пороги поднято из data.md в README набора.
- Проверка:
  - make config-test; make smoke 21/21; make check-clickhouse 10/10.
  - все одиннадцать панелей отвечают через /api/ds/query.
2026-08-21 14:52:58 -04:00
ddadmin 06c77c5ed2 docs(monitoring): «скребёт» заменено, поправлены падеж и тяжёлые фразы
- Зачем:
  - «что скребёт Prometheus» — калька со scrape, по-русски звучит коряво;
    рядом нашлись ошибка падежа и пара фраз, которые читаются с запинкой.
- Что:
  - заголовок набора и раздел названы по-русски: «за чем следит» и «кого
    опрашивает Prometheus».
  - «Единицы общие — сообщений за интервал» исправлено на именительный падеж.
  - разбиты две перегруженные фразы: про место диагностики версий и про то,
    что читать на панели сходимости.
- Проверка:
  - make config-test.
2026-08-21 14:15:57 -04:00
ddadmin 28177cff92 docs(monitoring): вводный абзац не повторяет каталог дашбордов
- Зачем:
  - проход на вычитание: каталог дашбордов появился в этом же PR, и абзац
    «что здесь есть» стал называть «Данные» второй раз.
- Что:
  - вводный абзац отсылает к каталогу, а имя дашборда и состав панелей
    остались только в самом каталоге.
- Проверка:
  - make config-test.
2026-08-21 14:07:35 -04:00
ddadmin ee0306d19e docs(monitoring): убраны повтор заголовков, запрет и двойной довод
- Зачем:
  - таблица панелей дублировала заголовки из data.json и уже правилась
    дважды; заголовок раздела называл запрещённое действие вместо нужного;
    довод про отвергнутые способы счёта сходимости стоял в двух местах.
- Что:
  - в таблице панелей остались только источники, вопрос панели читается с её
    заголовка на экране.
  - раздел про чтецов Kafka назван по тому, что панели делают, опасность
    kafka_commit_on_select осталась внутри как причина.
  - из записи о приёмке убран пересказ довода, замер оставлен.
- Проверка:
  - make config-test; make lint.
2026-08-21 14:01:32 -04:00
ddadmin 61eb2680ff docs(monitoring): приёмка дашборда переехала к дашборду
- Зачем:
  - в общем разделе «Что проверено» лежали два разных прогона, пола и
    дашборда; следующие дашборды копили бы там осадок.
- Что:
  - запись о приёмке дашборда «Данные» перенесена в data.md, в README.md
    осталась приёмка самой зоны.
- Проверка:
  - make config-test.
2026-08-21 14:00:33 -04:00
ddadmin 91e281fd32 docs(monitoring): справочник зоны разрезан на набор документов
- Зачем:
  - половина справочника была про один дашборд «Данные», а этап приносит ещё
    два дашборда и правила оповещения: описание самой зоны утонуло бы.
- Что:
  - заведён набор docs/architecture/monitoring/ по ADR 0011: README.md — зона
    и каталог дашбордов, data.md — устройство дашборда «Данные».
  - обе входящие ссылки перенацелены: README.md репозитория и ADR 0002.
- Проверка:
  - make config-test; ссылки из README.md и ADR 0002 открываются.
2026-08-21 14:00:05 -04:00
ddadmin e5c573bb25 fix(monitoring): панели уточнены по ревью, приёмка снята на стенде
- Зачем:
  - две линии ревью нашли, что колонки панели брака читаются как
    непересекающиеся кучи, «вовремя ли» из ADR 0002 не считается вовсе, а
    отказ от ряда «против вчерашнего» нигде не записан рядом с решением.
- Что:
  - в панели брака столбец назван «Из них в брак»: брак — часть приехавшего,
    а не соседняя куча.
  - панель слепка считает паузу после прошлого слепка — это и есть «вовремя
    ли» из ADR 0002; пустая пауза у самого раннего слепка держится
    обнуляемым типом.
  - отказ от ряда «против вчерашнего» записан в ADR 0002 и в monitoring.md с
    доводом: сдвиг на модельные сутки прописал бы ускорение мира в JSON.
  - в monitoring.md сказано, почему свежесть читает физическую _dist, а не
    ods.*_v, и почему панель слепка не знает окна; довод про сломанный JSON
    оставлен у кода, а не задвоен в доке.
  - README больше не утверждает, что панелей нет.
  - у разбора дашбордов убран флаг -e: он обещал утверждение, которого нет.
- Проверка:
  - make config-test (краснеет на нарочно сломанном JSON дашборда), make lint,
    make smoke 21/21, make check-clickhouse 10/10; все шесть панелей отвечают
    через /api/ds/query. Опыты приёмки и их следы — monitoring.md,
    «Что проверено».
2026-08-21 11:23:05 -04:00
ddadmin 180ef5b94f feat(monitoring): собран дашборд «Данные» из шести панелей
- Зачем:
  - главный дашборд ADR 0002 отвечает на вопрос «с потоком всё в порядке?»;
    три его вопроса — свежесть, брак и сходимость — у предшественника не
    заданы вовсе, и в этом весь смысл экрана.
- Что:
  - добавлен infra/grafana/provisioning/dashboards/data.json: свежесть, поток,
    брак, сходимость, отставание чтения из Kafka и дневной слепок заказов;
    порогов и цветовых вердиктов на панелях нет.
  - в monitoring.md записано устройство дашборда: почему слои у панелей разные
    и как считается сходимость — с доводом и двумя отвергнутыми способами.
  - make config-test разбирает JSON дашбордов тем же доводом, что и файлы
    дагов: сломанный файл Grafana молча оставляет прежнюю версию.
- Проверка:
  - make config-test; на живом стенде make smoke 21/21 и make check-clickhouse
    10/10; все шесть панелей отвечают через /api/ds/query.
2026-08-21 11:06:43 -04:00
ddadmin c5dd9c2d34 feat(monitoring): подготовлена основа мониторинга Kafka и ClickHouse
- Зачем:
  - подготовлен пол мониторинга для SQL-панелей и метрик отставания Kafka.
- Что:
  - добавлены отдельная роль ClickHouse, источник Grafana и Kafka exporter.
  - образ Grafana собирается с закреплённым плагином вне тома данных.
  - Prometheus, смоук и документация обновлены под четыре цели.
- Проверка:
  - make config-test; make lint.
  - make smoke; make check-clickhouse; make check-services.
  - make -C generator lint typecheck test.
2026-08-21 09:56:56 -04:00
ddadmin 67313bf997 chore(main): объединены локальные и удалённые изменения
- Зачем:
  - локальные уточнения документации нужно опубликовать без потери слитых изменений #96 и #109.
- Что:
  - актуальный `origin/main` объединён с двумя локальными коммитами документации.
  - сохранены уточнения применения настроек ClickHouse и границ проектирования заказов.
- Проверка:
  - `make config-test`.
  - `make lint`.
2026-08-21 11:09:49 +03:00
ddadmin 2a94ee9bde refactor(ods): упрощены имена таблиц заказов
- Зачем:
  - ODS должен показывать сущность заказа, а не способ её доставки слепком.
- Что:
  - физические таблицы и представление переведены на имена `order_*`.
  - SQL загрузки, DAG и документация приведены к тому же словарю.
- Проверка:
  - `make config-test`, `make lint`.
  - `make clean && make up`, `make smoke`, `make check-clickhouse`, `make check-services`.
2026-08-21 10:49:02 +03:00
ddadmin ba4743ed52 feat(stand): добавлены стартовые слепки заказов
- Зачем:
  - свежий стенд должен показывать отставание ночной выгрузки заказов от кликстрима.
- Что:
  - после событий генератор отправляет семь слепков дней 0…6 в общий топик orders.
  - make up запускает orders_ingest, ограниченно ждёт его завершения и оставляет день 7 первому ходу мира.
  - README и карта проверок описывают второй источник и измеренную цену подъёма.
- Проверка:
  - make clean && make up; make smoke; make check-clickhouse; make check-services.
  - cd generator && make lint && make typecheck && make test.
2026-08-20 19:12:01 +03:00
ddadmin 9bb044e1ce docs(orders): убран преждевременный вопрос о _load_id
- Зачем:
  - состав полей DDS определяется вместе с моделью, поэтому отдельная задача заранее не нужна.
- Что:
  - удалена задача #85 из списка открытых решений по заказам.
  - убран устаревший указатель на неё из описания приёма.
- Проверка:
  - выполнен `git diff --cached --check`.
2026-08-20 15:57:37 +03:00
ddadmin 64bcd3d596 fix(generator): исправлен расчёт скидки после складской дельты
- Зачем:
  - слепок D7 содержал отрицательный итог заказа и попадал в брак ODS.
- Что:
  - скидка пересчитана от корзины после удаления отсутствующей позиции.
  - сериализатор отклоняет отрицательные деньги через ValueError.
  - добавлены проверки восьми стартовых дней и обновлена опись мира.
- Проверка:
  - make lint, make typecheck и make test в generator/.
  - живой world_next_day: 1694 строки приняты, таблица ошибок пуста.
2026-08-20 13:13:14 +03:00
ddadminandClaude Opus 5 5cc0ede75a fix(clickhouse): починена проверка словаря, выметены хвосты переноса
Зачем: ревью в три линии нашло настоящий дефект. Девятая проверка кластера
звала dictGet('dds.products', …) — объекта после переноса не существует, и
make check-clickhouse падал. Промах вырос из недосчёта: упоминания dds.products
я искал с фильтром по расширениям, .sh туда не попал, и в ADR уехало «четыре
упоминания» вместо семи.

Что: проверка переведена на dic.products и сверяет цену, умножив её обратно на
сто, — так утверждается ещё и точность приведения к Decimal. Раздел 3
мастер-спеки и список пользователей README догнали перенос: оба описывали
отменённое устройство. В ADR 0012 добавлено условие пересмотра для дома — для
него его не было, хотя ради дома тикет и заводился; недосчёт записан в
«Что проверено» как урок. Разнобой обозначений сведён: карта таблиц в доке
хранилища перешла на имена баз строчными, таблица мастер-спеки — на заголовок
«Где», зону всюду зовут зоной, а не слоем. Термин «Справочник» переписан без
метафоры и уложен в формат глоссария. Вычтено лишнее: комментарии DDL
сократились вдвое, из ADR 0006 и 0007 убраны самооправдание и дублирующие
абзацы, отраслевой образец получил честную оговорку о непроверяемости.

Проверка: make config-test, make lint, make smoke и make check-clickhouse —
все зелёные, десять проверок кластера из десяти.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 12:10:03 +03:00
ddadminandClaude Opus 5 b3e66034fc docs(storage): устройство справочников оставлено в одном месте
Зачем: каждое утверждение о словаре было записано трижды — в доке хранилища, в
комментариях DDL и в ADR 0012. Правка поведения стоила бы трёх согласованных
правок, а расхождение между копиями обнаружилось бы не сразу. Дока хранилища —
карта, а не пересказ реализации.

Что: раздел «Справочники» сведён к своему уровню — правило принадлежности зоны,
топология на кластере, единицы денег и указатели. Механика (движок, форма пути,
пользователь, окно обновления) осталась только в sql/ddl/05-dic-catalog.sql,
рядом с кодом, который она объясняет. Блок «Что проверено» отправлен к замерам
ADR 0012 вместо их пересказа — так же, как он уже поступает с ADR 0005.

Проверка: минус 30 строк; ссылок на убранный подзаголовок в репозитории нет,
три относительные ссылки нового текста разрешаются в существующие файлы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:42:26 +03:00
ddadminandClaude Opus 5 abc94ba406 feat(clickhouse): справочники вынесены в зону dic, словарь читает подложку
Зачем: место словаря в dds было унаследовано от первого объекта, а не решено.
Справочник читают несколько слоёв, а производит его в хранилище ни один —
прописка внутри одного из потребителей приписывала DDS владение, которого у
него нет.

Что: заведена база dic вне цепочки STG → ODS → DDS → DM, dds.products переехал
в dic.products. Под словарём появилась подложка dic.products_file на движке
File — намеренное усложнение ради урока: в бою источник словаря приезжает
процессом, а не лежит файлом у сервера. Источник объявлен формой query и
приводит цену из целых копеек каталога в Decimal(18, 2), как у денег бэкенда.
Обновление — окном LIFETIME(MIN 60 MAX 90) вместо ручной перезагрузки, ценой
заявленной неатомарности между нодами. У словаря свой беспарольный
пользователь dict с единственным правом на чтение dic. Суффикс _file добавлен
в конвенцию имён ADR 0006. Решение, отвергнутые варианты и условия пересмотра
— ADR 0012.

Проверка: make clean && make up на собранном заново стенде — зелено, make smoke
20 проверок и 0 ошибок, make lint чисто. Словарь LOADED со 180 строками, цена
Decimal(18, 2) и точна на копейках: 188990 → 1889.90. Пользователь bi читает
словарь dictGet-ом со второй ноды, analyst — подложку соединением, база dds
пуста. Минимальное право на движок замерено тремя пользователями: достаточно
GRANT FILE ON *.*, хотя отказ называет TABLE ENGINE ON File.

Closes #105

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 11:37:26 +03:00
ddadmin 13abc58d98 feat(clickhouse): добавлен словарь товаров из каталога
- Зачем:
  - витринам нужен общий с генератором каталог, а менти — пример штатного обновления словаря.
- Что:
  - каталог смонтирован read-only в обе ноды и создан как dds.products ON CLUSTER.
  - роли получили dictGet, а check-clickhouse сверяет ответ под etl с исходным CSV.
  - источник, раскладка и явная политика обновления записаны в документации.
- Проверка:
  - make up с чистыми томами; make lint; make config-test; make smoke; make check-clickhouse.
  - в generator: make lint typecheck test — 427 тестов прошли.
2026-08-19 00:50:14 +03:00
ddadmin a5508bd9ec refactor(orders): SQL приёма вынесен из DAG
- Зачем:
  - преобразования хранилища должны читаться рядом с целевым слоем, а DAG должен показывать оркестрацию.
- Что:
  - запросы забора и разбора заказов разложены по каталогам STG и ODS.
  - общий контракт провода подключён в обе ветви штатным шаблонизатором Airflow.
  - SQL смонтирован во все службы Airflow, а правило раскладки записано в архитектуре.
- Проверка:
  - make lint config-test smoke check-services.
  - airflow tasks render для pull_batch и parse_batch; airflow tasks test для parse_batch.
2026-08-18 23:55:04 +03:00
ddadmin bdcbe48b59 feat(orders): добавлен версионный ODS заказов
- Зачем:
  - менти должен различать версию заказа, наблюдение источника и запуск загрузки.
- Что:
  - добавлены таблицы версий и брака заказов, а также представление ods.order_v.
  - даг orders_ingest дополнен строгим переходом одного среза STG в две цели ODS.
  - результаты опытов записаны в документации, дефект генератора вынесен в #102.
- Проверка:
  - make lint config-test smoke check-clickhouse check-services.
2026-08-18 23:10:01 +03:00
ddadmin 6bd9190142 feat(orders): добавить приём слепков в STG
Зачем:
- связать проигрывание модельных дней с пакетным приёмом заказов
- показать на одном стенде различие потокового push и пакетного pull

Что:
- добавлен топик, Kafka-чтец и реплицированное сырьё заказов
- добавлен даг orders_ingest с одним прямым чтением и идентификатором загрузки
- работники мира отправляют слепок, ждут приём и только затем двигают позицию
- решения, границы отказа и проверки отражены в ADR и архитектурных документах

Проверка:
- make lint
- make config-test
- make smoke
- make check-clickhouse
- make check-services
2026-08-18 21:33:13 +03:00
ddadmin 0d90bd566b feat(generator): добавлена судьба заказов
- Зачем:
  - issue #91 требует решать исход и расхождение заказа в день его рождения.
- Что:
  - добавлены три исхода, моменты оплаты и отмены и дельта одной позиции.
  - обновлены проверки повторяемости и архитектурное описание заказов.
- Проверка:
  - cd generator && make lint && make typecheck && make test (418 passed).
2026-08-18 16:02:12 +03:00
ddadminandClaude Opus 5 b2fcc7e593 feat(generator): заказы дня — проекция покупок с деньгами магазина
- Зачем:
  - тикет #90: второй источник должен смотреть на торговую половину дня
    глазами бэкенда — заказ есть проекция покупки, а не второе порождение;
    учебный результат — два источника согласованы по построению, а не
    сверкой.
- Что:
  - `commerce` отдаёт вторым выходом покупки дня: номер заказа, корзину,
    выручку клиента, промокод и человека за кукой; новых бросков в
    подпоток `COMMERCE` не добавилось, события дня не сдвинулись.
  - заведена заказная сторона: подпоток `ORDERS` на позиции 2 дерева зерна
    (прежнее мёртвое имя `DISCREPANCIES`), ветвление — по дню рождения
    заказа; `LATECOMERS` не тронут, его наполнит этап 6.
  - новый модуль `orders.py`: деньги заказа целыми копейками —
    `items_total` из корзины, `discount` по таблице «код → скидка»
    (округление вниз), `delivery` броском по таблице весов на полную длину
    дня, `total` = `items_total` − `discount` + `delivery`.
  - план состава завёл `person_id` — последним броском когорты, после
    паспортов: вторая кука пары повторяет ID своего человека, заказ
    показывает его как `user_id`, в событие Метрики он не попадает.
  - `world`: окно изменяемости `ORDER_WINDOW_DAYS` = 7 рядом с D0 и поясом,
    черновая таблица стоимости доставки.
  - слепок дня в тестах сторожит обе половины: заказы сравниваются наравне
    с потоком событий.
  - доки: имя подпотока заказной стороны и состав денег заказа записаны в
    `docs/architecture/orders`, README генератора знает про новый модуль.
- Проверка:
  - из `generator/`: make lint, make typecheck, make test (417 тестов);
    в корне — make lint.
  - опись мира не покраснела: байты восьми дней те же, заказная сторона
    события не сдвинула.

Closes #90

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:30:13 +03:00
ddadmin ded0dac5e2 feat(ods): добавлено представление актуальных событий
- Зачем:
  - потребителю ODS нужна точная актуальная версия события без знания о FINAL.
- Что:
  - создано ods.event_v над распределённой таблицей событий с FINAL.
  - сверка стартового мира переведена на новое представление.
  - документация различает физические версии ODS и бизнес-представление DDS.
- Проверка:
  - make config-test.
  - make check-clickhouse.
2026-08-18 11:33:59 +03:00
ddadminandClaude Fable 5 cf1c6c4636 docs(orders): спека превращена в принятый живой набор architecture/orders
Зачем: датированная спека — событие истории, а устройство компонента — живой
документ; большое полотно плохо грузится и агентом, и человеком (ADR 0011).

Что: вычитание #84 слито с переустройством формы: набор
docs/architecture/orders/ — индекс README и семь файлов по частям устройства
(нарезка по правилу «семь плюс-минус два»); датированные файлы удалены,
ссылки перенацелены, AGENTS.md дополнен правилом подпапки. Приёмка
владельцем #88 пройдена, черновой статус снят из README.

Проверка: холодная сверка миграции свежим тредом — потерь решений нет;
обход относительных ссылок набора — битых нет.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 22:05:52 +03:00
ddadmin ede1df765c docs(orders): зафиксирован версионный приём заказов
- Зачем:
  - отменённая подмена партиции snapshot_date противоречила порционному чтению Kafka и могла обучать потере ранее принятых версий.
- Что:
  - добавлены спецификация приёма заказов и ADR о версионном ODS с ods.order_v.
  - согласованы мастер-спека, дока хранилища, ADR 0008 и исследование формата.
  - зафиксированы граница приёма, координаты загрузки, диагностические повторы и отложенное проектирование DDS.
- Проверка:
  - git diff --cached --check.
  - горячее ревью по правилам репозитория и принятому решению.
  - два прохода холодного ревью.
2026-08-16 21:42:11 +03:00
ddadmin 8d54a3caba docs(orders): уточнён формат слепка и временные поля
- Зачем:
  - аудит источника нельзя смешивать с бизнес-временем и `_load_ts` ClickHouse.
- Что:
  - зафиксированы JSON-контракт слепка, порядок ключей и намеренное различие `Array(Float64)` и `Decimal`.
  - разведены бизнес-время, аудит источника и загрузка; уточнены слой ODS, `snapshot_date` и технический ключ партиции.
  - добавлены термины словаря и исследование точного миллисекундного формата с проверками ClickHouse.
- Проверка:
  - выполнен `git diff --cached --check`.
2026-08-16 17:56:46 +03:00
ddadminandClaude Opus 5 c9b11224d2 refactor(ruff): линтер стенда говорит на языке дагов, а не младшего образа
- Зачем:
  - target-version по младшему образу (Superset, 3.10) занижал проверку для дагов: они бегут на 3.13, а ruff предлагал им идиомы старее их рантайма. На учебном стенде это вывернуто наизнанку — менти читает и правит именно даги.
  - защита от Superset была верна не по устройству, а по сегодняшнему содержимому одного файла настройки.
- Что:
  - target-version в корневом ruff.toml поднят до py313, комментарий переписан.
  - две находки UP017 в дагах починены: datetime.timezone.utc заменён на datetime.UTC.
  - из карты проверок убран пункт про разную строгость дверей — с равными версиями он потерял предмет.
- Проверка:
  - make lint; make config-test — зелёные.
  - make -C generator lint — зелёный; исходники генератора не менялись.
  - находок ruff в дагах теперь ровно четыре, как обещал тикет: два переформата и два UP017.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:58:48 +03:00
ddadminandClaude Opus 5 e4e5688775 refactor(make): правки по холодному ревью реализации
- Зачем:
  - половина критерия приёмки стояла не там, где решено: предупреждение о ручном равенстве версий ruff адресовано тому, кто правит лок в generator/, а лежало в корневом Makefile.
  - довод «генератор — отдельная сущность» был выписан трижды почти дословно.
- Что:
  - равенство версий и охват корневой цели названы в карте проверок; три примечания к таблице собраны списком.
  - названа цена занижения target-version: даги бегут на 3.13 и модернизаций не получают.
  - шапка generator/Makefile вырезана, корневая сжата до строки, объяснение в ruff.toml укорочено.
  - формулировки в AGENTS.md и README поправлены.
- Проверка:
  - make lint; make config-test — зелёные.
  - make -C generator lint; typecheck — зелёные; исходники генератора не менялись.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:49:54 +03:00
ddadminandClaude Opus 5 6a708cd5aa refactor(make): корень — про стенд, генератор — за своей дверью
- Зачем:
  - корневые цели смешивали два уровня: пять из шестнадцати начинались с cd generator.
  - цель, названная общерепозиторной, охватывала 31 файл Python из 34: даги и Superset не видел ни линт, ни типы.
- Что:
  - lint, typecheck, test, docs и inventory переехали в новый generator/Makefile.
  - корневой lint заведён по коду стенда — dags и infra/superset — с явными путями и закреплённой версией ruff.
  - заведён корневой ruff.toml: тот же список правил, target-version по младшему Python в образах стенда.
  - цели корня сгруппированы по использованию, осталось двенадцать.
  - два файла дагов переформатированы под новую проверку.
  - карта проверок, оба README, спека генератора и AGENTS.md приведены к двум дверям.
- Проверка:
  - make lint; make config-test — зелёные.
  - make -C generator lint; typecheck; test — зелёные, 407 тестов.
  - ruff check --show-files: из корня ровно три файла стенда, из generator/ — только его.
  - цена корневого lint замерена (0,4 с) и вписана в карту проверок.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 21:31:07 +03:00
ddadminandClaude Opus 5 76405a06ee docs(storage): у Date пояса нет, и по нему легко промахнуться
- Зачем:
  - фраза «Date не участвует вовсе» выводила тип из-под общего правила:
    про объявление это правда, про употребление — нет. Считая время по
    EventDate без имени пояса, легко получить часы вне диапазона.
- Что:
  - фраза заменена на две: Date хранит только номер дня, пояс при счёте
    времени называют руками, промах виден по часам за границами 0–23.
- Проверка:
  - замерено на стенде: без имени пояса часы от начала суток идут -4…19,
    отрицательных 15 843 события; с названным поясом — 0…23

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 20:15:56 +03:00
ddadminandClaude Opus 5 359570ec65 docs(63): правки по двум холодным ревью реализации
- Зачем:
  - линия дефектов нашла три неверных утверждения и мёртвый замер, линия
    уместности — три пересказа уже сказанного.
- Что:
  - «тип колонки не решает, какое число ляжет» сужено до правды: разбор
    отдаёт готовое число, а пояс приёмника решал бы судьбу строки.
  - замер до правки типов помечен как неповторяемый на нынешнем стенде.
  - правило о поясе сервера привязано к местам, где линза что-то решает:
    матвью приёма пояс не называет, и это не нарушение.
  - убраны: пересказ механики в ADR 0005, четыре строки учебного
    комментария, утверждение о порядке файлов и «секунды от начала эпохи»
    у миллисекундной метки.
- Проверка:
  - make lint, make typecheck, make test (408 тестов)
  - make clean && make up && make check-clickhouse — 9 из 9

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 19:44:53 +03:00
ddadminandClaude Opus 5 d67e697821 feat(ddl): пояс назван явно — в типах колонок и в разборе строки
- Зачем:
  - конвенция #63 записана, а код её не достиг: колонки времени стояли без
    пояса, и сходилось всё лишь потому, что пояс сервера — UTC.
- Что:
  - UTCEventTime объявлен DateTime('UTC'), служебные метки _load_ts и
    kafka_timestamp — DateTime64(3, 'UTC') в STG и ODS.
  - parseDateTimeOrNull получил третьим аргументом 'UTC': маска сверяет
    суффикс Z как букву, зоны из строки не берёт вовсе.
  - контракт схемы и описание выгрузки несут тип с поясом; имя пояса
    Europe/Samara встало рядом со смещением в world.py, сходимость сверяет
    тест.
  - учебный комментарий о линзе — у первой колонки с явным поясом.
- Проверка:
  - make lint, make typecheck, make test (408 тестов)
  - make clean && make up && make check-clickhouse — 9 из 9
  - замер тикета повторён: под session_timezone='Europe/Samara' колонка
    показана 2026-05-31 23:37:00, как и без настроек

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 19:30:50 +03:00
ddadmin e6f300a66e docs(storage): правки конвенции по двум холодным ревью
- Зачем:
  - раздел «Часовые пояса» прошёл два холодных ревью — по дефектам и по
    уместности. Первое поймало ложный замер и три расхождения с живым
    стендом, второе — материал не своей зоны и дубли (#63).
- Что:
  - замер «расхождение живёт по HTTP» отозван: мерил toString(UTCEventTime)
    в родном клиенте против голой колонки по HTTP, а это разные вещи.
    Перемерено — клиенты ведут себя одинаково; записан верный факт: вывод
    колонки идёт по поясу сессии, функция — по поясу типа.
  - «по поясу сервера» заменено на «по поясу сессии, а тот по умолчанию
    серверный» — в разделе и в ADR 0005; утверждение в ледгере переписано
    под измеренный раскол вывода и типа.
  - PARTITION BY toDate(_load_ts) больше не выдаётся за уже соблюдённое
    правило: _load_ts сегодня DateTime64(3) без пояса.
  - абзац ADR 0005 больше не спорит с цитатой вызова строкой выше.
  - вырезано: веер отклонённых вариантов под заголовком (живые отказы
    разложены прозой по своим абзацам, как принято в этом документе),
    ссылка на несуществующую связку в world.py, осиротевшая строка про
    Grafana, абзац про пояс показа — он уехал комментарием в #63.
- Проверка:
  - make lint
  - замеры повторены на живом стенде 8 августа 2026 года
  - DDL к конвенции по-прежнему не приведён: документы описывают цель
2026-08-08 19:05:39 +03:00
ddadmin a93a3bf3a3 docs(storage): конвенция часовых поясов принята и записана
- Зачем:
  - пояс в стенде нигде не назван: числа верны только потому, что сервер
    ClickHouse стоит в UTC, а правило понадобится в dds и витринах —
    воронки, удержание, «покупки по дням» (#63).
- Что:
  - раздел «Часовые пояса»: пояс — линза, называется в типе колонки либо в
    вызове; какая именно — решает слой (ODS на языке выгрузки, DDS и витрины
    на языке бизнеса); день берётся из EventDate.
  - названы оба перехода, где пояс выбирается, включая разбор строки в
    матвью — он берёт пояс у сервера и тип колонки этого не чинит.
  - отвергнутые варианты прозой: умолчание сервера, TZ серверу, ODS в поясе
    счётчика, хранение местного времени.
  - три замера ушли в «Что проверено», сверка с документацией — от 8 августа.
- Проверка:
  - make lint
  - DDL к конвенции ещё не приведён: документ описывает цель, код идёт
    следом тем же тикетом.
2026-08-08 18:36:29 +03:00
ddadmin 6f5f76fafb docs(stage-2): приёмка этапа 2 — снятые замеры и решение по дублям Kafka
Зачем: этап 2 принят прогоном с нуля, и два его следа должны остаться в
доках — иначе решение «не проверяем» станет забытым долгом, а цена цели
разойдётся с замером.

Что:
- в спеке v2 раздел 11 больше не держит Kafka Engine на двух нодах: дубли и
  раскладку партиций между прогонами решено не проверять — дубль возможен по
  устройству движка, окно разобрано в доке хранилища, в ODS его схлопывает
  ReplacingMergeTree. Половина, которую показал #37, названа;
- в карте проверок цена check-services 44 с -> 59 с и абзац с замерами
  приёмки: clean+up 2 м 58 с, смоук 9 с, check-clickhouse 8 с, test 71 с.
  Причина подорожания не выдумывается — названа неизвестной.

Проверка: make clean && make up, затем make smoke, make check-clickhouse,
make check-services, make lint, make typecheck, make test — всё зелёное
7 августа 2026 года.
2026-08-07 20:14:35 +03:00
ddadmin c696fce40b fix(stand): находки ревью — диагноз не утверждает причину, комментарии не врут
Зачем
Холодное ревью нашло три места, где написанное сильнее сделанного.

Что
- Непустая таблица брака больше не выдаётся за доказательство сломанного
  разбора. Модельного дня у брака нет, обрамить его нечем, и строки прежних
  уроков лежат в нём месяц: после первого же урока с мусором проверка
  давала бы неверный диагноз навсегда. Теперь она даёт признак, по которому
  причину отличают, — сошлась недостача с числом брака или нет.
- Комментарий у world-init обещал, что расхождение числа дней с
  STARTING_DAYS поймают счётчики. Это неправда в одну сторону: лишний день
  ложится за рамкой дат описи. Обещание убрано, дыра названа.
- Довод «даг next_day этапа 5 продолжит ось» опирался на несуществующий
  этап; заменён настоящей причиной — заливка замыкает цепь разовых служб.
- Потолок ожидания 300 с получил обоснование замером с кратностью, а сам
  скрипт — честную оговорку: его обещание работает на пустом стенде, на
  живом ждать нечего.
- Третья, пропущенная ссылка на снятый порог скорости дня убрана из спеки.
- Даты замеров в карте целей разведены: #42 менял три цели, а не шесть.

Проверка
Обе ветви диагноза сняты заново на живом стенде: без брака — «не доехали»,
с браком — признак различения. Стенд восстановлен, все 9 проверок зелёные,
брака 0. make lint, typecheck, config-test, test (407 тестов) зелёные.

Ссылка: #42
2026-08-07 18:47:13 +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
ddadminandClaude Opus 5 30a1e9e567 docs(testing): корректность процессов — в даги DQ, не в цели make
- Зачем:
  - находка «это стоило бы проверять регулярно» решалась заново в каждом
    тикете и каждый раз тянулась в make check-clickhouse. У неё есть
    назначенный дом: даги качества данных, которые придут со следующими
    уровнями хранилища.
- Что:
  - добавлен раздел «Корректность процессов живёт в дагах DQ, а не в целях
    make»: цели make отвечают «стенд собран», свойства данных — работа дага.
  - назван фильтр: про полноту дня, свежесть слоя, сходимость витрины с
    источником — это даг, а не цель.
  - названа учебная сторона: даг идёт по расписанию, пишет историю проверок
    и разбирается как обычная задача Airflow — так качество данных устроено
    в бою.
- Проверка:
  - make config-test

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:39:49 +03:00
ddadminandClaude Opus 5 a534f3cc94 fix(ods): метка времени разбирается по названному формату, а не best-effort
Зачем: parseDateTimeBestEffort на непонятной строке не краснеет, а достраивает
недостающее — обрезанное «20:00:21» становится первым января текущего года.
Такое сообщение проходило строгий приём с тихо неверным временем, то есть с
той самой порчей, ради которой класс key_field_unparsed и заведён.

Что:
- В обеих матвью разбор метки идёт parseDateTimeOrNull по формату
  '%Y-%m-%dT%H:%i:%SZ'. Форма на проводе одна и каноническая, поэтому широта
  best-effort не нужна вовсе, а платится за неё отключённой проверкой.
- Замеры в ADR 0005: три записи, которые best-effort достраивает; проверка,
  что настройка cast_string_to_date_time_mode не спасает JSONExtract; сверка
  на настоящих данных — по всем 101 252 строкам сырья модельного дня точный
  формат разобрал метку у каждой и ни на одной не разошёлся с best-effort.
- Записано наблюдение стенда: пересозданная на живом чтеце матвью пропускает
  ближайшее сообщение мимо ODS, через минуту то же сообщение разбирается.
  Воспроизведено дважды; на нём я сам споткнулся при проверке этой правки.

Проверка: опыт строгого приёма прогнан заново — три сообщения дали событие и
два key_field_unparsed, включая обрезанную метку, которая раньше проходила
годной. DDL применяется на живом кластере.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:48:37 +03:00