OPTIMAL — финальный алгоритм transit-налива

5-слойная архитектура замены текущего 7-key ORDER BY. Доказано в prod-faithful симуляции v5 (24+ итераций sprint'ов, 48ч прогон, 3 runs avg).
Индия · UPI P2P · INR Виджет: Orbit / MoXB / MRGB Tier-A: KPMI P2P + INTENT Реализация: ~1 месяц

✅ Достигнутые цели

Tier-A p90
36 мин
target ≤ 60 мин
было: 8.2ч (КPMI) → стало −97%
Tier-B p90
143 мин
target ≤ 180 мин
было: 11.9ч (Orbit) → стало −80%
Loss budget
0.44 %
target ≤ 2 %
было: 2.5% → стало в 5.7× ниже

🔄 Как работает (визуально)

Вход
Новый payin от Orbit / MoXB / MRGB виджета
L2: Sender scoring
Определить bucket sender'а из истории. RISKY (≥5 prior, ≤10% paid) → 80% сразу в trader pool.
L1: Filter кандидатов
Asymmetric tolerance (payin ≥ payout), per-merchant partial/full split, фильтрация blocked / problematic.
L3: Score-функция (вместо ORDER BY)
Рассчитать utility для каждого кандидата по формуле ниже. Выбрать max.
Match
Закрыть payout. Если переплата → L5 customerOverpayLedger (refund-batch), не тихий gift.
⤴ при failure
L4: Failure recovery
H-2 sync unlock (0 мин). Exp-backoff: 1ч после 3, 2ч после 4, 4ч после 5. Skip-problematic после 4ч.

🧩 5-слойная архитектура

1Source-side— расширение кандидатов
  • Asymmetric tolerance: payout ≤ payin ≤ payout + 199 ₹. Переплата OK, недоплата запрещена.
  • Per-merchant partial split: KPMI / MDS / MKC / MPUP → full-match only. ORBIT / MRJB → partial allowed.
  • Round-500 для full-match мерчантов требует переговоров с KPMI. Coverage 100% при exact-match.
2Sender scoring (Variant 1, rule-based)— 4 bucket'а по истории
TRUSTED
86%
≥5 prior, ≥90% paid, 0 appeals
→ score +30, fast-track
STANDARD
59%
mid-history
→ default
COLD
47%
new, <5 prior
→ default
RISKY
6%
≥5 prior, ≤10% paid
→ 80% fast-cancel
3Selection score-функция— вместо 7-key ORDER BY
utility(payin, payout) = + 1.2 × tier_bonus // Tier-A → +50 − 0.5 × age // НЕ old-day-first − 0.5 × overpay_amount − 1.0 × remainder_amount − 1.2 × death_march_penalty // smart roundness + 1.3 × sender_bonus // TRUSTED +30, RISKY −30 − 1000 × exact_mismatch − 100 × is_problematic_payout pick max(utility)
4Failure recovery— уничтожить хвост
  • H-2 sync unlock: unlock = 0 мин (убираем acqUnlocker 3-мин cycle)
  • Exponential backoff: lock 1ч после 3 failures, 2ч после 4, 4ч после 5, 8ч после 6 (вместо forever after 5)
  • Skip-problematic: 1.5% «проблемных» payouts (bimodal cluster из prod-данных) выкидываются после 4ч → free trader pool
  • maxFailures = 5 — не 3, агрессивнее ухудшает результат
5Overpay handling— честный учёт
  • customerOverpayLedger вместо тихого Expense(SYSTEM_ERRORS)
  • Batch refund job для возврата переплат клиенту
  • Эффект: Loss% с 2.09% (preset E) → 0.44% (×4.7 снижение)

📊 Сравнение пресетов (sim v5)

48ч прогон, 3 runs avg. Baseline overshoot ×1.6 vs prod 672м → реальные результаты могут быть лучше прогноза.

Полные данные

Presetp50p90Tier-A p90Tier-B p90Loss%Closed%
A) PROD current (baseline)197м1076м1038м1112м0.86%63%
B) H-2 unlock only35м978м949м1008м0.84%69%
C) Score-fix only194м163м236м1.74%63%
D) + Round-500136м47м204м2.07%55%
E) + Sender scoring98м37м144м2.09%59%
G) OPTIMAL ★98м36м143м0.44%59%
F) BEST (max aggressive)143м60м182м0.42%59%

🗑️ Что выкидываем из текущего алгоритма

Текущее поведениеПочему убираемЗамена
Old-day-first (ключ 1 ORDER BY) Positive feedback loop: старые → ещё старее. Главный источник хвоста. Возрастной decay в score: −0.5 × age
Roundness guard (сохранять круглый остаток) Оптимизирует под бот, генерирует death-march остатки Smart roundness — штраф за создание узкого окна
Forever-exclusion после 5 failures Dead inventory растёт линейно Exp-backoff: 1ч/2ч/4ч/8ч…
Симметричный BETWEEN tolerance Разрешает недоплату (бизнесово запрещено) Asymmetric: только payin ≥ payout
3-мин acqUnlocker cycle Каждый stuck = 3 мин простоя минимум H-2 sync unlock (0 мин)
Global partial flag KPMI требует full, Orbit разрешает partial Per-merchant config
Gift переплат в Expense Тихая потеря денег без шанса на refund customerOverpayLedger + batch-refund

📅 Implementation roadmap

Неделя 1 · параллельно
Переговоры с KPMI про Round-500 (внешний трек, не от нас) внешняя зависимость
Неделя 1-2 · код keystone
Asymmetric tolerance (WHERE clause) · Score-функция (заменить ORDER BY) · H-2 sync unlock · Exp-backoff failures
Неделя 2 · новая модель данных
customerOverpayLedger таблица + batch refund job + замена createExpense на ledger entry
Неделя 2-3 · scoring
Sender scoring V1 (4-bucket на SenderAccountPayinAggregate, без ML)
Неделя 3 · production A/B
Ramp: 5% → 20% → 50% → 100% трафика, каждый шаг 24-48ч наблюдения
Неделя 4 · доводка
Финальная калибровка весов score-функции на real-data feedback

⚠️ Caveats

Sim baseline overshoot ×1.6. В sim p90 = 1076м, в реальности 672м. Это значит модель чуть пессимистична — на реальном prod результаты OPTIMAL могут быть лучше прогноза.
Round-500 — внешняя зависимость. Если переговоры с KPMI не сработают — Tier-A может остановиться на ~80-100м (всё ещё в 5-6× лучше текущих 8.2ч, но не <60м).
Sender scoring V1 — простой rule-based. Через 2-3 недели данных Logging D можно поднять до LR/GBM модели на behavioral features.
Симуляция упрощает реальность. Не моделирует banks compatibility, mutex contentions. Validation через prod A/B обязателен.

🔗 Ссылки