ТЗ. Антифрод-сервис топливного процессинга FuelProcessing

Проект
FuelProcessing (Wellsoft), прод — сервер oktane (141.101.196.65)
БД
PostgreSQL fuel_prod1, 127.0.0.1:5432 на том же хосте
Стек приложения
.NET (legacy net5 + модули net8), EF Core; Docker-compose /home/wellsoft/projects/fuel_processing
Источники фактов
db_schema_digest.md (схема, снята с прода 2026-07-24), checks_audit.md (детекторы D-01…D-14)
Дата / автор
2026-07-24, архитектура
Статус
Черновик на согласование

Документ не переписывает SQL детекторов — они зафиксированы на странице «Детекторы», здесь ссылки по номерам D-XX.

1. Цели и метрики успеха

Проблема. За июнь–июль 2026 прод пережил три инцидента класса «порча / слив лимитов», каждый вычислен вручную постфактум:

Цель — превратить ручной постфактум-разбор в автоматическое обнаружение с приоритизацией по риску.

Метрики (measurable success criteria)

МетрикаЦелевое значение MVP
MTTD операционных инцидентов (порча лимитов, D-14)≤ 1 час от события
MTTD карт-скоринг детекторов (поведенческие)≤ 24 ч (ночной прогон); опция — каждые 4 ч
Recall на известных инцидентах (бэктест)≥ 80% карт/событий из размеченных инцидентов попадают в алерты
Precision «красных» алертов (score ≥ 75)≥ 60% (подтверждённый фрод среди красных) после калибровки
Ложные срабатывания≤ 5% карт в окне помечены алертом
Покрытие ущерба≥ 70% рублей подтверждённого фрода флагуются сервисом до ручного обнаружения
Ключевой критерий готовности к продуреальный кейс фрода 07/2026 (≈3,57 млн ₽ net, ядро 7 клон-карт) ловится за ≤ 14 дней при ≤ 5% карт в ложняках (см. §7)

Метрики без калибровки — это гипотезы; их арбитр — бэктест (§7), а не декларация.

2. Скоуп MVP и не-скоуп

В скоупе MVP

НЕ в скоупе MVP (backlog)

3. Варианты архитектуры

Три варианта — от самого лёгкого к тяжёлому, с трейд-оффами.

(а) SQL-джобы + Metabase/Grafana-алерты, без нового сервиса

Cron/pgAgent гоняет детекторы как SQL, Metabase шлёт threshold-алерты в Mattermost.

Плюсы

  • Дни на запуск, ноль нового кода/деплоя, Metabase уже ходит в fuel_prod1 read-only.

Минусы

  • Нет агрегации скоринга по карте между детекторами (Metabase алертит по одному запросу, не суммирует 14 сигналов в 0–100).
  • Нет подавления дублей, whitelist, workflow разбора (new→investigating→confirmed).
  • Нет обратной связи для калибровки.
  • Каждый запрос в лоб = seq scan 2.3 ГБ (нет индекса по CreateTime).

Вывод: не тянет карт-скоринг и разбор. Но D-14 (простой порог по всплеску изменений лимитов) на этом стеке живёт отлично — забираем как стопгап.

(в) Полноценный streaming через ClickHouse/otel-стек на logan

CDC из Postgres (Debezium/логическая репликация) → Kafka → materialized views в ClickHouse на logan (dedic-2), где уже живёт otel-стек fuel.

Плюсы

  • Суб-минутная задержка, колоночная агрегация перцентилей на больших окнах.

Минусы

  • Требования realtime нет (цель — часы/≤14 дней, не секунды).
  • CDC — тяжёлая новая конечность (Debezium, топики, DLQ, backfill, реконсиляция удалений из BillingCheckService.RemoveRange).
  • otel-ClickHouse на logan заточен под телеметрию (трейсы/доменные события), смешивать транзакционный фрод-анализ — архитектурный конфликт и кросс-серверная зависимость.
  • Over-engineering для 4.9M строк.

Вывод: отклонить для MVP. Держать в уме как scale-out, если объём/латентность вырастут на порядок.

Рекомендация. Вариант (б) как ядро, с D-14-стопгапом из варианта (а) на Metabase в первую же неделю (дешёвая защита от рецидива порчи лимитов, пока сервис пишется). ClickHouse-стриминг (в) — явно отложить.

4. Подключение к данным

4.1. Read-only роль PostgreSQL

Отдельная роль antifraud_ro — принцип наименьших привилегий:

4.2. Где живёт витрина — схема antifraud внутри fuel_prod1

Рассмотрены три варианта размещения:

ВариантВердикт
Схема antifraud в fuel_prod1ВЫБРАНО. Детекторы джойнят живые словари без репликации; запись изолирована грантами.
Отдельная БД fuel_antifraudОтклонено: Postgres не умеет cross-database join, пришлось бы реплицировать Cards/Limits/GasStations/Region и следить за их свежестью.
ClickHouse на loganОтклонено: кросс-серверная зависимость, otel-стек занят телеметрией (см. §3-в).

Обоснование выбора. Часть детекторов живёт не на FinanceTransaction, а на мутирующих словарях: D-08 (Limits), D-12 (Cards), D-14 (Limits.LastModifed), D-04 (GasStations/Region). Ключевое: D-14 по смыслу ловит всплеск изменений Limits в конкретный момент — ночной снапшот такое пропустит структурно. Отдельная БД заставила бы реплицировать эти таблицы и всё равно проиграла бы по свежести. Схема в той же БД даёт джойн живых словарей и запуск SQL из checks_audit.md почти вербатим (только schema-qualify ft_recent). Цена — «замусоривание» прод-БД (там и так 187 таблиц с легаси Metabase/Quartz) — косметическая и принимается.

Практическое следствие: не все 14 детекторов читают только витрину. Поведенческая нагрузка (2.3 ГБ FinanceTransaction) идёт через материализованную antifraud.ft_recent (последние 90 дней, джойн АЗС/региона). Point-in-time детекторы D-12 и D-14 ходят напрямую в живые public.Cards / public.Limits (это дёшево: Cards 113K, Limits 650K).

4.3. ETL — инкрементально по Id, НЕ по CreateTime

Критическое ограничение схемы: по FinanceTransaction.CreateTime индекса нет — «за последние N дней» = seq scan 2.3 ГБ. Поэтому:

4.4. BRIN-индекс по CreateTime — опция (не основной путь)

CREATE INDEX CONCURRENTLY ... USING brin ("CreateTime") — дешёвый по размеру, любит корреляцию порядка вставки. Порядок Id ≈ порядок CreateTime (несмотря на naive-TZ разброс, insert-order грубо трекает время), поэтому BRIN-блоки хорошо прунятся.

5. Детекторы и движок правил

Ядро — 14 детекторов D-01…D-14 из checks_audit.md (SQL там, здесь не дублируется). Классы:

Движок правил

6. Алертинг и workflow разбора

7. Бэктест и калибровка

8. Безопасность

9. Этапы, оценка трудоёмкости, риски

ЭтапСодержаниеОценка (чел-дни)
E0. D-14 стопгапSQL D-14 в Metabase-on-oktane + алерт в Mattermost1–2
E1. Фундамент данныхроли antifraud_ro/rw, схема antifraud, pg_hba/timeouts, витрина ft_recent + инкрементальный ETL по Id, реконсиляция удалений3–4
E2. Движок детекторовпорт D-01…D-14 на витрину/живые словари, YAML-конфиг, скоринг, дедуп, whitelist4–6
E3. Алертинг + workflowкарточки Mattermost, статусы разбора, таблицы alerts, обратная связь2–3
E4. Бэктест-харнессразворот бэкапа, «as of» прогон, разметка 3 инцидентов, отчёт precision/recall2–3
E5. Калибровкаподгон порогов до критерия §72–4
E6. Прод-выкаткадеплой в compose, мониторинг сервиса, runbook1–2
Итого MVP15–24 чел-дня
Опция: BRIN-индекссборка CONCURRENTLY в окне0.5

Риски

  1. Region.TimzeZone — единицы/корректность. Ночные детекторы (D-01) зависят от смещения; кривые данные → шум. Митигация: проверить на реальных данных до калибровки, детектор с параметром «доверять TZ / фолбэк».
  2. Мусорные text-координаты (D-04): часть АЗС без/с битыми координатами → NULL, impossible-travel не считается. Митигация: метрика покрытия координат, детектор скипает NULL честно.
  3. Дрейф Id-watermark из-за удалений (BillingCheckService). Митигация: суточная реконсиляция (§4.3).
  4. Аудит-лог слеп — историю изменений лимитов внутри БД восстановить нельзя. Митигация: D-14 по Limits.LastModifed + зеркало LimitVinkHistory/ApiLimitInfo, не по несуществующему CardChangeLogs.ChangeType.
  5. Пороги переносятся с бэктеста на «живой» прод неидеально (распределения дрейфуют). Митигация: обратная связь §6, периодическая рекалибровка.
  6. Нагрузка на прод от backfill/BRIN. Митигация: только окно низкой нагрузки + statement_timeout (§10).

10. Эксплуатация

Приложение. Связь детекторов и таблиц (быстрая карта)

ДетекторИсточникЧитает витрину / живое
D-01ночь вне профиля — FinanceTransaction + Region.TimzeZoneвитрина
D-02объём вне нормы — FinanceTransactionвитрина
D-03velocity — FinanceTransaction (LAG)витрина
D-04impossible travel — FinanceTransaction + GasStations (coords)витрина (очищенные coords)
D-05дубли номеров ВИНК — FinanceTransactionвитрина
D-06круглые/стабильные суммы — FinanceTransactionвитрина
D-07смена типа топлива — FinanceTransactionвитрина
D-08добор лимита — Limits (+ FinanceTransaction для полной версии)живые Limits + витрина
D-09концентрация на АЗС — FinanceTransactionвитрина
D-10карта между компаниями — FinanceTransactionвитрина
D-11дрейф поведения — FinanceTransaction (weekly)витрина
D-12мёртвая карта — FinanceTransaction ⋈ Cardsвитрина ⋈ живые Cards
D-13offline/ручные — FinanceTransactionвитрина
D-14порча лимитов — Limits.LastModifed (+ LimitVinkHistory)живые Limits (point-in-time)

Черновик на согласование, 2026-07-24. Детекторы — D-01…D-14, схема БД — fuel_prod1, живой пример — кейс 3,5 млн.