"""Конфигурация мира: все числа, которыми задан модельный магазин. Модуль — приглашение крутить: поменяйте число, пересоберите снимок и посмотрите, что стало с данными. Правка любой константы здесь — смена мира, поэтому чек манифеста честно покраснеет: манифест сторожит только канонический мир, свои миры менти собирает без его гарантий (спека генератора, раздел 9). Числа решены спекой и связаны между собой; связки сторожат тесты `test_world.py`, чтобы правка одного числа не рассыпала вывод соседнего. Здесь только числа — как в контракте схемы, никакого поведения; длинные таблицы записаны коротко, но остаются таблицами. """ from datetime import date # Счётчик стенда: сайт один, номер — константа мира. COUNTER_ID = 42150607 # Часовой пояс счётчика, минуты от UTC: Самара, UTC+4. Модельные сутки # считаются в этом поясе, как в выгрузке Метрики: `EventDate` — дата в поясе # счётчика, `UTCEventTime` — абсолютная метка. Отсюда следствие, о котором # сторона хранилища должна знать заранее: `toDate(UTCEventTime)` ≠ `EventDate` # у ночных событий (спека генератора, раздел 9). COUNTER_TIMEZONE_MINUTES = 240 # D0 — первый день оси модельного времени, понедельник. Реальный календарь в # модели не участвует: дата нужна лишь затем, чтобы дни оси легли в # `EventDate`/`UTCEventTime` конкретными числами. От даты запуска мир не # зависит — иначе манифест перестал бы быть воспроизводимым. ORIGIN = date(2026, 6, 1) # Приток: сколько новых людей приходит в мир в средний день. Каждый приводит # свою куку, поэтому число это же — приток кук; вторые куки двухкуковых пар # добавляют к нему меньше процента. DAILY_INFLUX = 3_800 # Недельная волна мира, проценты от среднего: понедельник … воскресенье. # Волна задана притоку — по ней в мир приходят новые люди. Трафик наследует # её через дневную аудиторию, а не вторым умножением (довод — у суточной # волны выходного дня), поэтому доля новичков по дням недели ровная, а # недельный размах трафика выходит мягче притока: ±7% против ±10%. В сумме # ровно 700: за неделю средний день остаётся средним. WEEKLY_PROFILE_PERCENT = (105, 108, 107, 105, 95, 88, 92) # Разброс притока изо дня в день, проценты: ровный приток выдал бы себя в # первом же графике по дням. INFLUX_JITTER_PERCENT = 5 # Доля одноразовых кук: пришли раз и не вернулись — как в живом трафике. ONE_SHOT_PERCENT = 75 # Сколько раз возвращается кука, которая вернулась хоть раз: веса для 1, 2, # 3 … возвратов. В среднем выходит 3–4 возврата; вместе с одноразовыми это # ≈1,9 активного дня на куку — отсюда дневная аудитория 6–8 тыс. при # притоке 3 800 (спека, разделы 5 и 9). RETURN_COUNT_WEIGHTS = (25, 20, 15, 12, 9, 7, 5, 4, 2, 1) # Хвост возвратов: окно активности человека от его первого дня, общее на # обе его куки. За краем окна кука не возвращается. Оно же — глубина # предыстории: столько когорт живёт до D0, чтобы дневная аудитория была на # полке с самого первого дня. RETURN_TAIL_DAYS = 90 # Профиль возвратов по дням от первого дня куки: почти всё в первую неделю, # дальше тонкий хвост до края окна — повторные покупки в магазине случаются # и через месяцы. Профиль затухает к краю, поэтому обрыв на нём в данных не # виден. Читается по парам «сколько дней — с каким весом»; дней в сумме # ровно `RETURN_TAIL_DAYS`. RETURN_DELAY_WEIGHTS = tuple( weight for days, weight in ((3, 100), (7, 40), (20, 8), (60, 1)) for _ in range(days) ) # Доля покупателей среди людей когорты. Считается людьми, не куками: человек # с двумя куками — один покупатель. Число плана, а не торгового поведения: # без него не отобрать двухкуковые пары. BUYER_PERCENT = 5 # Доля покупателей, у которых заведётся вторая кука (мастер-спека, раздел 5). # Такой паре план назначает по гарантированному заказу с каждой куки — на # этом стоит лаба про склейку личности. PAIRED_BUYER_PERCENT = 15 # Первый рычаг метки покупателя: помеченный дольше живёт и чаще возвращается. # Одноразовым он бывает много реже прочих, а возвращается вдвое чаще — # отсюда кука, которая ходит неделями. Без этого рычага «постоянный # покупатель» в данных не читается: метки в событии нет и не будет # (кликстрим анонимен), а кука живёт меньше двух визитов за снимок — второй # покупке негде случиться (спека генератора, раздел 9). BUYER_ONE_SHOT_PERCENT = 35 BUYER_RETURN_COUNT_WEIGHTS = (10, 11, 12, 12, 11, 10, 9, 7, 5, 4, 3, 2) # --- Числа дня: суточная волна, визиты, воронка --------------------------- # Суточная волна буднего дня: проценты от среднего часа, от 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 тыс. добирают торговые события. 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 # Воронка: доля визитов, дошедших до корзины, и доли следующих шагов от # предыдущего. Произведение — конверсия визита в оформленный заказ # (спека генератора, раздел 9). Гарантированные планом заказы двухкуковых # пар проходят воронку целиком независимо от этих долей. CART_PERCENT = 6 CHECKOUT_OF_CART_PERCENT = 45 CONFIRMATION_OF_CHECKOUT_PERCENT = 55 # Второй рычаг метки покупателя: помеченный отличается на обоих шагах # воронки — и до корзины доходит чаще, и бросает её реже. В жизни # различаются оба: кто пришёл смотреть, тот и кладёт реже, и до конца # доводит реже; один шаг дал бы половину картины. Шаг подтверждения общий: # оплата — про магазин, а не про склонность покупать. BUYER_CART_PERCENT = 18 BUYER_CHECKOUT_OF_CART_PERCENT = 60 # --- Числа торговых событий: корзина, заказ, деньги ------------------------ # Торговое событие встаёт на несколько секунд позже своей страницы: корзина # позже карточки, покупка позже подтверждения. Задержка короче самой # короткой паузы между страницами — иначе событие корзины обогнало бы # страницу, на которой посетитель его нажал. TRADE_DELAY_SECONDS = (2, 8) # Кто и что кладёт в корзину: доля открытых карточек, которая до неё дошла. # Оба ряда читаются по уровням спроса товара (`catalog.DEMAND_LEVELS`): # магнит, обычный, залёживается. # # Первый ряд — визит, дошедший до страницы корзины. Он пришёл покупать и # кладёт почти всё, что сравнивал; уровень решает, что именно из сравненного # он выберет, и залежавшийся товар проигрывает соседям по вкладке. Верх ряда # высок намеренно: опусти его — и корзина такого визита схлопнется до одной # позиции, а заказ до одного товара. # # Второй ряд — визит, который до страницы корзины не дошёл. Кладёт он редко, # и магнит уходит у него из карточки втрое чаще обычного товара и вшестеро # чаще залежавшегося: случайного посетителя решает соблазнить сам товар, # больше решать нечему. Событий при этом выходит почти поровну — магнитов # 47% таких добавлений, обычных 42%: обычных в каталоге вдвое с лишним # больше, и разница уровней живёт в доле открытых карточек, а не в счёте # событий. Нулю в этом ряду места нет: обнулить уровень значило бы вернуть # по его товарам ровно то равенство «положил — открыл корзину», ради # которого правка и делалась. # # Спрос растянут во втором ряду сильнее, чем в первом, и это не подгонка: # намерение заслоняет товар, поэтому привлекательность видна там, где # намерения нет. SHOPPING_ADD_PERCENT = (98, 94, 55) BROWSING_ADD_PERCENT = (6, 2, 1) # Доля позиций корзины, которые остались брошенными: заказ уже корзины. # Иначе событие корзины не рассказывало бы ничего сверх покупки — заказ был # бы её точной копией, и сравнивать было бы нечего. Пустым заказ не бывает: # если брошены все позиции, одна остаётся (спека генератора, раздел 9). # Число опущено с 15% вместе с отвязкой корзины от страницы корзины: корзина # стала уже (1,7 позиции вместо 2,4), и прежние 15% чаще выносили её целиком, # а спасённая позиция возвращала заказ к одному товару. ABANDONED_POSITION_PERCENT = 10 # Сколько штук одного товара берут: веса для 1, 2, 3 штук. Обычно одна, # изредка две-три — непродовольственная розница. ITEM_QUANTITY_WEIGHTS = (85, 11, 4) # Валюта магазина: один регион присутствия — одна валюта. CURRENCY = "RUB" # Доля заказов с промокодом. COUPON_PERCENT = 20 # Промокоды: код и скидка в процентах. Таблица — число мира, а не выдумка # бэкенда: этап 3 берёт её готовой и обязан дать заказу с кодом скидку, # иначе данные соврут. В клиентскую выручку скидка не входит — код на сайте # знает корзину, а не итог расчёта (спека генератора, раздел 9). Цифры в # коде — те же проценты: скидка в рублях потребовала бы второго правила # чтения таблицы, а вместе с ним и второго вида скидки на стороне бэкенда. COUPONS = ( ("VESNA10", 10), ("DOMASHNIY5", 5), ("PERVYY15", 15), ("UYUT7", 7), ) # Цели счётчика: корзина и покупка. Цели дублируют торговые события — в бою # так и бывает (мастер-спека, раздел 1.2). GOAL_CART_ID = 42150001 GOAL_PURCHASE_ID = 42150002