feat(generator): торговые события — корзина, покупка, сырой ecommerce

- Зачем:
  - клиентская сторона мира становится целой: без add_to_cart и purchase
    в данных нет ни таксономии событий, ни вложенного JSON, ни денег,
    а метка «покупатель» из плана состава ни на что не влияла (#40).
- Что:
  - добавлен модуль commerce: корзина шире заказа, номер заказа вида
    ГГГГММДД-NNNN, промокод без скидки в сумме, сырой ecommerce через orjson;
  - метка покупателя получила два рычага — долгую жизнь куки в плане и
    свою воронку в дне; CART_PERCENT опущен с 8 до 6, чтобы конверсия
    мира осталась около 2%;
  - часть цен каталога получила копейки: productPrice округляется форматом,
    purchaseRevenue несёт точную сумму — расхождение живёт внутри события;
  - граница суток забирает страницу подтверждения вместе с её покупкой:
    потерь на клиентской стороне этот этап не заводит;
  - решения и перемеренные числа мира записаны в спеку генератора, §9.
- Проверка:
  - make lint && make typecheck && make test — 383 passed (было 353).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 19:13:30 +03:00
co-authored by Claude Opus 5
parent bfbaa96696
commit 722dbe22b7
19 changed files with 1591 additions and 210 deletions
@@ -77,6 +77,19 @@ SEARCH_QUERIES = tuple(
)
# Вариант товара — цвет или исполнение карточки. Живёт только в сыром
# `ecommerce`: в плоских массивах события такой колонки нет, и это одна из
# причин, по которым лаба «сырое против разобранного» вообще имеет смысл.
PRODUCT_VARIANTS = (
"стандарт",
"белый",
"серый",
"бежевый",
"синий",
"зелёный",
)
# Коды типа устройства у Метрики: 1 — десктоп, 2 — телефон, 3 — планшет,
# 4 — телевизор. Телефон назван отдельно: по нему различаются и мобильный
# адрес, и вторая кука пары — «телефон и ноутбук» (мастер-спека, раздел 5).