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. Принципы
- Сырое отдельно от чистого. Любая загрузка сначала пишется в
raw_*без изменений (источник правды для аудита). Нормализация — отдельным шагом. - Идемпотентность. Повторный запуск ETL не создаёт дублей. Ключи:
(chain, tx_hash, log_index)для блокчейна,(sheet_id, row_hash)для Sheets. - Аудит-трейл. Любое изменение в
operations/matchesпишется вaudit_logс user_id, before/after, timestamp. - Прозрачность сверки. У каждой матч-пары есть статус, разница и причина. Никакой "магии".
- Минимум кастома, максимум стандартного. 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 — как работает
- При релизе делаем
git tag v0.X, билдим UI, сохраняем артефакт в/srv/obmenka/releases/v0.X/. - Скрипт деплоя добавляет в Caddyfile блок
v0.X.obmenka.felix.nohumaninside.me { root * /srv/obmenka/releases/v0.X; reverse_proxy /api/* 127.0.0.1:8781 }. - Backend старых версий может смотреть в ту же БД (read-only режим) — для просмотра, не для редактирования.
- Основной домен
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
- Отделения / кассы — сколько их сейчас, какие названия, в каких валютах работают?
- Курс — кто и где сейчас фиксирует обменный курс по сделке? Excel, бот, голова кассира?
- Формат ручного ввода на старте E1 — CSV-импорт раз в день / форма в админке / прямо Google Sheets?
- Маржа — считаем как (курс клиенту − рыночный курс) × объём? Откуда брать рыночный (Binance API)?
До старта E2 (блокчейн)
- Список кошельков с лейблами (отделение / тип) — на время можно фейковые.
До старта E4 (Telegram)
- Глубина анализа диалогов — хватит правил («сумма», «вне обменника», «личная карта») или сразу подключаем LLM для распознавания смысла?
12. Что НЕ делаем сейчас
- Никаких микросервисов на старте — один монорепо, один процесс backend + cron внутри.
- Никаких message queue (Kafka, RabbitMQ) — Postgres LISTEN/NOTIFY достаточно.
- Никакой Web3-кастоди (хранение приватных ключей) — мы только читаем on-chain.
- Никакого ML-скоринга — все правила сверки явные и проверяемые.