From 9b1826f5ff95d14afb43137c439fe190fbe48ab3 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 30 Aug 2026 16:42:32 -0400 Subject: [PATCH] =?UTF-8?q?feat(generator):=20=D0=BE=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B1=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - семь мест обещали калибровку черновых долей «при пересборке эталонного мира (этап 7)», а пересборки не будет (#192); числа сверены замером и зафиксированы (#194, хвост этапа 7 — #9). - Что: - замер на каноническом мире (28 дней, без транспорта): все доли в пределах ориентиров; пометки «черновое» сняты, наблюдаемые доли вписаны в комментарии world.py, fate.md и запись «Что проверено»; - потеря purchase: наблюдаемых 1,78% при броске 3% — вычеты штатные (пары склейки, приоритет отмены), принято решением владельца; ориентир класса B в мастер-спеке различает бросок и наблюдаемую долю; - веса мира и опись не тронуты: правки world.py — только комментарии. - Проверка: - cd generator && make lint && make typecheck && make test (437 passed); - git diff по data/world-inventory.json пуст; grep «черновое|калибровка» по generator/src и docs/architecture/orders пуст. Co-Authored-By: Claude Fable 5 --- docs/architecture/orders/README.md | 4 -- docs/architecture/orders/fate.md | 7 ++- docs/architecture/testing.md | 55 +++++++++++++++++++- docs/specs/2026-07-30-stand-v2-realism.md | 7 +-- generator/src/clickstream_generator/world.py | 29 ++++++----- 5 files changed, 79 insertions(+), 23 deletions(-) diff --git a/docs/architecture/orders/README.md b/docs/architecture/orders/README.md index 32e3336..9193503 100644 --- a/docs/architecture/orders/README.md +++ b/docs/architecture/orders/README.md @@ -51,10 +51,6 @@ ## Открытые решения -- **Конкретные веса и доли** — таблицы исходов, моментов, задержки опоздания, - доли классов, стоимость доставки — калибровка при реализации; финальная - фиксация чисел — пересборка эталонного мира, этап 7. При пересборке правки - потребуют только числа, не устройство. - **Контур проверок качества для расхождений** (даг DQ, `dm.dq_summary`) — остаётся в тумане карты #69; естественное место разговора — этап 4. - **Каноническое чтение событий `ods.event_v`** — отдельный тикет #86, к diff --git a/docs/architecture/orders/fate.md b/docs/architecture/orders/fate.md index 061df71..89b4367 100644 --- a/docs/architecture/orders/fate.md +++ b/docs/architecture/orders/fate.md @@ -55,8 +55,11 @@ часа покупки, и менти нашёл бы этот наклон первым же разрезом. **Доли.** Ориентир мастер-спеки ~5% читается как доля отменённых вообще; как -она делится между двумя дорогами — строки таблицы исходов. Точные числа — -калибровка этапа 7; проверок вида «отмен от 4 до 6 процентов» не заводим. +она делится между двумя дорогами — строки таблицы исходов. Замер на +каноническом мире (28 дней, 2026-08-30, docs/architecture/testing.md, «Что +проверено»): отменено 5,15% заказов — 3,03% оплачен-потом-отменен, 2,12% +отменен неоплаченным; в пределах ориентира. Проверок вида «отмен от 4 до 6 +процентов» не заводим. **Что из двух дорог видно.** В `dds.order` дороги неразличимы — там последняя версия; различает их история версий: сырьё STG и физические версии diff --git a/docs/architecture/testing.md b/docs/architecture/testing.md index 8b42ca3..3434ad5 100644 --- a/docs/architecture/testing.md +++ b/docs/architecture/testing.md @@ -123,8 +123,9 @@ ClickHouse отвечает сразу. Двадцать секунд из ее ста семидесяти шести — пересборка восьми модельных дней для сверки с описью мира (20,5 с из 176,5). Дешевле хеши не сравнить: чтобы -узнать, тот ли получается мир, его надо получить. Место выбрано по той же оси «кого -спрашивают» — вопрос обращён к коду генератора, стенд ему не нужен, — и +узнать, тот ли получается мир, его надо получить. Место выбрано по той же +оси «кого спрашивают» — вопрос обращён к коду генератора, стенд ему не +нужен, — и краснеет проверка там, где надо: сразу после правки генератора. Не будь её, расхождение всплыло бы получасом позже, на поднятом стенде, где выглядит поломкой хранилища, а не забытой пересборкой. @@ -238,6 +239,56 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`. ## Что проверено +**Калибровка заказной стороны 30 августа 2026 года при исполнении #194.** +Пересборки эталонного мира не будет (#192), поэтому доли заказной стороны +сверены замером внутренностей генератора — окно 28 модельных дней, +канонический seed, без транспорта, — тем же приемом, каким `test_plan.py` и +`test_commerce.py` меряют мир. Команда (из `generator/`): + +```bash +uv run python - <<'PY' +from collections import Counter +from clickstream_generator import day, inventory, orders, world +from clickstream_generator.seeds import CANONICAL_SEED + +WINDOW = 28 +class_totals: Counter[str] = Counter() +outcome_totals: Counter[int] = Counter() +delivery_totals: Counter[int] = Counter() +delay_totals: Counter[int] = Counter() +total_orders = 0 + +for d in range(WINDOW): + today = day.stream(CANONICAL_SEED, d) + for key, value in inventory._order_class_counts(today).items(): + class_totals[key] += value + money = today.orders + total_orders += len(money) + for value in money.outcome.tolist(): + outcome_totals[value] += 1 + for value in money.delivery.tolist(): + delivery_totals[value] += 1 + for value in money.snapshot_delay.tolist(): + delay_totals[value] += 1 + +print(total_orders, dict(class_totals), dict(outcome_totals), + dict(delivery_totals), dict(delay_totals)) +PY +``` + +6973 заказов за 28 дней. Доли из таблицы сверки (после приоритета классов): +отменено 5,15% (ориентир мастер-спеки ~5%, в пределах; из них 3,03% +оплачен-потом-отменен и 2,12% отменен неоплаченным — веса `world.py` +95/3/2); дубль `purchase` 1,89% (ориентир ~2%, в пределах); дельта суммы +1,32% (ориентир 1–2%, в пределах); потеря `purchase` 1,78% при броске 3% — +вычеты штатные: назначенные планом покупки из броска исключены (мост +склейки), часть случаев уходит в отмену приоритетом; наблюдаемая доля +принята решением владельца (#194). Стоимость доставки — 35,5% / 49,2% +/ 15,4%, совпадает с весами `DELIVERY_KOPECKS_WEIGHTS`. Опоздание слепков +(`ORDER_SNAPSHOT_DELAY_WEIGHTS`) — доля заказов с δ>0 составила 8,39% +(ориентир мастер-спеки ~10%, в пределах). Скрипт замера в репозиторий не +входит; вывод — в отчете issue #194. + **Перезамер 30 августа 2026 года при исполнении #192.** Команды запускались из `generator/`: diff --git a/docs/specs/2026-07-30-stand-v2-realism.md b/docs/specs/2026-07-30-stand-v2-realism.md index 3853c26..7a59982 100644 --- a/docs/specs/2026-07-30-stand-v2-realism.md +++ b/docs/specs/2026-07-30-stand-v2-realism.md @@ -265,7 +265,7 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate | | Расхождение | Механика в генераторе | Ориентир доли | |---|---|---|---| | A | Отмена | заказ дошёл до `cancelled`, `purchase` остался | ~5% заказов — отменённые вообще, обе дороги отмены вместе | -| B | Потерянное событие | заказ есть, `purchase` не доехал | ~3% | +| B | Потерянное событие | заказ есть, `purchase` не доехал | бросок 3%, наблюдаемых ~1,8%: часть маячков защищена парами склейки, часть уходит в отмену приоритетом | | C | Дельта суммы | вычеркнутая позиция: товара не оказалось в наличии, у заказа на одну позицию меньше, чем в клиентских массивах. Сверка приведена к сравнимой базе (`items_total`, не `total`); `amount_delta` — остаток, не объяснённый этим приведением, и сравнением позиций он объясняется — на этом стоит урок класса | ~1–2% | | D | Дубль события | повторный `purchase` от обновления `/confirmation`: новый `WatchID` с тем же `purchaseID` — бизнес-дубль, не технический; дедуп ReplacingMergeTree его не съедает и не должен | ~2% | @@ -274,8 +274,9 @@ CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `cate Пятое — **опоздание** — бесплатно даёт формат доставки: часть заказов впервые появляется в слепке D+1/D+2 («вчера не сходилось, сегодня сошлось»), -ориентир ~10%. Точные доли фиксируются при пересборке эталонного мира; -опись хранит счётчики наблюдаемых классов после приоритета — в заказах и +ориентир ~10%. Точные доли зафиксированы замером на каноническом мире +(28 дней, 2026-08-30, #194) — запись в «Что проверено» +[карты проверок](../architecture/testing.md); опись хранит счётчики наблюдаемых классов после приоритета — в заказах и только по дням с закрытым окном (раздел 8). Не берём: сироту-фрод (`purchase` есть, а заказа не будет никогда) — diff --git a/generator/src/clickstream_generator/world.py b/generator/src/clickstream_generator/world.py index e2f5993..43ff27b 100644 --- a/generator/src/clickstream_generator/world.py +++ b/generator/src/clickstream_generator/world.py @@ -170,8 +170,11 @@ BUYER_CHECKOUT_OF_CART_PERCENT = 60 TRADE_DELAY_SECONDS = (2, 8) # Событийная сторона сверки: потерянные маячки purchase и повторная -# отправка пары «просмотр подтверждения + purchase». Числа черновые: -# точная калибровка будет при пересборке эталонного мира, этап 7. +# отправка пары «просмотр подтверждения + purchase». Замер (28 дней, +# канонический мир, 2026-08-30): дубль 1,89% — в пределах ориентира ~2%; +# потеря 1,78% при броске 3% — так и задумано: назначенные планом покупки +# из броска исключены (мост склейки), часть съедает приоритет отмены; +# наблюдаемая доля принята решением владельца (#194). LOST_PURCHASE_PERCENT = 3 DUPLICATE_PURCHASE_PERCENT = 2 DUPLICATE_DELAY_SECONDS = (30, 301) @@ -245,8 +248,8 @@ GOAL_PURCHASE_ID = 42150002 # обычная курьерская, срочная. Это единственные деньги заказа, которых нет # ни в одном событии: выручка клиента — сумма позиций, скидку по промокоду # считает бэкенд по таблице `COUPONS`, а доставку решает свой бросок -# заказной стороны. Числа черновые: калибровка — пересборка эталонного -# мира, этап 7. +# заказной стороны. Замер (28 дней, канонический мир, 2026-08-30): 35,5% / +# 49,2% / 15,4% — совпадает с весами. DELIVERY_KOPECKS_WEIGHTS = ( (0, 35), (29_900, 50), @@ -255,12 +258,13 @@ DELIVERY_KOPECKS_WEIGHTS = ( # Чем кончается окно изменяемости заказа: веса трёх исходов, в порядке # `orders.OrderOutcome`. «Создан» навсегда мир не допускает — из окна заказ -# выходит либо оплаченным, либо отменённым. Доли черновые: калибровка — -# пересборка эталонного мира, этап 7. +# выходит либо оплаченным, либо отменённым. Замер (28 дней, 2026-08-30): +# отменено 5,15% заказов — 3,03% оплачен-потом-отменен, 2,12% отменен +# неоплаченным; ориентир мастер-спеки ~5%. ORDER_OUTCOME_WEIGHTS = ( 95, # оплачен 3, # оплачен, потом отменён - 2, # отменён неоплаченным + 2, # отменен неоплаченным ) # Когда внутри окна случается момент заказа — оплата или отмена: веса часов @@ -268,8 +272,8 @@ ORDER_OUTCOME_WEIGHTS = ( # с каким весом»; часов в сумме ровно 144 — самый узкий край окна, у заказа, # рождённого в конце суток (docs/architecture/orders/fate.md). Почти всё в # первые сутки, дальше затухание до края: -# профиль гаснет, поэтому обрыв окна в данных не виден. Веса черновые, как и -# доли исходов. +# профиль гаснет, поэтому обрыв окна в данных не виден. Веса зафиксированы +# вместе с долями исходов — тем же замером выше. ORDER_MOMENT_HOUR_WEIGHTS = tuple( weight for hours, weight in ((6, 100), (18, 60), (24, 25), (48, 8), (48, 2)) @@ -280,11 +284,12 @@ ORDER_MOMENT_HOUR_WEIGHTS = tuple( # наличии, и заказ приезжает на строку короче клиентской корзины. Бросается # доля на все заказы дня, а достаётся дельта не всем — однопозиционному заказу # терять нечего, и таких больше половины, — поэтому в сверке наблюдаемая доля -# выходит вдвое-втрое ниже броска, около ориентира мастер-спеки в 1–2%. Число -# черновое: калибровка — пересборка эталонного мира, этап 7. +# выходит вдвое-втрое ниже броска, около ориентира мастер-спеки в 1–2%. Замер +# (28 дней, 2026-08-30): наблюдаемая доля 1,32% — в пределах ориентира. ORDER_DELTA_PERCENT = 4 # На сколько первых дневных слепков заказ опоздает: веса δ = 0, 1, 2. # Назначенные планом заказы после броска получают 0, чтобы мост склейки -# появился в стартовом мире. Числа черновые: калибровка — этап 7. +# появился в стартовом мире. Замер (28 дней, 2026-08-30): доля опоздавших +# (δ>0) 8,39% — в пределах ориентира мастер-спеки ~10%. ORDER_SNAPSHOT_DELAY_WEIGHTS = (90, 5, 5) -- 2.54.0