- Зачем:
- клиентская сторона мира становится целой: без 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>
48 lines
3.0 KiB
Python
48 lines
3.0 KiB
Python
"""Идентификаторы событий: неповторяющиеся числа, которые переживут JSON.
|
|
|
|
Уникальность обещана не для красоты: `WatchID` — ключ дедупликации при
|
|
переигровке дня (спека генератора, раздел 4), и два одинаковых номера
|
|
склеили бы разные события. Строк дня две породы — трафиковые и торговые, —
|
|
и рисует их разная случайность, поэтому обещание держится здесь, в одном
|
|
месте на весь генератор: у приёма одно определение, иначе дисциплина живёт
|
|
копиями и расходится с ними.
|
|
"""
|
|
|
|
import numpy as np
|
|
from numpy.typing import NDArray
|
|
|
|
# Потолок идентификаторов: выше 2^53 числа в JSON (jq, консоль браузера)
|
|
# округляются при разборе, и id перестаёт быть собой. Настоящая Метрика так
|
|
# не делает — её id длиннее (мастер-спека, раздел 1.1).
|
|
LIMIT = 2**53
|
|
|
|
|
|
def unique(rng: np.random.Generator, size: int) -> NDArray[np.uint64]:
|
|
"""Неповторяющиеся id ниже 2^53, разбросанные по диапазону.
|
|
|
|
Не броски наугад, а шаги случайной длины — они не повторяются по
|
|
построению, — и потом перемешивание, чтобы номер не выдавал порядок
|
|
строк. Средний шаг — весь диапазон, делённый на число событий, поэтому в
|
|
среднем ряд занимает его половину.
|
|
"""
|
|
step = LIMIT // (size + 1)
|
|
numbers = np.cumsum(rng.integers(1, step + 1, size, dtype=np.uint64))
|
|
return numbers[np.argsort(rng.integers(0, size * size + 1, size), kind="stable")]
|
|
|
|
|
|
def unique_apart_from(
|
|
rng: np.random.Generator, size: int, taken: NDArray[np.uint64]
|
|
) -> NDArray[np.uint64]:
|
|
"""То же, но и с занятыми номерами ряд не пересекается.
|
|
|
|
Торговые события берут случайность из своего подпотока — иначе правка
|
|
торгового поведения сдвинула бы трафик, — а значит, про уже розданные
|
|
трафиковые номера их ряд ничего не знает. Совпадение двух рядов на
|
|
диапазоне в 2^53 невероятно, но обещание уникальности держит дедуп, и
|
|
проверить его дешевле, чем предположить.
|
|
"""
|
|
while True:
|
|
numbers = unique(rng, size)
|
|
if not np.intersect1d(numbers, taken).size:
|
|
return numbers
|