Генератор начинает портить событийный поток: терять 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`) — по-прежнему нулевое
расхождение.
CODE-6 — REJECTED — pageview устойчивое значение провода с двумя целевыми применениями в проде; общая константа добавит связанность без усиления контракта, что уже покрыто тестами на дубль и страницу.
TASK-1 — FIXED — устаревшие проценты заменены на фактический инвариант, измеренный после порчи.
TASK-2 — FIXED — восстановлена пустая строка после «Открытые решения».
TASK-3 — FIXED — переформатированы только задетые абзацы в README и fate.
Приемка на стенде: 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 не относится.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #8.
Цель
Генератор начинает портить событийный поток: терять purchase (класс B) и
задваивать его (класс D). Ветки
lost_eventиduplicate_event, заранеепостроенные в витрине сверки на этапе 4, оживают сами; опись считает новые
классы, проверка
classes_vs_inventoryих сверяет. Учебный результат: дваурока, ради которых правило «деньги — по бэкенду» перестает быть догмой, —
недосчет трекера (событие не доехало) и пересчет (наивная сумма удваивает
выручку, а дедупликация движка бизнес-дубль не съедает и не должна).
Что войдет
BEACONSвместомертвого
LATECOMERS: компонент называет часть мира (правило fate.md), ачасть мира здесь — маячки счетчика, которыми события уезжают в трекер:
потеря — маячок не долетел, дубль — отправлен дважды. Слово «доставка» не
годится:
deliveryв генераторе и DDL уже занято деньгами за доставкузаказа (
Orders.delivery,DELIVERY_KOPECKS_WEIGHTS). Имя фиксируетсяэтой постановкой; несогласие — вопросом в отчете, не молчаливой заменой.
Правится:
seeds.py,seeds.md,orders/README.md,fate.md— тем жеPR.
purchaseвыбрасывается извыдачи событий, когда номер уже присвоен (ADR 0017) и заказная сторона
покупку уже прочитала: шов — после
_purchases, читающего сотканныестроки, не внутри
_stream— иначе вместе с событием пропал бы и заказ.Просмотр
/confirmationостается. Ориентир доли — константа мира ~3%;точная калибровка — этап 7.
/confirmationшлет пару:второй просмотр и второй
purchase— новыйWatchID, тот жеpurchaseID, суммы и позиции один в один. Вставка — тем же швом после_purchases: дубль не рождает второго заказа. Задержка — секунды-минуты,короче таймаута визита:
VisitIDтот же. Условие допуска из fate.md:дубль случается только там, где до следующего визита куки остается запас
сверх таймаута плюс задержка дубля — иначе дубль съедает паузу, сборка
сессий по таймауту склеивает два визита и расходится с
VisitID,ломается эталон, ради которого
VisitIDв потоке лежит. Условиеотсеивает немногих: у большинства покупок следующего визита в тот день
нет вовсе, допущенных в мире ~96%. Доля ~2% считается от допущенных.
Полночь режет пару по общему правилу подтверждения. Дубль решается
только у выживших покупок.
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в описи. Хеши дней изменятся — это ожидаемо,событийная сторона двигает именно их.
purchase_vs_orders_rebuild.sql,purchase_vs_orders_vs_dds.sql) не менять: ветки построены на этапе 4 иоживают сами. Единственная SQL-правка — фильтр
classes_vs_inventory.заводить — калибровка и точные числа уходят этапу 7.
Кому что
Механика, канал исходов, счет и тесты — Codex целиком по брифу: проект
принят в fate.md, работа техническая. Вычитка правок прозы (
fate.md,seeds.md,dm.md) — вкусовой модели.Сначала прочитать
docs/architecture/orders/fate.md, раздел «Классы расхождений иопоздание» — это спека тикета; там же «Два подпотока дня» и правило формы
бросков.
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) — по-прежнему нулевоерасхождение.
pageviewустойчивое значение провода с двумя целевыми применениями в проде; общая константа добавит связанность без усиления контракта, что уже покрыто тестами на дубль и страницу.