Событийная сторона: потеря и дубль purchase (классы B и D), счетчики описи #184

Closed
opened 2026-08-28 18:42:06 +03:00 by ddmitry · 1 comment
Owner

Part of #8.

Цель

Генератор начинает портить событийный поток: терять purchase (класс B) и
задваивать его (класс D). Ветки lost_event и duplicate_event, заранее
построенные в витрине сверки на этапе 4, оживают сами; опись считает новые
классы, проверка classes_vs_inventory их сверяет. Учебный результат: два
урока, ради которых правило «деньги — по бэкенду» перестает быть догмой, —
недосчет трекера (событие не доехало) и пересчет (наивная сумма удваивает
выручку, а дедупликация движка бизнес-дубль не съедает и не должна).

Что войдет

  • Имя подпотока. Позиция 3 дерева сидов получает имя BEACONS вместо
    мертвого LATECOMERS: компонент называет часть мира (правило fate.md), а
    часть мира здесь — маячки счетчика, которыми события уезжают в трекер:
    потеря — маячок не долетел, дубль — отправлен дважды. Слово «доставка» не
    годится: delivery в генераторе и DDL уже занято деньгами за доставку
    заказа (Orders.delivery, DELIVERY_KOPECKS_WEIGHTS). Имя фиксируется
    этой постановкой; несогласие — вопросом в отчете, не молчаливой заменой.
    Правится: seeds.py, seeds.md, orders/README.md, fate.md — тем же
    PR.
  • Механика B — точечная потеря. Строка purchase выбрасывается из
    выдачи событий, когда номер уже присвоен (ADR 0017) и заказная сторона
    покупку уже прочитала: шов — после _purchases, читающего сотканные
    строки, не внутри _stream — иначе вместе с событием пропал бы и заказ.
    Просмотр /confirmation остается. Ориентир доли — константа мира ~3%;
    точная калибровка — этап 7.
  • Механика D — сюжетный дубль. Обновление /confirmation шлет пару:
    второй просмотр и второй purchase — новый WatchID, тот же
    purchaseID, суммы и позиции один в один. Вставка — тем же швом после
    _purchases: дубль не рождает второго заказа. Задержка — секунды-минуты,
    короче таймаута визита: VisitID тот же. Условие допуска из fate.md:
    дубль случается только там, где до следующего визита куки остается запас
    сверх таймаута плюс задержка дубля
    — иначе дубль съедает паузу, сборка
    сессий по таймауту склеивает два визита и расходится с VisitID,
    ломается эталон, ради которого VisitID в потоке лежит. Условие
    отсеивает немногих: у большинства покупок следующего визита в тот день
    нет вовсе, допущенных в мире ~96%. Доля ~2% считается от допущенных.
    Полночь режет пару по общему правилу подтверждения. Дубль решается
    только у выживших покупок.
  • Форма бросков — по правилу fate.md: на полную длину дня, из подпотока
    BEACONS.
  • Гарантия моста. Назначенные планом покупки (assigned_order) не
    теряются — иначе кука выпадает из карты идентичностей и лаба склейки не
    воспроизводится. Признак назначения сейчас умирает внутри day.stream и
    до сотканных строк не доезжает — до места порчи его нужно довести
    каналом по образцу того, как в weave уже едет person; форму выбирает
    реализация. Инвариант закрывается тестом; дубль мосту не вредит и
    назначенным разрешен.
  • Канал исходов до описи. Сейчас inventory._order_class_counts видит
    только today.orders и события дня — узнать, какие заказы задеты потерей
    и дублем, ему неоткуда. Исходы событийной стороны доезжают до описи полем
    на структурах дня — по образцу того, как опись уже читает заказную
    сторону; точную форму выбирает реализация, требование одно: опись считает
    по итоговой судьбе заказа после приоритета cancelled > lost_event > duplicate_event > amount_delta > match.
  • Счетчики и проверка. В data/world-inventory.json у дней закрытого
    окна появляются lost_event и duplicate_event; опись пересобирается.
    Фильтр sql/dq/classes_vs_inventory.sql расширяется обоими классами тем
    же PR — иначе проверка сравнивает неполные наборы и краснеет между
    правками.
  • docs/architecture/dm.md обновить тем же PR: фраза «Проверяются
    match, cancelled и amount_delta» перестает быть правдой вместе с
    фильтром. inventory.md классы не перечисляет («число заказов каждого
    класса») — там править нечего.

Границы

  • Заказную сторону не трогать: байты слепков не меняются — наблюдаемо по
    неизменным хешам snapshots в описи. Хеши дней изменятся — это ожидаемо,
    событийная сторона двигает именно их.
  • Опоздания — соседний тикет этапа.
  • SQL витрины сверки (purchase_vs_orders_rebuild.sql,
    purchase_vs_orders_vs_dds.sql) не менять: ветки построены на этапе 4 и
    оживают сами. Единственная SQL-правка — фильтр classes_vs_inventory.
  • Доли точно не калибровать, проверок вида «потерь от 2 до 4 процентов» не
    заводить — калибровка и точные числа уходят этапу 7.

Кому что

Механика, канал исходов, счет и тесты — Codex целиком по брифу: проект
принят в fate.md, работа техническая. Вычитка правок прозы (fate.md,
seeds.md, dm.md) — вкусовой модели.

Сначала прочитать

  • docs/architecture/orders/fate.md, раздел «Классы расхождений и
    опоздание» — это спека тикета; там же «Два подпотока дня» и правило формы
    бросков.
  • Мастер-спека, раздел 4 — классы и приоритет.
  • generator/src/clickstream_generator/seeds.py (позиция 3),
    commerce.py (_order_events, _seal, _purchases, weave),
    day.py (stream — как person доезжает до weave; интервалы
    визитов), inventory.py (_order_class_counts), plan.py
    (assigned_order).
  • docs/adr/0017-order-numbering.md — потеря не перенумеровывает соседей.
  • dags/dq_check.py (build_classes_vs_inventory) и
    sql/dq/classes_vs_inventory.sql.
  • Линт и тесты — make lint, make typecheck, make test из generator/.

Проверка

Опись пересобирается make inventory из generator/; «изменились ли
хеши» читается по git diff data/world-inventory.json.

  • make lint, make typecheck, make test в generator/ зеленые.
  • Хеши snapshots в описи не изменились; хеши дней пересобраны.
  • У дней закрытого окна в описи есть все пять классов; сумма классов
    дня равна числу заказов дня.
  • Тест гарантии моста: ни одна назначенная планом покупка не потеряна.
  • Тест дубля: тот же VisitID и purchaseID, новый WatchID, пара
    «просмотр + покупка», задержка внутри таймаута визита и запаса куки.
  • На стенде: classes_vs_inventory зеленая; dm.purchase_vs_orders_v
    показывает lost_event и duplicate_event; счетная сверка сессий с
    эталоном VisitID (sessions_vs_reference) — по-прежнему нулевое
    расхождение.
Part of #8. ## Цель Генератор начинает портить событийный поток: терять purchase (класс B) и задваивать его (класс D). Ветки `lost_event` и `duplicate_event`, заранее построенные в витрине сверки на этапе 4, оживают сами; опись считает новые классы, проверка `classes_vs_inventory` их сверяет. Учебный результат: два урока, ради которых правило «деньги — по бэкенду» перестает быть догмой, — недосчет трекера (событие не доехало) и пересчет (наивная сумма удваивает выручку, а дедупликация движка бизнес-дубль не съедает и не должна). ## Что войдет - **Имя подпотока.** Позиция 3 дерева сидов получает имя `BEACONS` вместо мертвого `LATECOMERS`: компонент называет часть мира (правило fate.md), а часть мира здесь — маячки счетчика, которыми события уезжают в трекер: потеря — маячок не долетел, дубль — отправлен дважды. Слово «доставка» не годится: `delivery` в генераторе и DDL уже занято деньгами за доставку заказа (`Orders.delivery`, `DELIVERY_KOPECKS_WEIGHTS`). Имя фиксируется этой постановкой; несогласие — вопросом в отчете, не молчаливой заменой. Правится: `seeds.py`, `seeds.md`, `orders/README.md`, `fate.md` — тем же PR. - **Механика B — точечная потеря.** Строка `purchase` выбрасывается из выдачи событий, когда номер уже присвоен (ADR 0017) и заказная сторона покупку уже прочитала: шов — после `_purchases`, читающего сотканные строки, не внутри `_stream` — иначе вместе с событием пропал бы и заказ. Просмотр `/confirmation` остается. Ориентир доли — константа мира ~3%; точная калибровка — этап 7. - **Механика D — сюжетный дубль.** Обновление `/confirmation` шлет пару: второй просмотр и второй `purchase` — новый `WatchID`, тот же `purchaseID`, суммы и позиции один в один. Вставка — тем же швом после `_purchases`: дубль не рождает второго заказа. Задержка — секунды-минуты, короче таймаута визита: `VisitID` тот же. **Условие допуска из fate.md: дубль случается только там, где до следующего визита куки остается запас сверх таймаута плюс задержка дубля** — иначе дубль съедает паузу, сборка сессий по таймауту склеивает два визита и расходится с `VisitID`, ломается эталон, ради которого `VisitID` в потоке лежит. Условие отсеивает немногих: у большинства покупок следующего визита в тот день нет вовсе, допущенных в мире ~96%. Доля ~2% считается от допущенных. Полночь режет пару по общему правилу подтверждения. Дубль решается только у выживших покупок. - **Форма бросков** — по правилу fate.md: на полную длину дня, из подпотока `BEACONS`. - **Гарантия моста.** Назначенные планом покупки (`assigned_order`) не теряются — иначе кука выпадает из карты идентичностей и лаба склейки не воспроизводится. Признак назначения сейчас умирает внутри `day.stream` и до сотканных строк не доезжает — до места порчи его нужно довести каналом по образцу того, как в `weave` уже едет `person`; форму выбирает реализация. Инвариант закрывается тестом; дубль мосту не вредит и назначенным разрешен. - **Канал исходов до описи.** Сейчас `inventory._order_class_counts` видит только `today.orders` и события дня — узнать, какие заказы задеты потерей и дублем, ему неоткуда. Исходы событийной стороны доезжают до описи полем на структурах дня — по образцу того, как опись уже читает заказную сторону; точную форму выбирает реализация, требование одно: опись считает по итоговой судьбе заказа после приоритета `cancelled > lost_event > duplicate_event > amount_delta > match`. - **Счетчики и проверка.** В `data/world-inventory.json` у дней закрытого окна появляются `lost_event` и `duplicate_event`; опись пересобирается. Фильтр `sql/dq/classes_vs_inventory.sql` расширяется обоими классами тем же PR — иначе проверка сравнивает неполные наборы и краснеет между правками. - `docs/architecture/dm.md` обновить тем же PR: фраза «Проверяются `match`, `cancelled` и `amount_delta`» перестает быть правдой вместе с фильтром. `inventory.md` классы не перечисляет («число заказов каждого класса») — там править нечего. ## Границы - Заказную сторону не трогать: байты слепков не меняются — наблюдаемо по неизменным хешам `snapshots` в описи. Хеши дней изменятся — это ожидаемо, событийная сторона двигает именно их. - Опоздания — соседний тикет этапа. - SQL витрины сверки (`purchase_vs_orders_rebuild.sql`, `purchase_vs_orders_vs_dds.sql`) не менять: ветки построены на этапе 4 и оживают сами. Единственная SQL-правка — фильтр `classes_vs_inventory`. - Доли точно не калибровать, проверок вида «потерь от 2 до 4 процентов» не заводить — калибровка и точные числа уходят этапу 7. ## Кому что Механика, канал исходов, счет и тесты — Codex целиком по брифу: проект принят в fate.md, работа техническая. Вычитка правок прозы (`fate.md`, `seeds.md`, `dm.md`) — вкусовой модели. ## Сначала прочитать - `docs/architecture/orders/fate.md`, раздел «Классы расхождений и опоздание» — это спека тикета; там же «Два подпотока дня» и правило формы бросков. - Мастер-спека, раздел 4 — классы и приоритет. - `generator/src/clickstream_generator/seeds.py` (позиция 3), `commerce.py` (`_order_events`, `_seal`, `_purchases`, `weave`), `day.py` (`stream` — как `person` доезжает до `weave`; интервалы визитов), `inventory.py` (`_order_class_counts`), `plan.py` (`assigned_order`). - `docs/adr/0017-order-numbering.md` — потеря не перенумеровывает соседей. - `dags/dq_check.py` (`build_classes_vs_inventory`) и `sql/dq/classes_vs_inventory.sql`. - Линт и тесты — `make lint`, `make typecheck`, `make test` из `generator/`. ## Проверка Опись пересобирается `make inventory` из `generator/`; «изменились ли хеши» читается по `git diff data/world-inventory.json`. - [x] `make lint`, `make typecheck`, `make test` в `generator/` зеленые. - [x] Хеши `snapshots` в описи не изменились; хеши дней пересобраны. - [x] У дней закрытого окна в описи есть все пять классов; сумма классов дня равна числу заказов дня. - [x] Тест гарантии моста: ни одна назначенная планом покупка не потеряна. - [x] Тест дубля: тот же `VisitID` и `purchaseID`, новый `WatchID`, пара «просмотр + покупка», задержка внутри таймаута визита и запаса куки. - [x] На стенде: `classes_vs_inventory` зеленая; `dm.purchase_vs_orders_v` показывает `lost_event` и `duplicate_event`; счетная сверка сессий с эталоном `VisitID` (`sessions_vs_reference`) — по-прежнему нулевое расхождение.
ddmitry added the ready-for-agent label 2026-08-28 18:42:34 +03:00
Author
Owner
  • Реализация: ветка 184-beacons, коммит c28f724; 19 файлов, 433 теста зеленые.
  • Ревью: две слепые линии (задача/вкус и код), обе APPROVED_WITH_MINORS, 0 блокеров и мажоров; саморевью исполнителя нашло 1 находку, исправлено 1.
  • Триаж: 8 FIXED, 2 REJECTED
    • CODE-1 — FIXED — классы с нулевым числом заказов теперь по умолчанию нулевые, когда SQL опускает пустую группу.
    • CODE-2 — REJECTED — сканирование стоит 0.049 сек, обход добавит проброс выбора без изменения бросков; существующие тесты разрывов сессии уже доказывают нужное поведение.
    • CODE-3 — FIXED — убрана устаревшая формулировка про «будущий класс».
    • CODE-4 — FIXED — документация допуска теперь требует и таймаут, и задержку дубля, как в коде и постановке.
    • CODE-5 — FIXED — восстановлена пустая строка после заголовка, поправлен перенос строк.
    • CODE-6 — REJECTED — pageview устойчивое значение провода с двумя целевыми применениями в проде; общая константа добавит связанность без усиления контракта, что уже покрыто тестами на дубль и страницу.
    • TASK-1 — FIXED — устаревшие проценты заменены на фактический инвариант, измеренный после порчи.
    • TASK-2 — FIXED — восстановлена пустая строка после «Открытые решения».
    • TASK-3 — FIXED — переформатированы только задетые абзацы в README и fate.
    • TASK-4 — FIXED — убраны лишние проверки процентов, контракт задержки/таймаута сохранен.
  • Приемка на стенде: make smoke 21/21, check-clickhouse 10/10, check-services 7/7; в dq_summary classes_vs_inventory и sessions_vs_reference pass; витрина сверки: lost_event 30, duplicate_event 28, amount_delta 22, cancelled 67, awaiting_order 253, match 1515.
  • Примечание: единичный перезапуск airflow-dag-processor на стенде — OOM хоста в 06:21, до работ по тикету; к #184 не относится.
- Реализация: ветка 184-beacons, коммит c28f724; 19 файлов, 433 теста зеленые. - Ревью: две слепые линии (задача/вкус и код), обе APPROVED_WITH_MINORS, 0 блокеров и мажоров; саморевью исполнителя нашло 1 находку, исправлено 1. - Триаж: 8 FIXED, 2 REJECTED - CODE-1 — FIXED — классы с нулевым числом заказов теперь по умолчанию нулевые, когда SQL опускает пустую группу. - CODE-2 — REJECTED — сканирование стоит 0.049 сек, обход добавит проброс выбора без изменения бросков; существующие тесты разрывов сессии уже доказывают нужное поведение. - CODE-3 — FIXED — убрана устаревшая формулировка про «будущий класс». - CODE-4 — FIXED — документация допуска теперь требует и таймаут, и задержку дубля, как в коде и постановке. - CODE-5 — FIXED — восстановлена пустая строка после заголовка, поправлен перенос строк. - CODE-6 — REJECTED — `pageview` устойчивое значение провода с двумя целевыми применениями в проде; общая константа добавит связанность без усиления контракта, что уже покрыто тестами на дубль и страницу. - TASK-1 — FIXED — устаревшие проценты заменены на фактический инвариант, измеренный после порчи. - TASK-2 — FIXED — восстановлена пустая строка после «Открытые решения». - TASK-3 — FIXED — переформатированы только задетые абзацы в README и fate. - TASK-4 — FIXED — убраны лишние проверки процентов, контракт задержки/таймаута сохранен. - Приемка на стенде: make smoke 21/21, check-clickhouse 10/10, check-services 7/7; в dq_summary classes_vs_inventory и sessions_vs_reference pass; витрина сверки: lost_event 30, duplicate_event 28, amount_delta 22, cancelled 67, awaiting_order 253, match 1515. - Примечание: единичный перезапуск airflow-dag-processor на стенде — OOM хоста в 06:21, до работ по тикету; к #184 не относится.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ddmitry/clickstream-data-platform#184