feat(generator): день-функция — трафик, визиты и просмотры страниц
Зачем: план состава отдаёт дневную аудиторию, но событий у мира ещё не было. День-функция превращает аудиторию в поток просмотров — на нём стоят лабы про сборку визитов и про витрины, а следующий этап вешает на него торговые события. Что: - `day.py` — день как чистая функция зерна и номера дня: суточная волна в местном времени посетителя, визиты по документированным правилам нарезки, все 47 колонок выгрузки; шов для торговых событий — ряды `page` и `product`; - `reference.py` — справочники-литералы: профили устройств, города Поволжья с настоящими гео-id Яндекса, источники трафика, карта сайта; - `catalog.py` и `data/catalog/products.csv` — каталог на 180 позиций, общий у генератора и будущего словаря ClickHouse; - `weights.py` — выбор по целым весам, один на план и на день; - паспорт куки (устройство и город) переехал в план состава; броски приписаны последними, поэтому измеренные числа канонического мира не сдвинулись; - словарь: «визит» закреплён за сессией, одноимённое понятие плана стало «днём активности»; статьи в `CONTEXT.md`; - решения по ходу — в спеку генератора, раздел 9; наполнение `ParsedParamsKey1` отложено тикетом #47. Проверка: `make lint`, `make typecheck`, `make test` — 353 passed (было 297). Счётчики плана после правки те же: приток 3827,64/день, дневная аудитория 6235–7124, 68 119 посетителей за 14 дней, 170 двухкуковых пар. День 0 — 45 810 событий за 0,6 с, снимок 14 дней — 5,9 с при пороге 30 с на день. Две слепые линии ревью, десять находок, все закрыты и перепроверены. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,3 +64,56 @@ def test_most_returns_land_in_the_first_days():
|
||||
def test_pairs_are_a_small_part_of_the_cohort():
|
||||
"""Вторые куки пар добавляют к притоку меньше процента (спека, раздел 9)."""
|
||||
assert world.BUYER_PERCENT * world.PAIRED_BUYER_PERCENT < 100
|
||||
|
||||
|
||||
def test_both_daily_waves_cover_a_day_and_average_to_one():
|
||||
"""Форма волны не меняет суточный объём: его задаёт недельный профиль."""
|
||||
for profile in (world.WEEKDAY_HOURS_PERCENT, world.WEEKEND_HOURS_PERCENT):
|
||||
assert len(profile) == 24
|
||||
assert sum(profile) == 2400
|
||||
|
||||
|
||||
def test_the_wave_dips_at_night_and_peaks_up_to_twice_the_average():
|
||||
"""Пики до ~2× среднего, ночью провал (спека, раздел 2)."""
|
||||
for profile in (world.WEEKDAY_HOURS_PERCENT, world.WEEKEND_HOURS_PERCENT):
|
||||
assert min(profile[2:6]) < 50
|
||||
assert 150 < max(profile) <= 220
|
||||
|
||||
|
||||
def test_the_weekend_wakes_up_later_than_a_weekday():
|
||||
morning = slice(7, 10)
|
||||
assert sum(world.WEEKEND_HOURS_PERCENT[morning]) < sum(
|
||||
world.WEEKDAY_HOURS_PERCENT[morning]
|
||||
)
|
||||
|
||||
|
||||
def test_an_active_day_holds_a_visit_or_two():
|
||||
"""Дневная аудитория 6–8 тыс. и 8–12 тыс. визитов сходятся через это число."""
|
||||
weights = world.VISITS_PER_ACTIVE_DAY_WEIGHTS
|
||||
counts = tuple(range(1, len(weights) + 1))
|
||||
assert 1.2 <= mean_by_weights(counts, weights) <= 1.6
|
||||
|
||||
|
||||
def test_a_visit_is_a_few_pages_long_and_often_a_single_one():
|
||||
weights = world.VISIT_PAGES_WEIGHTS
|
||||
pages = tuple(range(1, len(weights) + 1))
|
||||
assert 4 <= mean_by_weights(pages, weights) <= 6
|
||||
bounced = weights[0] / sum(weights)
|
||||
assert 0.15 < bounced < 0.35
|
||||
|
||||
|
||||
def test_pauses_stay_inside_the_visit_timeout():
|
||||
"""Иначе визит распался бы там, где генератор этого не обещал."""
|
||||
assert max(world.PAGE_PAUSE_SECONDS) < world.VISIT_TIMEOUT_SECONDS
|
||||
assert max(world.LONG_PAUSE_SECONDS) < world.VISIT_TIMEOUT_SECONDS
|
||||
|
||||
|
||||
def test_the_funnel_converts_about_two_percent_of_visits():
|
||||
"""Конверсия ~2% на сессию (спека, раздел 9) — произведение трёх шагов."""
|
||||
conversion = (
|
||||
world.CART_PERCENT
|
||||
* world.CHECKOUT_OF_CART_PERCENT
|
||||
* world.CONFIRMATION_OF_CHECKOUT_PERCENT
|
||||
/ 100**2
|
||||
)
|
||||
assert 1.5 <= conversion <= 2.5
|
||||
|
||||
Reference in New Issue
Block a user