День-функция: трафик, сессии, pageview #39

Closed
opened 2026-08-01 21:19:39 +03:00 by ddmitry · 2 comments
Owner

Part of #4.

Цель

День-функция, часть первая: (зерно, D) → упорядоченный поток pageview.
Суточные волны, сессии, поля страницы, атрибуции, браузера, устройства и
гео — трафиковая половина широкого события.

Что войдёт

  • Суточный профиль: ночной провал, обеденный и вечерний пики (до ~2×
    среднего), различие будней и выходных.
  • Сессии: 30-минутный таймаут, VisitID от генератора, резка по границе
    модельных суток; правила документируются — лаба сессий сверяет себя
    по ним.
  • Поля pageview: URL/Referer/Title/UTM/LastTrafficSource/HasGCLID/YCLID,
    браузер/ОС/устройство/экран, IP и регионы. Справочники — таблицы-литералы
    или посточный фейкер, но только для справочников, не в горячем цикле;
    выбор библиотеки — здесь (спека генератора, раздел 6).
  • Векторная генерация numpy (PCG64); посточный цикл — только цепочки
    сессий; целочисленная случайность.

Критерии приёмки

  • Все 47 полей присутствуют в каждом событии: «пусто» — пустой массив,
    пустая строка или 0, а не отсутствие ключа. Торговые поля пустые
    по смыслу — их заполнит следующий тикет.
  • Детерминизм: два вычисления дня D с одним зерном — идентичные
    события; день N+1 не меняет события дней 1…N (тесты).
  • Волны видны: часовые счётчики дня показывают провал и пики, будни
    отличаются от выходных (тест на форму, без хрупких порогов).
  • Масштаб среднего дня — порядок ~50 тыс. событий при полном мире;
    здесь проверяется порядок величины: итоговое число сложится после
    торговых событий (#40) и ляжет в манифест.
  • Идентификаторы (WatchID, VisitID, ClientID) — UInt64 ниже 2^53.
  • Правила резки визитов задокументированы.
  • Термин «день-функция» добавлен в CONTEXT.md, если его там ещё нет.

Границы

  • Торговые события (add_to_cart, purchase) — следующий тикет.
  • Сериализация в JSON — тикет проигрывателя; здесь события как данные.

Сначала прочитать

  • docs/specs/2026-08-01-generator.md — разделы 1, 2, 6.
  • docs/specs/2026-07-30-stand-v2-realism.md — разделы 1.1–1.2.

Проверка

  • make test
Part of #4. ## Цель День-функция, часть первая: (зерно, D) → упорядоченный поток pageview. Суточные волны, сессии, поля страницы, атрибуции, браузера, устройства и гео — трафиковая половина широкого события. ## Что войдёт - Суточный профиль: ночной провал, обеденный и вечерний пики (до ~2× среднего), различие будней и выходных. - Сессии: 30-минутный таймаут, `VisitID` от генератора, резка по границе модельных суток; правила документируются — лаба сессий сверяет себя по ним. - Поля pageview: URL/Referer/Title/UTM/LastTrafficSource/HasGCLID/YCLID, браузер/ОС/устройство/экран, IP и регионы. Справочники — таблицы-литералы или посточный фейкер, но только для справочников, не в горячем цикле; выбор библиотеки — здесь (спека генератора, раздел 6). - Векторная генерация numpy (PCG64); посточный цикл — только цепочки сессий; целочисленная случайность. ## Критерии приёмки - [x] Все 47 полей присутствуют в каждом событии: «пусто» — пустой массив, пустая строка или 0, а не отсутствие ключа. Торговые поля пустые по смыслу — их заполнит следующий тикет. - [x] Детерминизм: два вычисления дня D с одним зерном — идентичные события; день N+1 не меняет события дней 1…N (тесты). - [x] Волны видны: часовые счётчики дня показывают провал и пики, будни отличаются от выходных (тест на форму, без хрупких порогов). - [x] Масштаб среднего дня — порядок ~50 тыс. событий при полном мире; здесь проверяется порядок величины: итоговое число сложится после торговых событий (#40) и ляжет в манифест. - [x] Идентификаторы (`WatchID`, `VisitID`, `ClientID`) — UInt64 ниже 2^53. - [x] Правила резки визитов задокументированы. - [x] Термин «день-функция» добавлен в CONTEXT.md, если его там ещё нет. ## Границы - Торговые события (`add_to_cart`, `purchase`) — следующий тикет. - Сериализация в JSON — тикет проигрывателя; здесь события как данные. ## Сначала прочитать - docs/specs/2026-08-01-generator.md — разделы 1, 2, 6. - docs/specs/2026-07-30-stand-v2-realism.md — разделы 1.1–1.2. ## Проверка - `make test`
ddmitry added the ready-for-agent label 2026-08-01 21:20:01 +03:00
ddmitry added a new dependency 2026-08-01 21:20:06 +03:00
Author
Owner

Решения при исполнении (2026-08-02)

Приняты владельцем до реализации; в спеку генератора (раздел 9) уезжают тем же PR.

1. Каталог товаров переезжает сюда из #40. У карточки товара есть Title — это имя
товара, а имена живут в каталоге: карточка без каталога невозможна. Без карточек же #39
сделал бы примерно половину трафика (критерий «итоговое число сложится после торговых
событий» предполагает добавку, а не удвоение), а подпоток TRAFFIC отдал бы содержание
трафика торговому подпотоку. Файл data/catalog/products.csv (sku, name, category, brand,
price) заводится здесь; #40 получает готовый — его пункт и критерий про каталог снимаются.

2. Ассортимент — непродовольственная розница: товары для дома, текстиль, посуда, мелкая
бытовая техника, детское, одежда и обувь; ~150–200 sku. Довод не вкусовой: числа мира уже
приняты под этот профиль — «цикл повторной покупки магазина — месяцы», 75% одноразовых кук,
3–4 возврата, конверсия ~2% на сессию. Продуктовая сеть требовала бы других чисел (возврат
раз в неделю, корзина в двадцать позиций), то есть переоткрытия #38.

3. География — один регион присутствия: город-миллионник, города своего региона и тонкий
хвост остальной страны, а не «топ городов России». Рабочий выбор — Поволжье, центр Самара
(пояс UTC+4).

4. Модельные сутки считаются в часовом поясе счётчика, как в выгрузке Метрики:
EventDate — дата в поясе счётчика, UTCEventTime — абсолютная метка. Тогда шов суток
всегда приходится на местную полночь и не режет утренние сессии, а регион мира выбирается
свободно. Суточная волна задаётся в местном времени посетителя: у большинства это пояс сети,
у хвоста свой — пик слегка размазывается, как в жизни. Следствие для соседних этапов:
toDate(UTCEventTime) ≠ EventDate. Это записывается в описание выгрузки и комментарием в
DDL, иначе сторона хранилища выведет дату сама и разойдётся на несколько часов данных.

5. Паспорт куки — постоянная часть мира, а не поведение дня. Браузер, ОС, устройство,
экран и город у куки одни и те же во всех её визитах: кука — это браузер на устройстве.
Держит их план состава: когорта бросает куке паспорт (номер профиля устройства и номер
города), день-функция разворачивает его в поля события. Довод против вывода паспорта из
ClientID арифметикой: про двухкуковые пары знает только план, а два города у одного
человека — ложь в данных; пара получает один город и разные устройства («телефон и
ноутбук», мастер-спека, раздел 5). Плата: Cohort и DayAudience из #38 прирастают
массивами. Броски приписываются последними, поэтому измеренные числа канонического мира не
двигаются — проверяется прогоном счётчиков до и после, а не на глазок. Ленивая форма плана
не страдает: паспорт — константа куки, истории не требует.

6. Справочники — таблицы-литералы рядом с конфигурацией мира. Faker и mimesis не
подключаются: нам нужны веса долей и связки (город → id региона → часовой пояс; телефон →
Safari → iOS → размер экрана), а их фейкер не даёт — таблицу мы напишем всё равно. Новая
зависимость при этом молча меняла бы мир при обновлении словарей.

7. Гео-id — настоящие идентификаторы Яндекса, если подтвердятся первоисточником
(проверка идёт отдельно); иначе свои с прямым комментарием «id наши, не яндексовы».
Выдуманные по памяти числа не годятся ни в каком случае.

8. IP-адреса — нероутируемые диапазоны: документационные (RFC 5737) и benchmark
(198.18/15) по ломтю на город, мобильным — CGNAT 100.64/10, как у настоящих операторов.
Правдоподобные публичные адреса принадлежат живым организациям, и в учебных данных им не
место; условность объясняется комментарием.

9. Словарь: «визит» остаётся за сессией и полем VisitID. В плане состава одноимённое
понятие переименовывается в «день активности» — правка докстрингов plan.py, имён двух
полей Cohort и статьи CONTEXT.md тем же PR.

Хвост для #42: паспорт мира в манифесте (зерно и версия генератора) файл каталога не
накрывает — правка цены в CSV меняет мир молча. Манифесту нужен хеш каталога.

## Решения при исполнении (2026-08-02) Приняты владельцем до реализации; в спеку генератора (раздел 9) уезжают тем же PR. **1. Каталог товаров переезжает сюда из #40.** У карточки товара есть `Title` — это имя товара, а имена живут в каталоге: карточка без каталога невозможна. Без карточек же #39 сделал бы примерно половину трафика (критерий «итоговое число сложится после торговых событий» предполагает добавку, а не удвоение), а подпоток `TRAFFIC` отдал бы содержание трафика торговому подпотоку. Файл `data/catalog/products.csv` (sku, name, category, brand, price) заводится здесь; #40 получает готовый — его пункт и критерий про каталог снимаются. **2. Ассортимент — непродовольственная розница:** товары для дома, текстиль, посуда, мелкая бытовая техника, детское, одежда и обувь; ~150–200 sku. Довод не вкусовой: числа мира уже приняты под этот профиль — «цикл повторной покупки магазина — месяцы», 75% одноразовых кук, 3–4 возврата, конверсия ~2% на сессию. Продуктовая сеть требовала бы других чисел (возврат раз в неделю, корзина в двадцать позиций), то есть переоткрытия #38. **3. География — один регион присутствия:** город-миллионник, города своего региона и тонкий хвост остальной страны, а не «топ городов России». Рабочий выбор — Поволжье, центр Самара (пояс UTC+4). **4. Модельные сутки считаются в часовом поясе счётчика**, как в выгрузке Метрики: `EventDate` — дата в поясе счётчика, `UTCEventTime` — абсолютная метка. Тогда шов суток всегда приходится на местную полночь и не режет утренние сессии, а регион мира выбирается свободно. Суточная волна задаётся в местном времени посетителя: у большинства это пояс сети, у хвоста свой — пик слегка размазывается, как в жизни. Следствие для соседних этапов: `toDate(UTCEventTime) ≠ EventDate`. Это записывается в описание выгрузки и комментарием в DDL, иначе сторона хранилища выведет дату сама и разойдётся на несколько часов данных. **5. Паспорт куки — постоянная часть мира, а не поведение дня.** Браузер, ОС, устройство, экран и город у куки одни и те же во всех её визитах: кука — это браузер на устройстве. Держит их план состава: когорта бросает куке паспорт (номер профиля устройства и номер города), день-функция разворачивает его в поля события. Довод против вывода паспорта из `ClientID` арифметикой: про двухкуковые пары знает только план, а два города у одного человека — ложь в данных; пара получает один город и разные устройства («телефон и ноутбук», мастер-спека, раздел 5). Плата: `Cohort` и `DayAudience` из #38 прирастают массивами. Броски приписываются последними, поэтому измеренные числа канонического мира не двигаются — проверяется прогоном счётчиков до и после, а не на глазок. Ленивая форма плана не страдает: паспорт — константа куки, истории не требует. **6. Справочники — таблицы-литералы** рядом с конфигурацией мира. Faker и mimesis не подключаются: нам нужны веса долей и связки (город → id региона → часовой пояс; телефон → Safari → iOS → размер экрана), а их фейкер не даёт — таблицу мы напишем всё равно. Новая зависимость при этом молча меняла бы мир при обновлении словарей. **7. Гео-id — настоящие идентификаторы Яндекса**, если подтвердятся первоисточником (проверка идёт отдельно); иначе свои с прямым комментарием «id наши, не яндексовы». Выдуманные по памяти числа не годятся ни в каком случае. **8. IP-адреса — нероутируемые диапазоны:** документационные (RFC 5737) и benchmark (198.18/15) по ломтю на город, мобильным — CGNAT 100.64/10, как у настоящих операторов. Правдоподобные публичные адреса принадлежат живым организациям, и в учебных данных им не место; условность объясняется комментарием. **9. Словарь: «визит» остаётся за сессией** и полем `VisitID`. В плане состава одноимённое понятие переименовывается в «день активности» — правка докстрингов `plan.py`, имён двух полей `Cohort` и статьи CONTEXT.md тем же PR. **Хвост для #42:** паспорт мира в манифесте (зерно и версия генератора) файл каталога не накрывает — правка цены в CSV меняет мир молча. Манифесту нужен хеш каталога.
ddmitry self-assigned this 2026-08-02 14:29:41 +03:00
Author
Owner

Сделано и проверено (2026-08-02)

Реализация закрыта, ветка feat/39-day-function, коммит в ней.

Проверка: 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 с.

Ревью: две слепые линии — по коду и по задаче с вкусом. Десять находок (1 MAJOR, 8 MINOR, 1 NIT), все закрыты и перепроверены авторами находок. MAJOR: сторож детерминизма не замечал ни порядка колонок, ни швов page/product для #40 — теперь замечает, проверено мутацией. Ещё одна находка того же класса: тест правил нарезки доказывал только склейку визитов, но не разрез, — теперь доказывает обе стороны.

Решения владельца при приёмке:

  • визит с обещанным планом заказом не режется полуночью, а сдвигается назад — принятое ограничение модели, цена названа в докстринге day.py (около 24 визитов в день из ~9,5 тыс.);
  • ParsedParamsKey1 остаётся пустым; наполнение вариантом A/B-теста вынесено в тикет #47;
  • недельная волна доходит до трафика через дневную аудиторию, вторым множителем не применяется; размах ±7% против ±10% у притока. Тексты, обещавшие обратное, исправлены.

Каталог data/catalog/products.csv заведён здесь (решение 1): 180 позиций, шесть категорий. Из #40 пункт про каталог снят.

Сделано и проверено (2026-08-02) Реализация закрыта, ветка `feat/39-day-function`, коммит в ней. Проверка: `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 с. Ревью: две слепые линии — по коду и по задаче с вкусом. Десять находок (1 MAJOR, 8 MINOR, 1 NIT), все закрыты и перепроверены авторами находок. MAJOR: сторож детерминизма не замечал ни порядка колонок, ни швов `page`/`product` для #40 — теперь замечает, проверено мутацией. Ещё одна находка того же класса: тест правил нарезки доказывал только склейку визитов, но не разрез, — теперь доказывает обе стороны. Решения владельца при приёмке: - визит с обещанным планом заказом не режется полуночью, а сдвигается назад — принятое ограничение модели, цена названа в докстринге `day.py` (около 24 визитов в день из ~9,5 тыс.); - `ParsedParamsKey1` остаётся пустым; наполнение вариантом A/B-теста вынесено в тикет #47; - недельная волна доходит до трафика через дневную аудиторию, вторым множителем не применяется; размах ±7% против ±10% у притока. Тексты, обещавшие обратное, исправлены. Каталог `data/catalog/products.csv` заведён здесь (решение 1): 180 позиций, шесть категорий. Из #40 пункт про каталог снят.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Blocks
#40 Торговые события
ddmitry/clickstream-data-platform
Depends on
Reference: ddmitry/clickstream-data-platform#39