v0.9 — Сверка сделок ↔ блокчейн + on-chain виджет

Дата: 2026-05-15 Цель: пункты 1 и 2 из роадмапа после v0.8 — реализовать матчинг raw_blockchain_tx↔deals и добавить on-chain виджет на дашборд.

✅ Что сделано

1. Матчинг raw_blockchain_tx ↔ deals

  • app/jobs/reconcile.py — скоринговый алгоритм:
  • +30 за совпадение направления (in→crypto-give, out→crypto-get)
  • +10 за совпадение актива (USDT-TRC20 ≠ USDT-ERC20)
  • +40 за точное совпадение суммы; +30 если ≤1% diff; +15 если ≤3%; 0 если больше
  • +20/+10/+5 за временное окно (≤1мин / ≤10мин / ≤30мин)
  • Порог матча: score ≥ 70
  • Записывает matched_deal_id в raw_blockchain_tx.
  • Audit-запись на каждый прогон с количеством matched.
  • Автозапуск: tron_sync после ingest вызывает reconcile. Также есть кнопка «Запустить сверку» на /reconciliation для admin/manager.

2. UI /reconciliation полностью переделан

  • 4 KPI: сделок done / без on-chain / блокчейн-tx / tx без сделки.
  • 2 колонки:
  • Слева: сделки done без on-chain подтверждения — потенциально не оформлено
  • Справа: блокчейн-tx без сделки — потенциальный левак
  • Подсветка возраст > 24 часов (красная полоса).
  • Клик по карточке ведёт на сделку или кошелёк.

3. Дашборд: виджет «On-chain активность · сегодня»

  • 3 цифры: количество транзакций, sum(in), sum(out).
  • Время последней синхронизации.
  • Виден только тем, у кого есть доступ к /wallets (admin/manager/accountant).

🧪 E2E проверка (выполнено)

  1. Взял реальную блокчейн-tx: out 1000 USDT-TRC20 от 15:23:36 UTC с Лиман-кошелька (tx_hash 182f55...).
  2. Создал клиента «Recon Test Client» и сделку #453: 1020 USD → 1000 USDT-TRC20 в Лимане, время 15:25:00 UTC (1.5 мин разница).
  3. Запустил python -m app.jobs.reconcile. Результат: checked 688 unmatched tx, matched 1.
  4. Проверил БД: raw_blockchain_tx.id=9380 → matched_deal_id=453
  5. UI /reconciliation: счётчики обновились (688→687 tx без сделки, 131 done сделок).
  6. Audit пишет reconcile_run с заметкой checked 688 unmatched, matched 1.

📊 Текущее состояние

  • 688 блокчейн-tx (после очередного hourly sync — было 624, добавилось 64 новых за день).
  • 687 tx без сделки — это нормально для текущего состояния: seed-сделки фейковые с придуманными tx_hash, реальные на блокчейне — другие.
  • 130 done-сделок без on-chain подтверждения — seed-сделки.
  • 1 настоящий матч — наша E2E-сделка с tx #9380.
  • Балансы обновляются автоматически: за время сессии CryptoDom упал 86k→23k, Лиман 94k→46k. Реальные движения денег видны.

⚠ Что несовершенно

  1. Огромное число «потенциальных леваков» сейчас (687) — это артефакт seed-данных. В реальной эксплуатации, когда менеджеры начнут вводить настоящие сделки, цифра упадёт почти до нуля. Сейчас отображается корректно — но пугает.

  2. Алгоритм не учитывает направление через wallet branch. Я игнорирую wallet.branch_id при сравнении со сделкой. То есть если on-chain tx прошла через Лиман-кошелёк, а сделка создана в Лара-отделении — может совпасть по сумме/времени. Это уязвимость на edge-case (две одинаковые сделки в близкое время в разных отделениях). Для прода нужно добавить +10 за same branch (wallet.branch_id == deal.branch_id).

  3. Множественное матчирование исключено по тривиальному WHERE matched_deal_id IS NULL — но если сделка уже привязана к одной tx, и приходит вторая tx с теми же параметрами, она попытается матчить ту же сделку. Сейчас она просто пропустится (deal без on-chain — пройдёт алгоритм, но мы не проверяем что у deal уже есть какая-то raw_tx). Лучше: проверить что у deal ещё нет привязанной tx (через Deal.operations или отдельный запрос). Это тоже edge-case.

  4. Reconcile матчит только done-сделки. Если сделка в new/in_work имеет соответствующий tx на блокчейне — её можно автоматически переводить в done. Сейчас нет — менеджер должен сам пройти workflow. Можно добавить опцию «авто-перевод в done при матче» — но это нужно обсудить с пользователем.

  5. На дашборде on-chain виджет показывает всё — даже для cashier. Сейчас он показывается только если can_see_wallets (т.е. не cashier/viewer). Корректно. Но для accountant виден — это OK (у него read-only доступ к кошелькам).

Что в очереди после v0.9

Из ТЗ партнёра остаются: - M3 — парсер курсов из TG-каналов - M4 — автопостинг курсов - M5 — клиент-бот FSM - M6 ⭐ — карточка-заявка менеджеру с inline-кнопками - 2FA для production

Покрытие ТЗ

Этап / модуль v0.8 v0.9
(наш) on-chain ETL
(наш) Сверка on-chain ↔ deals готово
M2 прибыль ручная ручная
M3 парсинг TG
M5/M6 бот + заявка

~50% ТЗ покрыто.