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:
2026-08-02 16:12:17 +03:00
co-authored by Claude Opus 5
parent 937ba014b4
commit eb433ad023
18 changed files with 2073 additions and 105 deletions
+70 -5
View File
@@ -13,6 +13,16 @@
from datetime import date
# Счётчик стенда: сайт один, номер — константа мира.
COUNTER_ID = 42150607
# Часовой пояс счётчика, минуты от UTC: Самара, UTC+4. Модельные сутки
# считаются в этом поясе, как в выгрузке Метрики: `EventDate` — дата в поясе
# счётчика, `UTCEventTime` — абсолютная метка. Отсюда следствие, о котором
# сторона хранилища должна знать заранее: `toDate(UTCEventTime)` ≠ `EventDate`
# у ночных событий (спека генератора, раздел 9).
COUNTER_TIMEZONE_MINUTES = 240
# D0 — первый день оси модельного времени, понедельник. Реальный календарь в
# модели не участвует: дата нужна лишь затем, чтобы дни оси легли в
# `EventDate`/`UTCEventTime` конкретными числами. От даты запуска мир не
@@ -25,9 +35,11 @@ ORIGIN = date(2026, 6, 1)
DAILY_INFLUX = 3_800
# Недельная волна мира, проценты от среднего: понедельник … воскресенье.
# Профиль один на весь мир: по нему идёт приток, по нему же пойдёт трафик
# дня-функции — иначе доля новичков скакала бы по дням недели. В сумме ровно
# 700: за неделю средний день остаётся средним.
# Волна задана притоку — по ней в мир приходят новые люди. Трафик наследует
# её через дневную аудиторию, а не вторым умножением (довод — у суточной
# волны выходного дня), поэтому доля новичков по дням недели ровная, а
# недельный размах трафика выходит мягче притока: ±7% против ±10%. В сумме
# ровно 700: за неделю средний день остаётся средним.
WEEKLY_PROFILE_PERCENT = (105, 108, 107, 105, 95, 88, 92)
# Разброс притока изо дня в день, проценты: ровный приток выдал бы себя в
@@ -43,13 +55,13 @@ ONE_SHOT_PERCENT = 75
# притоке 3 800 (спека, разделы 5 и 9).
RETURN_COUNT_WEIGHTS = (25, 20, 15, 12, 9, 7, 5, 4, 2, 1)
# Хвост возвратов: окно активности человека от его первого визита, общее на
# Хвост возвратов: окно активности человека от его первого дня, общее на
# обе его куки. За краем окна кука не возвращается. Оно же — глубина
# предыстории: столько когорт живёт до D0, чтобы дневная аудитория была на
# полке с самого первого дня.
RETURN_TAIL_DAYS = 90
# Профиль возвратов по дням от первого визита: почти всё в первую неделю,
# Профиль возвратов по дням от первого дня куки: почти всё в первую неделю,
# дальше тонкий хвост до края окна — повторные покупки в магазине случаются
# и через месяцы. Профиль затухает к краю, поэтому обрыв на нём в данных не
# виден. Читается по парам «сколько дней — с каким весом»; дней в сумме
@@ -69,3 +81,56 @@ BUYER_PERCENT = 5
# Такой паре план назначает по гарантированному заказу с каждой куки — на
# этом стоит лаба про склейку личности.
PAIRED_BUYER_PERCENT = 15
# --- Числа дня: суточная волна, визиты, воронка ---------------------------
# Суточная волна буднего дня: проценты от среднего часа, от 00 до 23 часов
# местного времени посетителя. Ночной провал, обеденный и вечерний пики до
# ~2× среднего (спека генератора, раздел 2). В сумме ровно 2400: средний час
# остаётся средним, и суточный объём от формы волны не зависит.
WEEKDAY_HOURS_PERCENT = (
35, 20, 12, 8, 8, 12, 25, 45, 70, 105, 115, 130,
170, 165, 140, 130, 130, 140, 160, 185, 210, 180, 130, 75,
) # fmt: skip
# Выходной день: подъём позже, обеденного пика нет — день ровнее, вечер
# ниже буднего. Здесь только форма, поэтому сумма та же — 2400: объём
# выходного день-функция не трогает, он приходит сам, потому что дневная
# аудитория уже дышит недельной волной через приток. Умножить на неё
# второй раз значило бы удвоить недельный размах.
WEEKEND_HOURS_PERCENT = (
45, 30, 20, 12, 10, 10, 14, 22, 40, 70, 105, 140,
165, 160, 175, 175, 170, 165, 165, 175, 180, 165, 120, 67,
) # fmt: skip
# Сколько визитов у куки в её день активности: веса для 1, 2 и 3 визитов.
# В среднем ≈1,4 — при дневной аудитории 6–7 тыс. это 8–10 тыс. визитов
# (спека, раздел 5).
VISITS_PER_ACTIVE_DAY_WEIGHTS = (70, 22, 8)
# Длина визита в страницах: веса для 1, 2, 3 … страниц. Первая доля — отказы
# (посмотрел одну страницу и ушёл), дальше затухающий хвост. В среднем ≈4,8
# страницы: вместе с числом визитов это ~45 тыс. pageview в средний день,
# и до ~50 тыс. добирают торговые события (#40).
VISIT_PAGES_WEIGHTS = (250, 150, 120, 100, 88, 78, 68, 58, 50, 42, 35, 28, 22, 16)
# Таймаут визита: пауза дольше этой рвёт визит надвое. Правило резки, по
# которому лаба сессий сверяет свою сборку с `VisitID` (мастер-спека,
# раздел 1.2), поэтому паузы внутри визита всегда короче, а соседние визиты
# одной куки всегда разведены дальше.
VISIT_TIMEOUT_SECONDS = 1800
# Пауза между соседними страницами визита, секунды: обычная и «задумался».
# Обе целиком внутри таймаута — иначе визит распался бы там, где генератор
# этого не обещал.
PAGE_PAUSE_SECONDS = (8, 300)
LONG_PAUSE_SECONDS = (300, 1500)
LONG_PAUSE_PERCENT = 12
# Воронка: доля визитов, дошедших до корзины, и доли следующих шагов от
# предыдущего. Произведение — конверсия визита в оформленный заказ: 2%
# (спека генератора, раздел 9). Гарантированные планом заказы двухкуковых
# пар проходят воронку целиком независимо от этих долей.
CART_PERCENT = 8
CHECKOUT_OF_CART_PERCENT = 45
CONFIRMATION_OF_CHECKOUT_PERCENT = 55