fix(generator): исправлен расчёт скидки после складской дельты
- Зачем: - слепок D7 содержал отрицательный итог заказа и попадал в брак ODS. - Что: - скидка пересчитана от корзины после удаления отсутствующей позиции. - сериализатор отклоняет отрицательные деньги через ValueError. - добавлены проверки восьми стартовых дней и обновлена опись мира. - Проверка: - make lint, make typecheck и make test в generator/. - живой world_next_day: 1694 строки приняты, таблица ошибок пуста.
This commit is contained in:
@@ -78,22 +78,26 @@
|
||||
|
||||
**Дельта суммы (C) — вычеркнутая позиция.** Товара не оказалось в наличии,
|
||||
позицию сняли: у заказа на одну позицию меньше, чем в клиентских массивах, а
|
||||
`items_total` меньше на её стоимость. Уменьшаются ровно на полную стоимость
|
||||
вычеркнутой строки и `items_total`, и `total`; скидка остаётся рассчитанной от
|
||||
исходной клиентской покупки и после складского вычёркивания не пересчитывается.
|
||||
Момента у неё нет — заказ приезжает
|
||||
урезанным во всех своих слепках: первый слепок снимается на границе суток,
|
||||
когда склад заказ уже собрал. Заказ из одной позиции дельты не получает —
|
||||
пустых заказов не бывает. Позиция выбирается равновероятно: корреляция со
|
||||
спросом на доле 1–2% статистически ненаблюдаема — менти платил бы за неё
|
||||
таблицей чисел мира, а увидеть не мог бы ничем. Доводы за вычёркивание:
|
||||
остаток — сотни рублей, он торчит в витрине сверки сам; расхождение
|
||||
объясняется сравнением позиций — разбором вложенного JSON и `ARRAY JOIN`,
|
||||
ровно тем навыком, ради которого позиции разбираются; история рассказывается
|
||||
словами без легенды про генератор. Отклонено: *переоценка позиции* и *другое
|
||||
количество* — дельта в десятки рублей, её надо захотеть заметить; *чистая
|
||||
дельта без истории* — тупик, объяснить нечем; *врёт клиент, а не бэкенд* —
|
||||
заказ у нас проекция той же корзины.
|
||||
`items_total` меньше на её полную стоимость. После вычёркивания деньги заказа
|
||||
считаются от оставшейся корзины по [общему
|
||||
правилу](snapshot.md#откуда-берётся-заказ), поэтому `total` остаётся
|
||||
неотрицательным по построению. Момента у дельты нет — заказ приезжает урезанным
|
||||
во всех своих слепках: первый слепок снимается на границе суток, когда склад
|
||||
заказ уже собрал. Заказ из одной позиции дельты не получает — пустых заказов
|
||||
не бывает. Позиция выбирается равновероятно: корреляция со спросом на доле
|
||||
1–2% статистически ненаблюдаема — менти платил бы за неё таблицей чисел мира,
|
||||
а увидеть не мог бы ничем. Доводы за вычёркивание: остаток — сотни рублей, он
|
||||
торчит в витрине сверки сам; расхождение объясняется сравнением позиций —
|
||||
разбором вложенного JSON и `ARRAY JOIN`, ровно тем навыком, ради которого
|
||||
позиции разбираются; история рассказывается словами без легенды про генератор.
|
||||
Отклонено: *сохранять скидку исходной корзины и отбирать только безопасные
|
||||
позиции* — отсутствие товара стало бы зависеть от купона и доставки, а выбор
|
||||
позиции перестал бы быть равновероятным; *разрешить отрицательный `total` или
|
||||
ослабить строгий приём* — ошибка источника превратилась бы в брак, который
|
||||
молча терпит хранилище; *переоценка позиции* и *другое количество* — дельта в
|
||||
десятки рублей, её надо захотеть заметить; *чистая дельта без истории* —
|
||||
тупик, объяснить нечем; *врёт клиент, а не бэкенд* — заказ у нас проекция той
|
||||
же корзины.
|
||||
|
||||
**Потеря события (B) — точечная.** Уходит строка `purchase`, просмотр
|
||||
`/confirmation` остаётся: события уезжают разными запросами, потерять один и
|
||||
|
||||
@@ -171,13 +171,12 @@ JSON-объектом с точным набором ключей: `order_id`, `
|
||||
опытов убраны. Числа, механика опытов и поведение ClickHouse, на которое всё это
|
||||
опирается, — [документ хранилища](../storage.md), «Что проверено».
|
||||
|
||||
Одно расхождение с ожиданием осталось, и оно снаружи приёма: восемь настоящих
|
||||
строк слепка получили класс `field_invalid` — все версии одного заказа с
|
||||
отрицательным `total`. Класс заслужен, граница верна, дефект в генераторе и
|
||||
заведён отдельным issue
|
||||
[#102](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/102).
|
||||
Пока он не починен, критерий «честный прогон дня даёт пустой `_errors`» на
|
||||
стартовом мире не выполняется.
|
||||
После исправления
|
||||
[#102](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/102)
|
||||
честный прогон дня снят на живом стенде 20 августа 2026 года: все 1694 строки
|
||||
слепка дня 7 приняты, `ods.order_snapshot_errors` осталась пустой. У заказа
|
||||
`20260608-0007`, который прежде приходил с отрицательным итогом, теперь
|
||||
`5490.00 − 823.50 + 299.00 = 4965.50`.
|
||||
|
||||
Забор из Kafka в STG снят на живом стенде 18 августа 2026 года при исполнении
|
||||
#93: одно прямое чтение приносит весь слепок дня, метаданные доставки доступны,
|
||||
|
||||
@@ -32,11 +32,12 @@
|
||||
позиций, посчитанная торговой половиной, то же число, что уехало клиентским
|
||||
`purchaseRevenue`, но у заказа с вычеркнутой позицией он меньше на её полную
|
||||
стоимость ([судьба заказа](fate.md)); `discount` — процент промокода от
|
||||
исходной суммы клиентской покупки, округлённый вниз, и дельта его не
|
||||
пересчитывает;
|
||||
оставшегося `items_total`, округлённый вниз: склад сначала определяет, какие
|
||||
позиции есть в заказе, затем бэкенд считает скидку от итоговой корзины;
|
||||
`delivery` — бросок заказной стороны по таблице целых весов, единственные
|
||||
деньги заказа, которых нет ни в одном событии; `total` = `items_total` −
|
||||
`discount` + `delivery`. Отсюда и правило витрин «деньги считаем по
|
||||
`discount` + `delivery`. Скидка не превышает `items_total`, поэтому итог
|
||||
неотрицателен по построению. Отсюда и правило витрин «деньги считаем по
|
||||
бэкенду»: про скидку и доставку клиент не знает вовсе.
|
||||
|
||||
Отклонено: *выводить заказ разбором собственного вывода* (`purchaseID`, сырой
|
||||
@@ -102,6 +103,11 @@
|
||||
развилки
|
||||
[«Форма записи слепка на проводе»](https://git.dementev.space/ddmitry/clickstream-data-platform/issues/81)).
|
||||
|
||||
Отрицательные копейки канонический сериализатор отвергает через `ValueError`
|
||||
до сборки записи: [контракт строгого
|
||||
приёма](ingestion.md#граница-строгого-приёма) их не допускает, а появление
|
||||
такого значения означает ошибку арифметики источника.
|
||||
|
||||
Сверх контракта здесь живёт одно правило: **порядок строк внутри слепка —
|
||||
порядок рождения заказов, он же возрастание `order_id`**. Детерминизм даёт
|
||||
его даром, а хешу слепка в описи нужен именно названный порядок.
|
||||
|
||||
Reference in New Issue
Block a user