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:
@@ -5,8 +5,10 @@
|
||||
пересобрать: `make docs`.
|
||||
|
||||
Одно событие — одна строка: хит по образцу облачной выгрузки Яндекс Метрики.
|
||||
Многозначное лежит в параллельных массивах одной длины, плюс одно сырое
|
||||
JSON-поле `ecommerce`. Отдельной сущности «визит» в выгрузке нет — визиты
|
||||
Многозначное лежит в параллельных массивах, плюс одно сырое JSON-поле
|
||||
`ecommerce`. Длина у массивов общая **внутри группы**, а не по всему
|
||||
событию: `purchase*` — по элементу на заказ (у нас всегда один), `product*` —
|
||||
по элементу на товар. Отдельной сущности «визит» в выгрузке нет — визиты
|
||||
собирают на стороне хранилища, а `VisitID` дан как эталон для самопроверки.
|
||||
|
||||
Имена и типы колонок — стороны источника. Хранилище принимает их как есть и
|
||||
@@ -21,6 +23,10 @@ JSON-поле `ecommerce`. Отдельной сущности «визит» в
|
||||
`add_to_cart` несёт один товар, `purchase` — состав заказа и блок
|
||||
`purchase*`. У остальных событий они пусты.
|
||||
|
||||
Деньги: `productPrice` — целые рубли, округление формата. Точная сумма
|
||||
заказа живёт в `purchaseRevenue` и в сыром `ecommerce`, поэтому пересчитать
|
||||
выручку по разобранным массивам нельзя — цены каталога бывают с копейками.
|
||||
|
||||
Всего колонок: 47.
|
||||
|
||||
## Идентификаторы и время
|
||||
|
||||
@@ -447,15 +447,18 @@ pytest-тест с маркером `perf` и таймаутом-обрубан
|
||||
- **D0 = 2026-06-01, понедельник** — решение владельца при нарезке
|
||||
(2026-08-01). Дата недавняя, чтобы данные первые месяцы выглядели
|
||||
свежими; привязки к реальному календарю у констант мира всё равно нет.
|
||||
- **Измерено на собранном плане** (#38, канонический мир, 14 дней):
|
||||
приток 3 828 кук в день в среднем, дневная аудитория 6 235–7 124 —
|
||||
обе величины в вилках раздела 5. Накопленная аудитория за снимок —
|
||||
68 тыс. кук: 53,6 тыс. новыми плюс 14,5 тыс. возвратами предыстории
|
||||
(при нарезке возвраты предыстории оценили вдвое скромнее — отсюда
|
||||
ходившая раньше оценка ≈60 тыс.). Пар, у которых оба назначенных
|
||||
заказа попали внутрь 14 дней, — 170; остальные пары горизонт
|
||||
- **Измерено на собранном плане** (канонический мир, 14 дней; числа
|
||||
перемерены при исполнении #40 — рычаг долгой жизни помеченного
|
||||
покупателя сдвинул состав): приток 3 828 кук в день в среднем, дневная
|
||||
аудитория 6 637–7 595 — обе величины в вилках раздела 5. Накопленная
|
||||
аудитория за снимок — 70,2 тыс. кук: 53,6 тыс. новыми плюс 16,6 тыс.
|
||||
возвратами предыстории (при нарезке возвраты предыстории оценили
|
||||
скромнее — отсюда ходившая раньше оценка ≈60 тыс.). Пар, у которых оба
|
||||
назначенных заказа попали внутрь 14 дней, — 192; остальные пары горизонт
|
||||
переживают, их вторая кука приходит позже. Цифры пересчитываются
|
||||
прогоном плана, событий для них не нужно.
|
||||
прогоном плана, событий для них не нужно. До правки #40 те же измерения
|
||||
давали: аудитория 6 235–7 124, накопленная 68,1 тыс. (14,5 тыс.
|
||||
возвратами предыстории), 170 пар.
|
||||
- **Конфигурация мира — модуль чистых данных** рядом с контрактом схемы:
|
||||
все числа мира в одном месте, написанном как приглашение любопытному
|
||||
менти крутить. Правка модуля — смена мира: чек манифеста честно
|
||||
@@ -538,10 +541,14 @@ pytest-тест с маркером `perf` и таймаутом-обрубан
|
||||
Правила лежат в докстринге `day.py`, и тест собирает визиты по ним
|
||||
заново, сверяя сборку с `VisitID`.
|
||||
- **Числа дня**: ≈1,4 визита на день активности куки и ≈4,8 страницы на
|
||||
визит — 9,5 тыс. визитов и ~45 тыс. pageview в средний день; воронка
|
||||
8% визитов до корзины → 45% из них до оформления → 55% из них до
|
||||
подтверждения, то есть конверсия визита ~2%. Итог сложится после
|
||||
торговых событий (#40) и ляжет в манифест.
|
||||
визит. Воронка при исполнении #39 была общей для всех: 8% визитов до
|
||||
корзины → 45% из них до оформления → 55% из них до подтверждения, то есть
|
||||
конверсия визита ~2% при 9,5 тыс. визитов и ~45 тыс. pageview в средний
|
||||
день. При исполнении #40 воронка развелась надвое (блок ниже): обычному
|
||||
посетителю 6% до корзины и те же 45% и 55%, помеченному покупателю — 18%
|
||||
и 60%. Перемеренный средний день: 9 879 визитов, 47 433 pageview, 4,80
|
||||
страницы на визит, конверсия визита 2,4%; полные числа — в блоке #40,
|
||||
оттуда же они лягут в манифест.
|
||||
- **Недельная волна применяется один раз.** Профиль ведёт приток, трафик
|
||||
наследует его через дневную аудиторию; измеренный размах трафика — ±7%
|
||||
против ±10% у притока. Второе умножение удвоило бы недельный размах. Если
|
||||
@@ -595,15 +602,24 @@ pytest-тест с маркером `perf` и таймаутом-обрубан
|
||||
воронке (#39); правятся оба. Отличается помеченный на обоих шагах воронки:
|
||||
и до корзины доходит чаще, и бросает её реже. В жизни различаются оба — кто
|
||||
пришёл смотреть, тот и кладёт реже, и до конца доводит реже; один шаг дал
|
||||
бы половину картины. Числа под каждый шаг — калибровка. Границы правки —
|
||||
вилки раздела 5: дневная
|
||||
аудитория остаётся в пределах 6–8 тыс., средний день — около 50 тыс.
|
||||
событий, конверсия визита — около 2%. Расти внутри вилки аудитории
|
||||
бы половину картины. Числа под каждый шаг — калибровка: помеченному 18% до
|
||||
корзины и 60% из корзины в оформление против 6% и 45% у обычного
|
||||
посетителя. Обычного калибровка тоже задела — его доля до корзины опущена
|
||||
с 8% до 6%, и это единственное число мира, изменённое вне двух рычагов.
|
||||
Довод: помеченные живут дольше и занимают уже 12% дневной аудитории вместо
|
||||
5% людей когорты, поэтому при прежних 8% конверсия мира ушла бы к 2,9% —
|
||||
за вилку. Рычаг метки не должен оплачиваться ростом конверсии, и границы
|
||||
правки — вилки раздела 5: дневная аудитория остаётся в пределах
|
||||
6–8 тыс., средний день — около 50 тыс. событий, конверсия визита —
|
||||
около 2%. Расти внутри вилки аудитории
|
||||
разрешено: помеченные ходят чаще, и это по-прежнему правдоподобный магазин.
|
||||
Измеренные числа состава (блок #38 выше) после правки перемеряются и
|
||||
переписываются; база до правки — в средний день аудитория 6 710, визитов
|
||||
9 249, событий 44 436, визитов с корзиной 780 (8,4%) и с покупкой 221
|
||||
(2,4%). Уточнение к разделу 1: вольные покупки по-прежнему решает день, но
|
||||
Измерено после правки (канонический мир, 14 дней): в средний день
|
||||
аудитория 7 166, визитов 9 879, событий 49 509, визитов с корзиной 765
|
||||
(7,7%) и с покупкой 240 (2,4%) — все три вилки выдержаны. База до правки
|
||||
была: аудитория 6 710, визитов 9 249, событий 44 436, визитов с корзиной
|
||||
780 (8,4%) и с покупкой 221 (2,4%). Числа состава мира (блок #38 выше)
|
||||
перемерены тем же прогоном. Уточнение к разделу 1: вольные покупки
|
||||
по-прежнему решает день, но
|
||||
случайность в них больше не одинакова для всех. Отклонено: *оставить
|
||||
монетку* — «покупатель» остаётся именем без следа в данных; *один лифт без
|
||||
долгой жизни* — довод выше, повторных покупок он почти не прибавляет;
|
||||
@@ -662,6 +678,46 @@ pytest-тест с маркером `perf` и таймаутом-обрубан
|
||||
ломал бы его ровно там, ради чего написан. Визит, у которого страница
|
||||
подтверждения срезана полуночью (1,2% покупающих), покупки не даёт — заказа
|
||||
не было.
|
||||
- **Счётчика покупок в плане состава не заводится.** Тикет просил «счётчики
|
||||
покупок и двухкуковых пар сходятся с планом», но покупок план не считает и
|
||||
считать не будет: раздел 1 обещает до генерации только состав — посетителей,
|
||||
приток, пары, — а торговые счётчики складываются из поведения дня. Квота на
|
||||
покупки заставила бы день добирать до точного числа, и конверсия в данных
|
||||
вышла бы подозрительно ровной — условность видна там, куда лаба смотрит.
|
||||
Сходятся пары; покупки проверяются постфактум по сгенерированному дню и по
|
||||
вилке конверсии (~2% на визит).
|
||||
|
||||
Решено при исполнении #40 самой реализацией:
|
||||
|
||||
- **Торговый хвост разводит и соседние визиты, а не только сутки.** Правило
|
||||
резки требует, чтобы визиты одной куки стояли дальше таймаута; считать это
|
||||
расстояние от последней страницы, когда последним событием визита бывает
|
||||
покупка, значило отдать таймауту торговый хвост — лаба сессий склеила бы
|
||||
два визита в один и разошлась бы с `VisitID`. Расстояние считается от
|
||||
последнего события; поймано тестом сборки визитов.
|
||||
- **Полночь забирает подтверждение вместе с его покупкой.** Резерв суток
|
||||
сначала считался только для визитов с обещанным планом заказом, и у
|
||||
вольной покупки оставалось окно в несколько секунд: страница
|
||||
подтверждения дожила до потока, а событие покупки за полночь вышло. Это
|
||||
была бы потеря на клиентской стороне — класс этапов 4 и 6, заведённый по
|
||||
недосмотру. Теперь запас под торговый хвост есть у всякой страницы
|
||||
подтверждения: либо она уходит за границу суток вместе с покупкой, либо
|
||||
остаются обе. Это ровно то правило, которое блок выше называет словами
|
||||
«визит, у которого страница подтверждения срезана полуночью, покупки не
|
||||
даёт»; в каноническом мире числа от правки не сдвинулись — случай не
|
||||
выпал ни разу за 14 дней.
|
||||
- **Задержка торгового события короче самой короткой паузы между
|
||||
страницами.** Тогда событие корзины не обгоняет страницу, на которой
|
||||
посетитель нажал кнопку, и не выходит за полночь: за ним в том же визите
|
||||
всегда идёт страница, которая полночь пережила.
|
||||
- **Номера торговых событий — из торгового подпотока.** `WatchID` строки
|
||||
корзины и покупки рисует `COMMERCE`: возьми их у трафика, и правка
|
||||
торгового поведения сдвинула бы трафиковые номера. Ряды двух подпотоков
|
||||
проверяются на непересечение — обещание уникальности держит дедупликацию
|
||||
при переигровке дня, и предполагать его нельзя.
|
||||
- **Вариант товара — справочник мира.** Сырой `ecommerce` несёт `variant`,
|
||||
которого в плоских колонках нет; строки живут в справочниках рядом с
|
||||
устройствами и городами, а не сочиняются на месте.
|
||||
|
||||
Остаётся открытым, за тикетами:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user