SERP Rank Lab
▶ Схема потока данных — самодостаточный HTML, открывается в браузере — без установки и сборки
Ситуация
Метапоисковый сайт не продаёт билеты сам — его доход складывается из партнёрской комиссии или цены за клик, умноженной на то, какую позицию оффер занял на странице результатов. Поэтому роль держится на двух вопросах: как ранжировать офферы и сколько зарабатывает та или иная политика ранжирования за поиск.
Офлайн-метрика релевантности и деньги при этом не совпадают: политика, которая выглядит лучше по NDCG, может приносить меньше комиссии.
Задача
От меня требовалось построить субстрат измерения для этой поверхности: честный (не смещённый позицией) CTR, ranking-метрики и ридаут A/B-эксперимента поверх политики ранжирования — и связать их в один воспроизводимый пайплайн.
Действия
Слои
| Слой | Файлы | Роль |
|---|---|---|
| Данные | src/data_ingest.py, sql/01_normalize.sql |
booking-фид → строка на сегмент; нормализация заказов и событий |
| Сессии | src/sessions.py, sql/02_sessions.sql |
правило сессии: 30 минут неактивности + граница суток |
| Выдача | src/serp_sim.py |
синтетические страницы над реальными парами маршрут/цена; PBM-модель клика; экономика CPA/CPC |
| Оценка | src/ranking_eval.py, sql/03_funnel.sql, sql/04_revenue_per_search.sql |
несмещённый CTR, NDCG/MRR, interleaving, revenue per search |
| Эксперимент | src/stats.py, src/experiment.py |
SRM, A/A, delta-method, CUPED, mSPRT, peeking |
| Поверхность | app.py, run.py |
Streamlit-дашборд; единая точка входа пайплайна |
Методы
- Позиционное смещение (PBM). Вероятность просмотра падает с позицией как
(1/k)^η; клик = examined × relevance. - Несмещённый CTR. Inverse-propensity weighting по Horvitz–Thompson, кривая известна или подобрана EM.
- Ratio-метрики. Revenue per search — отношение сумм, поэтому дисперсию считает delta-method, а не наивный per-unit t-test.
- Последовательное тестирование. Фиксированный горизонт против всегда-валидного mSPRT для количественной оценки цены подглядывания.
Запуск
uv sync --all-groups
uv run python run.py --stage all # ingest → sessions → serp → sql → rank → experiment → business
uv run pytest -q # 110 тестов (108 pass, 2 skip без данных-соседа)
uv run streamlit run app.py # дашборд на :8501
Результат
| Проверка | Результат | Чтение |
|---|---|---|
| NDCG@10 против revenue/search | relevance-first: 1.000 / 868.9, revenue-first: 0.932 / 1,196.6 | политики движутся в разные стороны — revenue на 38% выше |
| Наивный против позиционно-свободного CTR | 0.163 против 0.629 | занижение примерно вчетверо |
| SRM против плана 90/10 | χ² = 0.119, p = 0.730 | сплит корректен |
| Ratio-метрика, delta-method против наивной | SE 69.0 против 88.5 | наивный SE шире в 1.28 раза |
| Peeking, фиксированный горизонт | FPR 14.5% за десять взглядов | 2.9× от номинала |
| Peeking, mSPRT | FPR 0.25% | всегда-валидный порог держит уровень |
Пайплайн проходит end-to-end из фиксированного seed; 108 тестов проходят, 2 пропускаются без опциональных данных-соседа.
Ограничения
Синтетический слой выдачи похож на реальный трафик только как предположение — настоящего лога показов для этой поверхности нет, поэтому все ranking- и A/B-числа получены на модели, а не измерены. Два «острова» реальных данных (заказы и сегменты бронирования) не имеют внешнего ключа к кликстриму и репортятся отдельно, никогда не сливаясь с моделируемой выдачей. Сырые сторонние данные не коммитятся: в репозитории только загрузчик, генератор выдачи и SQL.
Документация
Частые вопросы
Почему offline-метрика ранжирования плохой релизный гейт?
Политика, максимизирующая NDCG@10, набирает больше кликов, но зарабатывает на 38% меньше за поиск, чем revenue-first. Узкое место — revenue per search, а не офлайн-скор релевантности.
Насколько искажён наивный CTR?
Вчетверо: 0.163 против позиционно-свободного 0.629. Дело в позиционном смещении, а не в метрике; лечится inverse-propensity weighting.
Чем опасны повторные подглядывания?
На фиксированном горизонте false-positive rate растёт до 14.5% за десять взглядов. Всегда-валидный mSPRT держит его на 0.25%, но ценой более широкого порога.
Сколько стоит сплит 90/10?
2.78 раза больше трафика, чем у 50/50, при той же мощности: 55,196 против 19,871 пользователя.
Что показал CUPED на реальных данных?
На i.i.d.-пользователях ковариата не даёт выигрыша (корреляция около нуля), а на коррелированном контроле возвращает теоретические 11.2% снижения дисперсии.