Repository navigation
feat(search): перебор конфигураций политики и эталонное распределение очереди - #5
Merged
Merged
Conversation
data/derived/ (в docs/data.md прямо написано «в git не коммитим») и __pycache__/ висели неотслеживаемыми — один git add -A утащил бы их в историю. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
Документы разошлись с репозиторием: в шпаргалке стояло 211 спеков и 97.8% покрытия, в manual_check — 181 спек, 13 hard-правил и 8 пресетов. Эксперт может открыть репозиторий и сверить, поэтому везде проставлены измеренные значения. Производительность была занижена вчетверо: «≈2 700 заявок/с» — цифра с ноутбука разработки, на Ryzen 7 9700X полный конвейер даёт 10 642 заявок/с на 50 000 и 10 323 на 200 000. Обе цифры теперь с указанием, где мерили. curl localhost:8080 из шпаргалки на Windows не работает: сервис слушает 127.0.0.1, а localhost сначала резолвится в IPv6 ::1 — соединение не устанавливается. Плюс бриф на чекпоинт 2: аудит, разбор задачи и решения, сценарий демо и вопросы к жюри. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
share_deficit — целевая доля по числу заявок, посчитанная в заявках, а не в процентах: недобор = цель × обработано − выдано. Процентная разница одинаково оценивает 10 п.п. на пятой заявке (полоперации) и на пятисотой (полсотни операций), счётная — нет. Это deficit round-robin на допустимом пуле. selection.normalization: damped — третий режим, лечит вырождение pool при двух кандидатах, где любая разница растягивается до 0/1. Разброс в знаменателе увеличен на константу, поэтому цель, почти не различающая кандидатов, и вклад даёт почти нулевой. Обе механики по умолчанию выключены: перебор (docs/search.md) показал, что на этих данных они границу Парето не двигают — выбор упирается в допустимость, а не в закон управления. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
Роутер работает онлайн — по одной заявке, не зная следующих. AssignmentBound решает ту же очередь целиком как транспортную задачу «банк × провайдер» точным потоком минимальной стоимости. У эталона две форы: знает будущее и не считает ёмкости по деньгам, реквизитам и интенсивности. Отвечает на вопрос «а можно ли лучше»: наш жадный роутинг берёт 96–97% того, что берёт всезнающий оптимум, значит переписывать выбор на пакетную оптимизацию ради этих процентов незачем. Дополнительно меряет цену соблюдения долей — разницу между оптимумом со свободными долями и оптимумом с квотами. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
Команда search отвечает на «а нельзя ли настроить лучше» не интуицией, а перебором. Каждая точка — не оценка по модели, а полный прогон роутера на батарее очередей (публичная очередь + синтетические из истории + бэктест), 62 мс на конфигурацию. Пространство: структурные варианты (нормировка pool/absolute/damped × доли absolute/attainable × дневной лимит обычный/с резервированием) × веса целей — база, одиночные цели, аблации, исчерпывающая сетка и случайные точки симплекса, затем покоординатная доводка лучшей. На выходе граница Парето «доли против одобрений» и лидеры по каждой метрике. Фронт достраивается инкрементально и схлопывает совпадающие точки: полный пересчёт после каждого кандидата стоит дороже самого прогона роутера. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
Лучший рычаг для одобрений — не заявленная провайдером conversion_24h, а наблюдаемая проходимость пары «провайдер × банк». Заявленная врёт: payflow объявляет 0.91, по 19 историческим операциям факт 0.47. Против conversion_first на отложенных очередях: конверсия 0.7635 против 0.7622, маржа 172.1 ₽ против 169.9 ₽ на заявку, бэктест +5.25% против +4.71%. Цена та же — доли партнёров не держатся, это политика «максимум денег». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
Главный вывод: дефолтная политика balanced уже на границе Парето — из 113 000 проверенных конфигураций базу доминируют единицы, и все на уровне шума (67.49 → 67.07 п.п. отклонения, конверсия +0.05%). Крутить веса дальше незачем. Узкое место не в политике, а во входных данных: на реалистичном потоке payflow допустим примерно в 2% заявок при цели 35% — дневной лимит выбран на 96.7%, потолок чека 50 тыс. Снижение traffic_percentage с 35% до 10% выигрывает 25 п.п. отклонения без потери конверсии, то есть в шестьдесят раз больше всего перебора весов. Плюс аблации (turnover_min не меняет вообще ничего, cascade_priority и amount_band стоят 4.1 п.п. соблюдения долей ради 0.7% конверсии) и честный разбор двух механик, которые не сработали. Дефолтную политику и режим optimistic перебор не тронул: routing_decisions.json и routing_report.json в корне остались байт в байт прежними. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что это
Отвечаем на вопрос «а можно ли настроить роутер лучше?» не интуицией, а перебором.
Добавлена команда
search, которая гоняет конфигурации политики через настоящий роутер(не модель поверх модели) на общей батарее очередей, и
AssignmentBound, который считаетэталонное распределение той же очереди точным min-cost flow.
Прогнали 113 000 конфигураций в два раунда. Разбор — в
docs/search.md.Главные выводы
balancedуже на границе Парето. Из 113 000 конфигураций базудоминируют единицы, и все на уровне шума: 67.49 → 67.07 п.п. отклонения, конверсия +0.05%.
Подбирать веса дальше бессмысленно.
+6.2% конверсии и +8% маржи. Это выбор бизнеса одной строкой в YAML, а не программиста в коде.
(команда
bound). Переписывать выбор на batch-оптимизацию ради этих процентов незачем.payflowдопустим примерно в 2% заявок при цели 35%: дневной лимит выбран на 96.7% (2.9 из 3 млн),
потолок чека 50 тыс. Снижение
traffic_percentageс 35% до 10% даёт −25 п.п. отклонениябез потери конверсии — в шестьдесят раз больше, чем весь перебор весов.
Ровно это наш отчёт и рекомендовал сам; перебор рекомендацию не опроверг, а измерил.
turnover_minне меняет вообще ничего (подтверждаетgoal_activityи правилоdead_goal), аcascade_priorityиamount_bandстоят 4.1 п.п. соблюдения долей ради0.7% конверсии — осознанный размен, обе цели из ТЗ.
Что добавлено
lib/payout_router/search/Battery,Space,Front,Engine,Metrics,Outcome,Candidateanalytics/assignment_bound.rb+min_cost_flow.rbstrategies/share_deficit.rbselection.normalization: dampedpoolпри двух кандидатахconfig/policies/bank_affinity_first.ymlconversion_firstdocs/search.mddocs/criteria.mdРевизия документации
Сверены с фактическим прогоном все числа во всех
.md: 236 спеков, покрытие строк 96.0%,14 hard-правил, 13 целей, 13 правил рекомендаций, 10 пресетов, 11 политик в
compare,бэктест 72.2 → 75.6, соотношение Ruby/Python 91/9. Раньше в разных файлах стояли 211/213/181 спек,
11/12/13 правил рекомендаций и бэктест «+6.9%», которого давно нет.
Исправлена производительность (в доках стояло ≈2 700 заявок/с с ноутбука разработки, на Ryzen 7 9700X
полный конвейер даёт 10 642 на 50 000 и 10 323 на 200 000) и
curl localhost:8080→127.0.0.1(на Windows
localhostуходит в IPv6::1, и соединение не устанавливается).Удалён
docs/checkpoint2.md— он повторялdocs/checkpoint.mdи уже разошёлся с ним в цифрах.Уникальное перенесено туда, где ему место: «задача простыми словами» и таблица «если что-то
не так» — в README (их читает жюри), новые вопросы к экспертам — в
docs/checkpoint.md§5.Дублирующая таблица критериев из
docs/plan.mdзаменена указателем наdocs/criteria.md.Исправлена ошибка в модели эталона
Первая версия
AssignmentBoundдавала оптимум с квотами выше оптимума без них — невозможныйрезультат. Причина: self-provider был доступен эталону без ограничений, а по модели одобрения он
выглядит лучше всех внешних (нет истории, заявленные 0.95 против откалиброванных 0.62–0.80,
нет лимитов). Квоты выталкивали заявки на него и «улучшали» результат. Теперь эталону разрешено
ровно столько заявок на self-provider, сколько использовал наш роутер — наше распределение заведомо
допустимо в его задаче, и оптимум становится настоящей верхней границей. Инвариант закреплён спеком.
Что честно не сработало
share_deficit— механика строго сильнее процентной, но в одиночку даёт ровно то жераспределение, что
traffic_share: выбор упирается в допустимость, а не в закон управления.В смеси — 1 п.п. Оставлен с нулевым весом, за улучшение не выдаём.
damped— в финал попадает (208 конфигураций из 473), границу Парето не сдвинула.Риск для сдачи: нулевой
Дефолтная политика, режим
simulation.mode: optimisticи форматattemptsне тронуты.bundle exec rake publicперегенерируетrouting_decisions.jsonиrouting_report.jsonбайт в байт такими же, как в
main.Как проверить
🤖 Generated with Claude Code
https://claude.ai/code/session_01N7LBzBD5qBUzAAPsGTB9Nk