Карта: проектирование заказов бэкенда (этап 3) #69

Open
opened 2026-08-07 20:43:43 +03:00 by ddmitry · 0 comments
Owner

Пункт назначения

Карта завершена, когда цельная спека второго источника — заказов бэкенда —
принята владельцем и лежит живым набором docs/architecture/orders/ в main, а этап 3 нарезан на
тикеты чек-листом в #5.

Карта несёт и исполнение маршрута, а не только решения по развилкам:
/brainstorm-with-docs по развилкам (сначала веер, потом конвергенция) →
/to-spec: спека файлом → приёмка владельцем → /to-tickets режет тикеты
этапа 3. Образец — карта #26, тем же порядком собрана спека генератора.

Заведена сразу после закрытия этапа 2, а не при входе в этап: пока карты нет,
второй источник живёт только разделами мастер-спеки и выглядит забытым. В коде
от него сегодня есть один каталог товаров — топик orders не создаётся, таблиц
заказа нет, генератор слепков не написан.

Заметки

  • Источник истины — мастер-спека docs/specs/2026-07-30-stand-v2-realism.md;
    сначала читать разделы 2 (заказы), 3 (каталог), 4 (сверка), 5 (мост к
    склейке), 7 (карта слоёв — там абзац про нерешённую развилку приёма) и
    9 (страховочные срезы: что применено, что в резерве).
  • Ещё до начала прочитать: docs/adr/0005-event-ingestion.md — чем обоснован
    приём для событий; docs/architecture/storage.md — служебные колонки,
    конвенция суффиксов, сроки хранения; docs/specs/2026-08-01-generator.md
    детерминизм, иерархия зёрен, правило порядка бросков.
  • Развилки вести через /brainstorm-with-docs с /grilling и
    /domain-modeling; отклонённые варианты записывать с доводами.
  • Расхождение решения с мастер-спекой вносится в неё тем же коммитом, что и
    спека второго источника.
  • Спорные API перед фиксацией сверять через MCP Context7 (правило AGENTS.md).
  • Вход извне карты: #63 — часовые пояса. Слепок несёт created_at,
    updated_at и snapshot_date; их семантика закреплена в формате. Устройство
    модели DDS решается, когда начинаем строить DDS.

Решения по пути

  • Приём заказов: нужен ли слепку слой сырья
    — пакетный забор: байтовый чтец без матвью, сырьё есть, в ods.order_snapshot
    пишет шаг Airflow заменой партиции; топик в одну партицию, чтец на ноде 1 без
    ON CLUSTER. Раздел 12 переписан на «поток против слепка», сенсор снят, даг
    переехал на этап 3. Целиком — docs/adr/0008-order-ingestion.md. Публикацию
    через замену партиции позднее отменил «Брак в слепке: куда уходит и по каким
    классам»
    :
    прямое чтение не задаёт границы полного слепка, а ODS принимает версии заказа.

  • Генератор слепков: где живёт и чем связан с событиями
    — заказ не порождается заново, а проецируется: торговая половина отдаёт заказы
    дня структурой, ни одного нового броска в COMMERCE. Команда snapshot того же
    пакета: два источника — два запуска. Окно K = 7 собирается переигровкой семи
    дней; слепок снимается на границе суток, а отправляется следующим прогоном —
    ночная выгрузка за вчера, отчего живой день перестаёт быть особым случаем.
    Случайность — свой компонент дня по дню рождения заказа, вся судьба решается при
    рождении; момент оплаты — бросок по таблице целых весов. Целиком — в резолюции
    тикета.

  • Расхождения A–D и опоздания: механика, доли и что обещано
    — расхождение это судьба заказа, решённая при рождении и уложенная в окно: два
    подпотока (заказная сторона и порча событийного потока), броски независимы и
    делаются на полную длину дня, приоритет мастер-спеки работает по-настоящему.
    Заказ на выходе из окна либо paid, либо cancelled — неоплаченный отменяют,
    «навсегда created» не возникает, нового значения mismatch_class не нужно.
    Судьба бросается исходом из таблицы долей и моментами из таблицы длиной 143 часа
    (самый узкий край окна); форма из #71 — вес за краем окна — этим отменена.
    Дельта суммы — вычеркнутая позиция, заказ приезжает урезанным во всех слепках;
    потеря точечная и не трогает назначенные планом покупки, а назначенные заказы
    вдобавок не опаздывают — иначе рвётся лаба склейки с обоих концов; дубль
    сюжетный и только там, где есть место до следующего визита куки;
    опоздание кончается на втором-третьем дне. Опись считает наблюдаемые классы
    после приоритета, единица счёта — заказ, и получает строку на слепок с хешем
    байтов. Целиком — в резолюции тикета.

  • Мост к склейке: user_id и двухкуковые пары
    — план владеет человеком и связью с куками: непрозрачный person_id
    воспроизводится последним броском когорты, заказ показывает его как user_id,
    а кликстрим остаётся анонимным; богатой модели аккаунта и отдельного реестра
    нет. Целиком — в резолюции тикета.

  • Место заказов в стартовом мире
    — стартовый мир отправляет семь слепков (дни 0…6): слепок дня 7 снят на
    границе суток и уедет первым ходом мира, поэтому у последнего прожитого дня
    клики есть, а заказов нет, и график выручки заваливается к правому краю,
    дозаполняясь по ходу мира — главный наблюдаемый эффект ночной выгрузки.
    Особого режима у стартового мира не заводится, генератору тикет не стоит
    ничего. Опись держит правило «только то, чего движение мира не меняет»:
    хеши дней и слепков плюс счётчики дней с закрытым окном (на стартовом мире
    один); хеш — растяжка, счётчик — опора, и счётчик кладётся только с названным
    читателем. Контрольные числа идентичности из мастер-спеки §5 и §8 не заводятся
    вовсе. Опись описывает мир, а не доставку: работа генератора кончается на
    Kafka. Целиком — в резолюции тикета.

  • Форма записи слепка на проводе
    — деньги передаются строками с двумя знаками, items — обычным JSON-массивом;
    created_at и updated_at — аудит источника в UTC до миллисекунд,
    snapshot_date — дата модельного среза. Один внешний orjson.dumps, без
    универсального слоя кодеков. Целиком — в резолюции тикета и
    docs/research/2026-08-16-order-snapshot-wire-format.md.

  • Брак в слепке: куда уходит и по каким классам
    — строка либо целиком проходит строгий контракт 11 верхних полей, либо уходит
    сырой в _errors с классом not_an_object, keyset_mismatch или
    field_invalid; два взаимодополняющих запроса читают один срез STG по
    _load_id. ODS принимает версии на ReplacingMergeTree(updated_at) по
    order_id; snapshot_date остаётся происхождением строки, точное текущее
    чтение требует FINAL. Подмена партиций из «Приёма заказов: нужен ли слепку
    слой сырья»

    этим отменена. Итог закреплён в ADR 0010
    и спецификации приёма.

  • Собрать спеку заказов из решений развилок
    — черновик цельной спеки второго источника лежит в
    docs/specs/2026-08-16-orders.md (коммит 815f500): решения #70–#74, #80,
    #81 в одной картине, расхождения с мастер-спекой, спекой генератора,
    ADR 0008 и CONTEXT.md внесены тем же коммитом. Впереди вычитание #84 и
    приёмка #88.

  • Вычитание: какая механика этапа 3 не окупает себя эффектом
    — вычитание слилось с переустройством формы: спека заказов и спека приёма
    превращены в живой набор docs/architecture/orders/ (индекс и девять файлов
    по частям устройства), датированные файлы удалены, форма закреплена
    ADR 0011. По итогам двух слепых холодных линий провод и приём сведены к
    указателям (правило порядка строк слепка выжило на месте), замеры зерна и
    повторы срезаны; списки отклонённых вариантов сохранены, машинерия
    детерминизма не тронута — её пересмотр был бы переоткрытием #71–#74.
    Холодная сверка миграции потерь не нашла. Целиком — в резолюции тикета.

Тикеты

Устройство второго источника после вычитания #84 живёт набором
docs/architecture/orders/ (приём — orders/ingestion.md). Приёмка
владельцем — #88, после неё этап 3 режется на тикеты (#89).

Блокировки — нативные зависимости Gitea, они же определяют фронтир. Если открыто несколько разблокированных и неназначенных тикетов, берётся первый по этому списку.

  • #70 Приём заказов: нужен ли слепку слой сырья
  • #71 Генератор слепков: где живёт и чем связан с событиями
  • #72 Расхождения A–D и опоздания: механика, доли и что обещано
  • #73 Мост к склейке: user_id и двухкуковые пары
  • #74 Место заказов в стартовом мире
  • #81 Форма записи слепка на проводе
  • #80 Брак в слепке: куда уходит и по каким классам
  • #87 Собрать спеку заказов из решений развилок
  • #84 Вычитание: какая механика этапа 3 не окупает себя эффектом
  • #85 Трассировка загрузки: нужен ли _load_id выше ODS
  • #86 Добавить каноническое чтение актуальных событий через ods.event_v
  • #88 Приёмка спеки заказов владельцем
  • #89 Нарезать тикеты этапа 3 по принятой спеке

Ещё не сформулировано

  • Нужен ли расхождениям собственный контур проверок данных и где он живёт:
    даг DQ, витрина dm.dq_summary или обе.

За границей карты

  • Решённое мастер-спекой заново не обсуждается: поля слепка, окно K = 7 как
    константа мира, три статуса, приоритет классов расхождений, правило
    «поведение и атрибуцию считаем по трекеру, деньги — по бэкенду».
  • Витрины и сама сверка purchase против заказов — этап 4 (#6).
  • Расхождения B и D как отдельный этап сверки — этап 6 (#8). Механику
    генератора для них решаем здесь, сверку по ним пишут там.
  • Пересборка эталонного мира целиком — этап 7 (#9).
  • Код: пишется тикетами, нарезанными после приёмки спеки.
  • Перевод мастер-спеки и спеки генератора в ту же живую форму — отдельное
    усилие вне этой карты (ADR 0011).
## Пункт назначения Карта завершена, когда цельная спека второго источника — заказов бэкенда — принята владельцем и лежит живым набором `docs/architecture/orders/` в main, а этап 3 нарезан на тикеты чек-листом в #5. Карта несёт и исполнение маршрута, а не только решения по развилкам: `/brainstorm-with-docs` по развилкам (сначала веер, потом конвергенция) → `/to-spec`: спека файлом → приёмка владельцем → `/to-tickets` режет тикеты этапа 3. Образец — карта #26, тем же порядком собрана спека генератора. Заведена сразу после закрытия этапа 2, а не при входе в этап: пока карты нет, второй источник живёт только разделами мастер-спеки и выглядит забытым. В коде от него сегодня есть один каталог товаров — топик `orders` не создаётся, таблиц заказа нет, генератор слепков не написан. ## Заметки - Источник истины — мастер-спека `docs/specs/2026-07-30-stand-v2-realism.md`; сначала читать разделы 2 (заказы), 3 (каталог), 4 (сверка), 5 (мост к склейке), 7 (карта слоёв — там абзац про нерешённую развилку приёма) и 9 (страховочные срезы: что применено, что в резерве). - Ещё до начала прочитать: `docs/adr/0005-event-ingestion.md` — чем обоснован приём для событий; `docs/architecture/storage.md` — служебные колонки, конвенция суффиксов, сроки хранения; `docs/specs/2026-08-01-generator.md` — детерминизм, иерархия зёрен, правило порядка бросков. - Развилки вести через `/brainstorm-with-docs` с `/grilling` и `/domain-modeling`; отклонённые варианты записывать с доводами. - Расхождение решения с мастер-спекой вносится в неё тем же коммитом, что и спека второго источника. - Спорные API перед фиксацией сверять через MCP Context7 (правило AGENTS.md). - Вход извне карты: #63 — часовые пояса. Слепок несёт `created_at`, `updated_at` и `snapshot_date`; их семантика закреплена в формате. Устройство модели DDS решается, когда начинаем строить DDS. ## Решения по пути <!-- по одной строке на закрытый тикет: суть ответа и ссылка --> - [Приём заказов: нужен ли слепку слой сырья](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/70) — пакетный забор: байтовый чтец без матвью, сырьё есть, в `ods.order_snapshot` пишет шаг Airflow заменой партиции; топик в одну партицию, чтец на ноде 1 без `ON CLUSTER`. Раздел 12 переписан на «поток против слепка», сенсор снят, даг переехал на этап 3. Целиком — `docs/adr/0008-order-ingestion.md`. Публикацию через замену партиции позднее отменил [«Брак в слепке: куда уходит и по каким классам»](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/80): прямое чтение не задаёт границы полного слепка, а ODS принимает версии заказа. - [Генератор слепков: где живёт и чем связан с событиями](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/71) — заказ не порождается заново, а проецируется: торговая половина отдаёт заказы дня структурой, ни одного нового броска в `COMMERCE`. Команда `snapshot` того же пакета: два источника — два запуска. Окно K = 7 собирается переигровкой семи дней; слепок снимается на границе суток, а отправляется следующим прогоном — ночная выгрузка за вчера, отчего живой день перестаёт быть особым случаем. Случайность — свой компонент дня по дню рождения заказа, вся судьба решается при рождении; момент оплаты — бросок по таблице целых весов. Целиком — в резолюции тикета. - [Расхождения A–D и опоздания: механика, доли и что обещано](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/72) — расхождение это судьба заказа, решённая при рождении и уложенная в окно: два подпотока (заказная сторона и порча событийного потока), броски независимы и делаются на полную длину дня, приоритет мастер-спеки работает по-настоящему. Заказ на выходе из окна либо `paid`, либо `cancelled` — неоплаченный отменяют, «навсегда `created`» не возникает, нового значения `mismatch_class` не нужно. Судьба бросается исходом из таблицы долей и моментами из таблицы длиной 143 часа (самый узкий край окна); форма из #71 — вес за краем окна — этим отменена. Дельта суммы — вычеркнутая позиция, заказ приезжает урезанным во всех слепках; потеря точечная и не трогает назначенные планом покупки, а назначенные заказы вдобавок не опаздывают — иначе рвётся лаба склейки с обоих концов; дубль сюжетный и только там, где есть место до следующего визита куки; опоздание кончается на втором-третьем дне. Опись считает наблюдаемые классы после приоритета, единица счёта — заказ, и получает строку на слепок с хешем байтов. Целиком — в резолюции тикета. - [Мост к склейке: user_id и двухкуковые пары](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/73) — план владеет человеком и связью с куками: непрозрачный `person_id` воспроизводится последним броском когорты, заказ показывает его как `user_id`, а кликстрим остаётся анонимным; богатой модели аккаунта и отдельного реестра нет. Целиком — в резолюции тикета. - [Место заказов в стартовом мире](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/74) — стартовый мир отправляет семь слепков (дни 0…6): слепок дня 7 снят на границе суток и уедет первым ходом мира, поэтому у последнего прожитого дня клики есть, а заказов нет, и график выручки заваливается к правому краю, дозаполняясь по ходу мира — главный наблюдаемый эффект ночной выгрузки. Особого режима у стартового мира не заводится, генератору тикет не стоит ничего. Опись держит правило «только то, чего движение мира не меняет»: хеши дней и слепков плюс счётчики дней с закрытым окном (на стартовом мире один); хеш — растяжка, счётчик — опора, и счётчик кладётся только с названным читателем. Контрольные числа идентичности из мастер-спеки §5 и §8 не заводятся вовсе. Опись описывает мир, а не доставку: работа генератора кончается на Kafka. Целиком — в резолюции тикета. - [Форма записи слепка на проводе](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/81) — деньги передаются строками с двумя знаками, `items` — обычным JSON-массивом; `created_at` и `updated_at` — аудит источника в UTC до миллисекунд, `snapshot_date` — дата модельного среза. Один внешний `orjson.dumps`, без универсального слоя кодеков. Целиком — в резолюции тикета и `docs/research/2026-08-16-order-snapshot-wire-format.md`. - [Брак в слепке: куда уходит и по каким классам](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/80) — строка либо целиком проходит строгий контракт 11 верхних полей, либо уходит сырой в `_errors` с классом `not_an_object`, `keyset_mismatch` или `field_invalid`; два взаимодополняющих запроса читают один срез STG по `_load_id`. ODS принимает версии на `ReplacingMergeTree(updated_at)` по `order_id`; `snapshot_date` остаётся происхождением строки, точное текущее чтение требует `FINAL`. Подмена партиций из [«Приёма заказов: нужен ли слепку слой сырья»](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/70) этим отменена. Итог закреплён в [ADR 0010](https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/adr/0010-order-versions-in-ods.md) и [спецификации приёма](https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/specs/2026-08-16-order-ingestion.md). - [Собрать спеку заказов из решений развилок](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/87) — черновик цельной спеки второго источника лежит в `docs/specs/2026-08-16-orders.md` (коммит 815f500): решения #70–#74, #80, #81 в одной картине, расхождения с мастер-спекой, спекой генератора, ADR 0008 и CONTEXT.md внесены тем же коммитом. Впереди вычитание #84 и приёмка #88. - [Вычитание: какая механика этапа 3 не окупает себя эффектом](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/84) — вычитание слилось с переустройством формы: спека заказов и спека приёма превращены в живой набор `docs/architecture/orders/` (индекс и девять файлов по частям устройства), датированные файлы удалены, форма закреплена ADR 0011. По итогам двух слепых холодных линий провод и приём сведены к указателям (правило порядка строк слепка выжило на месте), замеры зерна и повторы срезаны; списки отклонённых вариантов сохранены, машинерия детерминизма не тронута — её пересмотр был бы переоткрытием #71–#74. Холодная сверка миграции потерь не нашла. Целиком — в резолюции тикета. ## Тикеты Устройство второго источника после вычитания #84 живёт набором `docs/architecture/orders/` (приём — `orders/ingestion.md`). Приёмка владельцем — #88, после неё этап 3 режется на тикеты (#89). Блокировки — нативные зависимости Gitea, они же определяют фронтир. Если открыто несколько разблокированных и неназначенных тикетов, берётся первый по этому списку. - [x] #70 Приём заказов: нужен ли слепку слой сырья - [x] #71 Генератор слепков: где живёт и чем связан с событиями - [x] #72 Расхождения A–D и опоздания: механика, доли и что обещано - [x] #73 Мост к склейке: user_id и двухкуковые пары - [x] #74 Место заказов в стартовом мире - [x] #81 Форма записи слепка на проводе - [x] #80 Брак в слепке: куда уходит и по каким классам - [x] #87 Собрать спеку заказов из решений развилок - [x] #84 Вычитание: какая механика этапа 3 не окупает себя эффектом - [ ] #85 Трассировка загрузки: нужен ли `_load_id` выше ODS - [ ] #86 Добавить каноническое чтение актуальных событий через `ods.event_v` - [ ] #88 Приёмка спеки заказов владельцем - [ ] #89 Нарезать тикеты этапа 3 по принятой спеке ## Ещё не сформулировано - Нужен ли расхождениям собственный контур проверок данных и где он живёт: даг DQ, витрина `dm.dq_summary` или обе. ## За границей карты - **Решённое мастер-спекой заново не обсуждается**: поля слепка, окно K = 7 как константа мира, три статуса, приоритет классов расхождений, правило «поведение и атрибуцию считаем по трекеру, деньги — по бэкенду». - Витрины и сама сверка `purchase` против заказов — этап 4 (#6). - Расхождения B и D как отдельный этап сверки — этап 6 (#8). Механику генератора для них решаем здесь, сверку по ним пишут там. - Пересборка эталонного мира целиком — этап 7 (#9). - Код: пишется тикетами, нарезанными после приёмки спеки. - Перевод мастер-спеки и спеки генератора в ту же живую форму — отдельное усилие вне этой карты (ADR 0011).
ddmitry added the wayfinder:map label 2026-08-07 20:43:43 +03:00
ddmitry added this to the Базаовая реализация платформы project 2026-08-16 11:04:47 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ddmitry/clickstream-data-platform#69