Obmenka — архитектура аналитической системы

Версия: 0.3 (зафиксированы ключевые решения, ожидает данные для старта E1) Дата: 2026-05-13 Цель: прозрачная аналитика и двусторонняя сверка данных для крипто-обменника (наличные ↔ крипта + онлайн), с заделом под CRM, мониторинг качества работы менеджеров и встроенную базу знаний.

История версий

  • 0.3 (2026-05-13) — зафиксированы решения: рабочие выделенные TG-аккаунты для мониторинга, snapshot deployments под отдельные subdomain, MVP-дашборд показывает оборот/остаток наличных/остаток крипты/маржу.
  • 0.2 (2026-05-13) — добавлены: версионирование релизов, Telegram-мониторинг менеджеров, knowledge base в админке; этапы перестроены под приоритеты пользователя (сверка+дашборд — первый шаг).
  • 0.1 (2026-05-13) — первый draft.

1. Принципы

  1. Сырое отдельно от чистого. Любая загрузка сначала пишется в raw_* без изменений (источник правды для аудита). Нормализация — отдельным шагом.
  2. Идемпотентность. Повторный запуск ETL не создаёт дублей. Ключи: (chain, tx_hash, log_index) для блокчейна, (sheet_id, row_hash) для Sheets.
  3. Аудит-трейл. Любое изменение в operations/matches пишется в audit_log с user_id, before/after, timestamp.
  4. Прозрачность сверки. У каждой матч-пары есть статус, разница и причина. Никакой "магии".
  5. Минимум кастома, максимум стандартного. Postgres + Python + FastAPI + React — без экзотики.

2. Слои

┌─────────────────────────────────────────────────────────────────┐
│                       Sources (внешние)                          │
│  TRON nodes/API   Ethereum nodes/API   Google Sheets   (later: CRM forms) │
└──────────┬──────────────┬──────────────────┬─────────────────────┘
           │              │                  │
           ▼              ▼                  ▼
┌─────────────────────────────────────────────────────────────────┐
│                    ETL collectors (Python)                       │
│  tron_collector   eth_collector       sheets_collector           │
│  (poll каждые N мин, идемпотентная запись в raw_*)               │
└──────────────────────────┬──────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Postgres (БД obmenka)                         │
│  raw_blockchain_tx  raw_sheet_rows  operations  matches          │
│  wallets  cash_points  users  roles  balances_snapshot  audit_log│
└──────────┬──────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────┐
│                    Reconciliation engine                         │
│  (нормализация raw → operations, авто-матчинг, расхождения)      │
└──────────┬──────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────┐
│              FastAPI (REST, JWT, RBAC)                           │
└──────────┬──────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────┐
│              React + Vite UI (дашборд, сверка, CRM)              │
└──────────┬──────────────────────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────────────────────────────────┐
│      Caddy → obmenka.<wildcard>.<domain> (TLS, reverse proxy)    │
└─────────────────────────────────────────────────────────────────┘

3. Стек

Слой Технология Обоснование
БД Postgres (shared instance на сервере, отдельная БД obmenka) Средний объём, нужны транзакции и FK для сверки
ETL / Backend Python 3.12 + FastAPI Один проект для collectors и API, богатые либы для блокчейн API
Планировщик APScheduler в процессе FastAPI (для старта); миграция на systemd timers если разрастётся Просто, без отдельного Celery/Redis
Frontend React + Vite + TypeScript, UI: shadcn/ui, роутинг: TanStack Router Быстрый старт, удобные таблицы для сверки
Auth На старте — локальные users + JWT (passlib/python-jose). После — Keycloak (forge infra) Минимум зависимостей на MVP
Деплой systemd --user units + Caddy reverse_proxy По правилам сервера
Секреты pass под obmenka/ Глобальное правило сервера

4. Модель данных (черновик)

4.1. Справочники

chains            (code PK, name)                          -- 'tron', 'ethereum'
assets            (code PK, chain_code FK, contract, decimals, symbol)  -- 'usdt-trc20', 'usdc-erc20', ...
wallets           (id PK, chain_code FK, address, label, owner_user_id, is_internal, created_at)
cash_points       (id PK, name, location, base_currency, manager_user_id, is_active, created_at)
roles             (code PK, name)                          -- admin, manager, cashier, accountant, viewer
users             (id PK, email, password_hash, role_code FK, cash_point_id FK NULL, is_active, created_at)

4.2. Сырые данные

raw_blockchain_tx (
  id PK, chain_code FK, tx_hash, log_index, block_number, block_time,
  from_addr, to_addr, asset_code FK, amount NUMERIC(38,8),
  payload JSONB, ingested_at,
  UNIQUE(chain_code, tx_hash, log_index)
)

raw_sheet_rows (
  id PK, sheet_id, sheet_name, row_index, row_hash,
  payload JSONB, ingested_at,
  UNIQUE(sheet_id, row_hash)
)

4.3. Бизнес-данные

operations (
  id PK, op_type ENUM('cash_in','cash_out','crypto_in','crypto_out','internal_transfer'),
  occurred_at TIMESTAMPTZ,
  cash_point_id FK NULL,           -- для cash_*
  wallet_id FK NULL,               -- для crypto_*
  asset_code FK,
  amount NUMERIC(38,8),
  counterparty_label TEXT,
  source ENUM('blockchain','sheet','manual'),
  source_ref_id BIGINT,            -- FK на raw_*
  created_by_user_id, created_at,
  notes TEXT
)

matches (
  id PK,
  cash_op_id FK,                   -- операция кассы (наличные)
  crypto_op_id FK,                 -- операция блокчейна
  status ENUM('auto_matched','manual_matched','disputed','unmatched'),
  amount_diff NUMERIC(38,8),
  time_diff_seconds INT,
  fx_rate NUMERIC(20,8),
  matched_by_user_id, matched_at,
  notes TEXT
)

balances_snapshot (
  id PK, snapshot_at TIMESTAMPTZ,
  scope ENUM('wallet','cash_point'),
  scope_id BIGINT,
  asset_code FK,
  amount NUMERIC(38,8),
  computed_from ENUM('chain','operations','manual_count')
)

audit_log (
  id PK, occurred_at, user_id, entity_type, entity_id,
  action ENUM('create','update','delete','match','unmatch'),
  before JSONB, after JSONB
)

5. Reconciliation (сверка)

Шаг 1: Нормализация. Каждые N минут engine забирает свежие raw_* и превращает в operations (idempotent upsert по source_ref).

Шаг 2: Авто-матчинг. Для каждой пары (cash_op, crypto_op) считаем score: - по сумме (с учётом FX, в нативной валюте крипты): exact / в пределах X% - по времени: разница <= Y минут - по адресу/контрагенту: явная привязка через wallets.label или счёт кассы

Если score выше порога → auto_matched. Иначе остаётся unmatched и попадает на ручной разбор.

Шаг 3: Ручная сверка. В UI — таблица расхождений с двух сторон, оператор тыкает "это одна операция" / "пометить как одиночную" / "это внутренний перевод".

Шаг 4: Балансы. Каждый день в полночь — снимок balances_snapshot по всем кошелькам и кассам. Источник: on-chain getBalance + сумма операций кассы с начала дня.


6. Роли и доступ

Роль Может
admin Всё, включая создание пользователей и редактирование справочников
manager Видит всё, может матчить, не может создавать пользователей
accountant Read-only по всем кассам и кошелькам, экспорт
cashier Видит только свою кассу (users.cash_point_id), может вводить операции
viewer Read-only по всем dashboards без деталей операций

RBAC проверяется в FastAPI middleware и дополнительно в SQL через row-level security (опционально на v2).


7. Этапы реализации (от MVP к CRM)

Этапы перестроены под приоритеты: сначала сверка + дашборд по отделениям, потом блокчейн с точными данными, потом мониторинг менеджеров, потом knowledge base.

Этап Что делаем Видимый результат
E0. Скелет git + структура проекта; docs viewer; Caddy + TLS https://obmenka.felix.nohumaninside.me работает
E1. Сверка + дашборд (MVP) БД obmenka; ручной ввод операций касс через простую админку; CSV-импорт смен; дашборд с 4 виджетами: (1) оборот по отделениям за день/неделю/месяц, (2) остаток наличных по отделениям, (3) остаток крипты по кошелькам (заглушка до E2, потом on-chain), (4) маржа/P&L по сделкам; ручной матчинг кассы↔крипта Руководитель открывает дашборд и за 10 секунд видит: оборот, остатки, маржу, расхождения
E2. Блокчейн ETL TRC20 + ERC20 collectors; raw_blockchain_tx; автоматическая привязка крипто-стороны к операциям кассы; полные данные транзакции у каждой записи (hash, block, время, комиссия) По любой операции из кассы видно соответствующую on-chain транзакцию с полным контекстом
E3. Sheets ETL (опционально) Если кассиры ведут в Sheets — авто-забор и нормализация в operations. Альтернатива — продолжать ручной ввод/CSV Sheets-операции попадают в БД без ручного импорта
E4. Telegram-мониторинг менеджеров MTProto клиенты для аккаунтов менеджеров; pipeline сообщений → анализ; флаги «расхождение условий», «леваки»; сверка обсуждённых сумм с реальными операциями На каждую сделку видно цепочку диалога, отклонения от регламента подсвечены
E5. RBAC + Knowledge base + полировка UI Роли (кассир/менеджер/бух/руководитель); каждой роли — раздел с инструкциями (markdown с версионированием через git); кассирский режим Менеджер сам читает свои инструкции в админке; данные видны только по своему scope
E6. CRM Клиенты, заявки на обмен, привязка к operations и Telegram-диалогам, история Полная карточка клиента: сделки + переписка + соответствие условий

Каждый этап — отдельный git tag (v0.1, v0.2...), отдельный snapshot деплоя (см. раздел 8), отдельный отчёт «что сделано».


8. Версионирование релизов

Цель: в любой момент посмотреть «как было неделю назад», сравнить две версии админки/документации, при необходимости откатиться.

8.1. Уровни версионирования

Что версионируем Механизм Где смотреть
Документация (docs/*.md) git история (auto-commit при изменениях + ручные коммиты) Docs viewer покажет историю файла + diff между версиями (TODO: добавить кнопку «история»)
Код backend + frontend git + теги релизов v0.1, v0.2... GitLab UI + локальный git log
UI/админка (собранный артефакт) Snapshot deployments — каждый релиз отдельным subdomain https://v0.1.obmenka.felix.nohumaninside.me, https://v0.2.obmenka..., основной https://obmenka... указывает на последний
Данные (БД) Ежедневные pg_dump в зашифрованный S3/локально + retention 90 дней Восстановление через скрипт

8.1.1. Решение

Snapshot deployments включены — каждая старая версия UI доступна по своему subdomain (v0.X.obmenka.felix.nohumaninside.me). Все версии смотрят в одну БД (read-only режим для не-текущих). Основной домен всегда указывает на текущую.

8.2. Snapshot deployments — как работает

  1. При релизе делаем git tag v0.X, билдим UI, сохраняем артефакт в /srv/obmenka/releases/v0.X/.
  2. Скрипт деплоя добавляет в Caddyfile блок v0.X.obmenka.felix.nohumaninside.me { root * /srv/obmenka/releases/v0.X; reverse_proxy /api/* 127.0.0.1:8781 }.
  3. Backend старых версий может смотреть в ту же БД (read-only режим) — для просмотра, не для редактирования.
  4. Основной домен obmenka.felix.nohumaninside.me всегда указывает на текущую версию.

8.3. Сравнение версий

  • Документы: docs viewer получит кнопку «история» рядом с каждым документом → список коммитов → diff между двумя.
  • UI: открой два subdomain в соседних вкладках.
  • Поведение: API двух версий смотрят в одну БД, так что данные одинаковые, отличается только UI/логика.

9. Telegram-мониторинг менеджеров (этап E4)

Цель: контроль качества работы менеджеров, защита от «леваков» (договорённостей в обход компании), проверка что условия в диалоге соответствуют операции в учёте.

9.1. Технология

  • MTProto через Telethon (Python) — подключение к аккаунту менеджера от его имени. Видит все диалоги (личные + группы) этого аккаунта. Это user-API, не bot — поэтому видит ретроспективу и личные сообщения.
  • НЕ Bot API — бот видит только то, куда его добавили, не работает для личных диалогов клиент↔менеджер.
  • Сессии Telethon хранятся зашифрованно в pass (obmenka/telegram-sessions/<manager>).

9.1.1. Аккаунты — РЕШЕНО

Используем рабочие выделенные аккаунты Telegram (отдельные номера/SIM на компанию). - Перевод существующих клиентов на рабочие аккаунты — операционная задача перед стартом E4. - Аккаунты юридически принадлежат компании → проще модель доступа, нет вопросов с личной перепиской менеджеров. - API ID/Hash регистрируем под корпоративный аккаунт.

9.2. Pipeline

Telegram (MTProto) → ingest worker (по 1 на менеджера) → raw_telegram_messages (Postgres)
       ↓
   analyzer (LLM + правила) → events таблица (флаги: discussed_amount, fx_rate, agreement, suspicion)
       ↓
   reconcile с operations → match_telegram_to_operation → пометки расхождений
       ↓
   UI: страница «диалоги менеджера X» + страница «подозрительные события»

9.3. Что анализируем

Базовые правила: - Извлечь обсуждённую сумму и валюту → сравнить с operations.amount - Извлечь обсуждённый курс → сравнить с применённым (с допуском) - Триггеры: «лично», «не оформляй», «без чека», «личная карта», «вне обменника» → флаг - Договорённость на сумму без последующей операции в БД за N часов → флаг «возможный леваль»

Глубокий анализ через LLM (опционально, дороже): - Контекстная оценка тона диалога - Распознавание неформальных договорённостей - Связывание клиента в нескольких диалогах

9.4. Юридический и operational контекст (требует проработки)

  • Согласие менеджеров обязательно — фиксируется в трудовом/служебном договоре. Без подписи — не подключаем.
  • Хранение — зашифрованно, доступ только у руководителя (роль manager или admin не имеют этого скоупа).
  • Retention — определить срок хранения (обычно 90–180 дней для операционных целей).
  • Аудит доступа — каждый просмотр диалога руководителем пишется в audit_log.

10. Knowledge base в админке (этап E5)

Цель: менеджеры и другие роли видят свои инструкции прямо в админке, без отдельных wiki/notion.

10.1. Источник

Markdown-файлы в репозитории, директория docs/handbook/: - docs/handbook/manager.md - docs/handbook/cashier.md - docs/handbook/accountant.md - docs/handbook/_shared.md

10.2. Доступ по ролям

Роль Видит
cashier cashier.md + _shared.md
manager manager.md + _shared.md
accountant accountant.md + _shared.md
admin всё

10.3. Версионирование

Те же файлы что лежат в git → автоматически версионируются. В админке на странице инструкции — кнопка «история» → виден список изменений с автором и датой.

10.4. Редактирование

  • На MVP — правка через git/PR (только admin).
  • На v2 — встроенный markdown-редактор в админке для роли admin, который коммитит изменения в git.

11. Открытые вопросы (требуют ответа до старта E1)

Зафиксировано

  • ✅ Subdomain docs viewer: obmenka.felix.nohumaninside.me
  • ✅ Telegram-аккаунты: рабочие выделенные (отдельные номера на компанию)
  • ✅ Snapshot deployments: да, под каждый релиз свой subdomain
  • ✅ Состав MVP-дашборда: оборот по отделениям + остаток наличных + остаток крипты + маржа

Ещё требует ответа до старта E1

  1. Отделения / кассы — сколько их сейчас, какие названия, в каких валютах работают?
  2. Курс — кто и где сейчас фиксирует обменный курс по сделке? Excel, бот, голова кассира?
  3. Формат ручного ввода на старте E1 — CSV-импорт раз в день / форма в админке / прямо Google Sheets?
  4. Маржа — считаем как (курс клиенту − рыночный курс) × объём? Откуда брать рыночный (Binance API)?

До старта E2 (блокчейн)

  1. Список кошельков с лейблами (отделение / тип) — на время можно фейковые.

До старта E4 (Telegram)

  1. Глубина анализа диалогов — хватит правил («сумма», «вне обменника», «личная карта») или сразу подключаем LLM для распознавания смысла?

12. Что НЕ делаем сейчас

  • Никаких микросервисов на старте — один монорепо, один процесс backend + cron внутри.
  • Никаких message queue (Kafka, RabbitMQ) — Postgres LISTEN/NOTIFY достаточно.
  • Никакой Web3-кастоди (хранение приватных ключей) — мы только читаем on-chain.
  • Никакого ML-скоринга — все правила сверки явные и проверяемые.