AAYMA
RU
Войти

FinPilot AI — Граф денег

Пайплайн анализа внутрибанковских переводов: граф денег, роли участников схемы (coordinator, consolidator, distributor, transit и др.), кластеры…

Доведённый продукт
Команда «FinPilot AI» 8💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: FinPilot AI
Кейс HackAlem.ai: Финансы — Freedom
Стадия: Доведённый продукт

Задача кейса: «Граф денег»: восстановить структуру организованной группы по сети транзакций (AML)

Суть

Пайплайн анализа внутрибанковских переводов: граф денег, роли участников схемы (coordinator, consolidator, distributor, transit и др.), кластеры, приоритетная очередь проверки и веб-карта с ассистентом аналитика.

Как устроено

Из трёх parquet-файлов строится направленный граф. Для узлов считаются признаки: степени, обороты, pass_through, betweenness, seed_reach, fast_share, а также «след денег курьеров» через пропорциональное смешивание, которое итерируется до сходимости в 1 ₸. Роль назначается упорядоченными пороговыми правилами, поверх неё ставятся метки (in_cycle, layering, split_in и др.). Кластеры выделяются Louvain на неориентированной проекции, приоритет считается взвешенной формулой. Результат (graph.json) показывается в веб-интерфейсе на vis-network. Ассистент на OpenAI (без ключа работает replay) готовит черновик запроса, аналитик его подтверждает или отклоняет, решение пишется в PostgreSQL.

Ключевые решения

  • NetworkX
  • betweenness centrality
  • PageRank
  • Louvain
  • proportional money-mixing propagation
  • rule-based role assignment
  • priority scoring
  • cycle/layering flags
  • human-in-the-loop approval

Метрики

2248 узлов, 3119 рёбер, 81 seed; роли: coordinator 8, consolidator 105, distributor 50, transit 178; 71 кластер; план из 30 узлов даёт модельный охват 32,24%

Особенности

Деньги курьеров отслеживаются моделью пропорционального смешивания (haircut) по графу. Для каждой метки указано альтернативное объяснение, баллы явно названы гипотезами, а не вероятностями вины. Решения аналитика (подтвердить или отклонить) сохраняются в PostgreSQL. Можно загрузить собственные выгрузки через интерфейс.

Стек

Python · NetworkX · SciPy · Parquet · PostgreSQL · Docker Compose · JavaScript · vis-network

Модели

OpenAI


Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-d785f448-finpilot-ai.

README

README.md

FinPilot AI — Граф денег

Продукт разработан в рамках Hackalem.ai на основе кейса № 2 «Финансы» — задачи «Граф денег» — и предоставленного организаторами датасета «4-хоповый граф внутрибанковских переводов (июль 2026)». Эти табличные данные служат исходной базой для расчётов и демонстрации приложения; в репозитории они представлены файлами data/nodes.parquet, data/edges.parquet и data/transactions.parquet.

Локальный математический пайплайн анализа внутрибанковских переводов. Он рассчитывает признаки узлов, назначает одну основную роль по правилам, выделяет кластеры, ранжирует узлы для проверки и создаёт graph.json для веб-интерфейса.

В репозитории объединены математический пайплайн, веб-интерфейс, ассистент и PostgreSQL. Сдаваемая версия предназначена для локального запуска через Docker Compose. Без ключа OpenAI ассистент работает в явно обозначенном replay.

Быстрый запуск приложения

Нужен работающий Docker Desktop либо Docker Engine с Compose. После клонирования репозитория:

cp .env.example .env
docker compose up --build

В PowerShell вместо cp можно использовать Copy-Item .env.example .env. Приложение: http://localhost:8000. Проверка приложения и базы: http://localhost:8000/api/health. Первая сборка скачивает образы и зависимости; время зависит от сети и компьютера.

Подготовка окружения, шесть таблиц PostgreSQL, API, остановка и хранение данных описаны в DOCKER.md. .env исключён из Git; ключ OpenAI для replay не нужен.

В браузере откройте демо-проект или загрузите три parquet через «Новый проект». Выберите клиента в очереди либо найдите его по GID: карточка показывает правило, факты и ограничения, ассистент объясняет результат. Черновик запроса можно подтвердить или отклонить; оба решения записываются в PostgreSQL. Загруженные проекты сохраняются в projects/, база — в постоянном томе db_data. При переключении проекта граф и ассистент используют именно выбранный результат.

Что решает

Из трёх parquet-файлов строится направленный граф движения денег. Каждый клиент получает роль, балл выраженности признаков, идентификатор правила и объяснение. Результаты помогают аналитику выбрать следующие проверки. Роли и метки являются гипотезами; баллы не являются вероятностями виновности или реальной управленческой роли.

Запуск с чистого клона

Проверенное окружение: Python 3.12. Версии пакетов закреплены в requirements.txt; для этих версий требуется Python 3.12 или новее. Исходные скрипты используют синтаксис Python 3.10+, но совместимость этого набора зависимостей с 3.10/3.11 не заявляется.

git clone https://github.com/BAITC-Hacks/hack-d785f448-finpilot-ai.git
cd hack-d785f448-finpilot-ai
python -m venv .venv

Активация окружения:

# macOS / Linux
source .venv/bin/activate
# Windows PowerShell
.\.venv\Scripts\Activate.ps1

Если политика PowerShell запрещает активацию, используйте .\.venv\Scripts\python.exe вместо python в следующих командах; менять системную политику не требуется.

python -m pip install -r requirements.txt
python run.py --data data --out out

Последняя команда последовательно запускает pipeline.py, addons_v3.py, check.py и прекращает выполнение при ненулевом коде возврата. Для расчёта не нужны аккаунты команды, API-ключи, облако или платные сервисы. Репозиторий приватный: для клонирования нужен обычный доступ GitHub к нему.

SciPy включён в зависимости, потому что networkx.pagerank использует его. Обычные результаты out/ исключены из Git; готовый пример находится в out_sample/.

Данные и результат контрольного запуска

В data/ находятся исходные обезличенные файлы хакатона: nodes.parquet, edges.parquet, transactions.parquet. Они предназначены для работы над задачей хакатона. GID — синтетический идентификатор; в JSON он передаётся строкой, поскольку не помещается точно в JavaScript Number.

Контрольные результаты приложенных скриптов:

  • 2 248 узлов, 3 119 рёбер, 4 840 транзакций, 81 seed;
  • роли: coordinator — 8, consolidator — 105, distributor — 50, transit — 178, terminal — 323, peripheral — 1 584;
  • 71 строка в clusters.csv, включая служебный кластер 0 для 19 изолированных seed;
  • метки: payer_of_couriers — 17, courier_candidate — 15, beneficiary — 9, in_cycle — 303, layering — 28, split_in — 67;
  • план: 30 узлов, модельный охват 32,24% при фиксированных июльских потоках и пропорциональном смешивании;
  • все 1 261 расчёт распространения, включённые в plan_meta.json, сошлись; последний шаг изменения меньше 1 ₸. Это критерий остановки итераций, а не гарантированная ошибка оценки.

Исторический раздел «Результат v1» в присланном RULES.md и некоторые оценки времени в SPEC.md относятся к предыдущему состоянию. Для текущих количественных результатов используйте сводку выше и out_sample/. Формулы и пороги в трёх присланных скриптах сохранены.

Критерии ролей и пороги

Ниже приведён RULES.md целиком. Термины «деньги курьеров», «след денег» и «перехват» в исходных материалах обозначают условный результат модели смешивания. Достижимость от seed сама по себе не доказывает происхождение конкретных денег. seed_reach считает пути от seed к рассматриваемому клиенту.


Критерии ролей и пороги — v3.2

Готово для раздела README «Критерии ролей и пороги». Пороги — в CONFIG в pipeline.py.

Метрики узла

МетрикаЧто значит
in_deg / out_degсколько разных плательщиков / получателей
in_kzt / out_kztсколько получил / отправил внутри графа, ₸
pass_throughout_kzt / in_kzt — доля полученного, ушедшая дальше. У seed недостоверна: входящие извне не выгружены
truncated4-е колено без исходящих — обход обрезан, исходящие не выгружены
seed_reachсколько разных seed-клиентов имеют путь до узла — деньги скольких курьеров сюда доходят
betweennessдоля кратчайших путей через узел, направленный граф — посредничество
fast_shareдоля исходящих денег, покрытая входящими за ≤ 2 дней до них — по суммам: входящий перевод покрывает исходящие не больше своей суммы и только один раз (перевод на 20 тыс. ₸ не подтверждает исходящие на 600 тыс. ₸)
reciprocal, sync_payers_dayконтрагенты с переводами в обе стороны; максимум разных плательщиков за один день
rep_inот скольких разных плательщиков пришло 2+ перевода — устойчивая связь, а не разовая
seed_money, seed_money_shareслед денег курьеров: сколько ₸ из исходящих seed дошло до узла и какая это доля от всех (55,3 млн ₸). Допущение: деньги на счёте смешиваются и уходят дальше пропорционально (haircut). Доля считается от max(получено, отправлено): если узел отправил больше, чем получил внутри графа, разница пришла извне и разбавляет долю — узел никогда не передаёт дальше больше денег курьеров, чем получил. Считается итерациями до сходимости (1 ₸)

Роли — проверяются по порядку, первое совпадение

#РольПравило
0peripheralнет переводов от 5 000 ₸ — 19 seed вне сети
1coordinatorin_deg ≥ 5, out_deg ≥ 5, betweenness ≥ 99-го перцентиля, out_deg ≤ 10 · in_deg
2distributorout_deg ≥ 10 и out_deg ≥ 3 · in_deg
3consolidatorin_deg ≥ 3 и out_deg ≤ in_deg / 2; для 4-го колена и seed — уверенность ниже
3бconsolidatorне seed, по деньгам: seed_money ≥ 500 000 ₸ (99-й перцентиль) и дальше уходит ≤ 50% (или исходящих нет) — сюда сходятся уже собранные деньги курьеров, даже если плательщиков 1–2
4хperipheralусловие 4 выполнено, но все исходящие раньше первого входящего — «временная последовательность не подтверждает транзит наблюдаемых поступлений»; один день — совместимо
4transitне seed, есть вход и выход, pass_through 0,8–1,2 или ≥ 70% исходящих покрыто входящими за ≤ 2 дня (по суммам), последний исходящий не раньше первого входящего
5transitseed с исходящими: передаёт деньги дальше
6terminalколено 0–3, исходящих нет, получено ≥ 100 000 ₸ или от 2+ плательщиков
7peripheral4-е колено без исходящих — граница выборки, роль не определить
8peripheralостальное: разовые входящие < 100 000 ₸, смешанные потоки

role_score — 0,55–0,95: растёт с запасом над порогом, ниже у seed и узлов 4-го колена.

Приоритет

0,35 · √(seed_money / max) + 0,15 · ранг(log оборота) + 0,10 · ранг(betweenness) + 0,30 · вес роли + 0,10 · ранг(seed_reach), затем ×0,85 для seed и ×0,8 для 4-го колена, нормировка в 0–1. Ранг — перцентиль среди всех узлов; деньги курьеров берутся по абсолютной величине, корень сглаживает разброс.

Вес роли: coordinator 1,0 · consolidator 0,85 · distributor 0,75 · transit 0,4 · terminal 0,3 · peripheral 0,1.

Кластеры

Louvain на неориентированной проекции — веса суммируются в обе стороны, seed=42, результат воспроизводим. Направление денег учтено в ролях, не в кластерах. Кластер 0 — 19 изолированных seed. hypothesis строится по составу ролей в кластере.

Учёт ограничений данных

  • 444 узла 4-го колена без исходящих — не terminal: либо peripheral «граница выборки», либо consolidator с пониженной уверенностью, если плательщиков 3+.
  • seed: pass_through не используется, роли — по исходящим.
  • pass_through > 1,2 у не-seed — есть входящие извне; роль транзита только при подтверждении по датам.
  • Порог 5 000 ₸: дробление ниже порога не видно.
  • Формулировки — «признаки …», это гипотезы для проверки, а не утверждения.

Результат v1

coordinator 8 · consolidator 111 (из них 11 — по деньгам) · distributor 50 · transit 233 · terminal 327 · peripheral 1 519. Кластеров 71. Расчёт — около 3 секунд.

Топ-10 узлов собирают 38% всех денег курьеров, топ-30 — 85%. 8 координаторов стоят на местах 4, 6, 9, 18, 24, 34, 70, 74.

Метки поверх ролей (addons_v3.py → nodes_flags.csv)

У узла одна роль и любое число меток. Метка — наблюдение, не вывод; рядом всегда иное объяснение, которое проверяет аналитик.

МеткаУсловиеИное объяснение
payer_of_couriers (кассир)не seed, переводит ≥ 2 известным курьерамзарплата, возврат долга, оплата услуг
courier_candidateне seed; ≥ 50% исходящих — в точки сбора, куда платят ≥ 2 seed, или перевод туда в один день с seedобщий легальный получатель
beneficiary (получатель)исходящие не наблюдаются, не край выборки, seed_money ≥ 500 000 ₸ — независимо от ролинакопление, счёт бизнеса
in_cycleлежит на замкнутом маршруте ≤ 6 шаговвзаимные расчёты
layeringтранзит, и с ним ещё ≥ 2 транзита подряд (поиск ≤ 3 рёбер в каждую сторону)цепочка оплат между посредниками
split_in≥ 2 раза получал несколько переводов от одного плательщика за деньлимит банка на разовый перевод

План охвата (block_plan.csv)

M_B — след денег курьеров при условии, что узлы B дальше не передают. capture(B) — доля денег курьеров, которая остановилась бы на B. Жадный набор: на каждом шаге из 40 кандидатов с наибольшим остаточным следом берётся узел с максимальным приростом capture; seed исключены. Формулировка результата: «модельный охват выбранных счетов — 32,24% при фиксированных июльских потоках и пропорциональном смешивании». Жадная эвристика, не глобальный оптимум; реакцию сети на блокировку не описывает. money_stopped_kzt — предельный прирост в тенге, сумма колонки равна итоговому перехвату.

Мировые типологии (IBM AMLSim)

consolidator = fan-in · distributor = fan-out · transit = stack · coordinator = gather-scatter · in_cycle = cycle · кассир с возвратом денег = scatter-gather.

Готовность и проверка

nodes_roles.csv содержит rule_id сработавшего правила и вклады приоритета p_*; run_meta.json и plan_meta.json — флаг converged, число итераций и последнее изменение (порог 1 ₸ — порог изменения между итерациями, не погрешность). python check.py --data data --out out --prev out_prev — 28 проверок; несошедшийся расчёт, пропавший файл или расхождение топ-листа с основной таблицей — ошибка.


Выгрузки

Три обязательных CSV:

ФайлПоля
nodes_roles.csvgid, role, rule_id, role_score, cluster_id, priority_score, evidence; метрики перечислены ниже
clusters.csvcluster_id, n_nodes, n_seed, sum_kzt_internal, top_gids, hypothesis
top_nodes.csvrank, gid, role, priority_score, why

Дополнительные колонки nodes_roles.csv: depth, is_seed, truncated, in_deg, out_deg, in_kzt, out_kzt, in_tx, out_tx, pass_through, seed_reach, seed_direct, seed_money, seed_money_share, rep_in, betweenness, pagerank, fast_share, seq_ok, reciprocal, sync_payers_day, p_money, p_turnover, p_betweenness, p_role, p_reach, p_multiplier, priority_raw.

Дополнительные файлы:

ФайлСодержание
graph.jsonУзлы, рёбра, метки, шорт-листы и план для фронтенда; контракт данных
run_meta.jsonКонфигурация правил, размеры графа, сходимость базового распространения
nodes_flags.csvМетки и их наблюдаемые основания по каждому gid
block_plan.csvstep, gid, role, gain, cumulative, money_stopped_kzt
levels.csvlevel, list, gid, role, priority, seed_money
plan_meta.jsonПараметры и сходимость расчётов плана, итоговый модельный охват

CSV следует импортировать с GID как строкой, особенно в Excel и JavaScript. levels — шорт-листы для навигации, а не доказанная иерархия участников. depth — расстояние от исходного списка, а не должность.

Ограничения данных

  • Выгрузка охватывает один банк и июль 2026 года; внешние потоки, остатки и назначения платежей не наблюдаются.
  • Обход построен по исходящим переводам от исходного списка. Входящие истории не полны.
  • У 444 узлов четвёртого колена исходящие не выгружены. Отсутствие стрелок не доказывает, что деньги остались на счёте.
  • Порог 5 000 ₸ скрывает операции ниже него; время внутри дня не задаёт точный порядок событий.
  • Haircut предполагает пропорциональное смешивание. Результат распространения — модельная величина, не установленное движение тех же денежных средств.
  • План охвата — жадная эвристика с фиксированными потоками; он не описывает реакцию сети и не гарантирует глобальный оптимум.
  • Проверки целостности не доказывают истинность назначенных ролей: независимой разметки реальных ролей нет.

Масштабирование до порядка миллиона узлов

Текущий NetworkX-пайплайн предназначен для приложенного набора. Для большого графа планируются потоковая агрегация/партиционирование в DuckDB, графовые вычисления в igraph, приближённый betweenness, пакетное распространение признаков от seed и Leiden. Параметры точности и память потребуется измерить на отдельном нагрузочном наборе. Работа на миллионе узлов в этом репозитории не проверялась.

Проверка результата

python check.py --data data --out out

Без --prev проверка повторного запуска явно помечается как пропущенная. Для полного набора из 28 проверок с эталонным результатом:

python run.py --data data --out out --prev out_sample

Ожидаемый итог: ошибок 0, пропущено 0, проверок 28. check.py --prev сравнивает роли, правила, приоритеты и план; сам по себе он не является побайтовой проверкой всех файлов.

Контрольный запуск из чистого клона в новом окружении Python 3.12.14 прошёл 28 из 28 проверок без ошибок и пропусков. Полная цепочка с проверками заняла 13,09 с. Дополнительно все девять выходных файлов побайтово совпали с out_sample/; идентификаторы узлов, рёбер и плана в JSON проверены как строки. Протокол запуска, версии и SHA-256. Время зависит от оборудования; межплатформенная побайтовая идентичность этим запуском не проверялась.

Запуск в Docker

См. DOCKER.md. Приложение и PostgreSQL запускаются локально, внешние сервисы для replay не требуются. Реальные ключи и пароли задаются в исключённом из Git .env и не передаются фронтенду.

Что дальше

Контрольный прогон всей сборки и известные ограничения фиксируются в PROGRESS.md. Качество live-модели проверяется отдельно от replay. Обмен между банками требует отдельного согласованного источника данных.

Проверка интеграции сайта

После запуска Compose выполните docker compose exec app python check_integration.py без OpenAI-ключа. Тест проверяет реальный API, PostgreSQL, направление достижимости, запись объяснений, подтверждение/отказ и защиту от повторного выполнения. Подробности и ограничения фронта — API_CONTRACT.md; фактически выполненные проверки — PROGRESS.md.

Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
23
Авторы
3
Файлы
42
Ветки
1

Авторы коммитов

yuriispirin 12gansrichin-ux 10Yurii 1

История коммитов

  • 2026-09-23 17:49docs: credit Hackalem.ai finance case and organizer datasetgansrichin-ux
  • 2026-09-23 17:43docs: record merged Docker and browser acceptance resultsgansrichin-ux
  • 2026-09-23 17:38fix: persist rejection and isolate assistant requests by projectgansrichin-ux
  • 2026-09-23 17:20Merge branch 'main' of https://github.com/BAITC-Hacks/hack-d785f448-finpilot-ai into frontyuriispirin
  • 2026-09-23 17:17Record two real browser-to-model direction checks and remaining limitationsgansrichin-ux
  • 2026-09-23 17:12Surface live model failures and record clean-clone Docker acceptancegansrichin-ux
  • 2026-09-23 17:08Validate Compose integration and persist idempotent human decisionsgansrichin-ux
  • 2026-09-23 16:49Record repeated Docker build failure caused by full host diskgansrichin-ux
Все коммиты (23)
  • 2026-09-23 16:43Document Docker checkpoint and report database readiness accuratelygansrichin-ux
  • 2026-09-23 16:24web: demo case instead of simulated upload; vendor vis-network 10.1.2yuriispirin
  • 2026-09-23 16:14web: project sidebar and upload-first start screenyuriispirin
  • 2026-09-23 16:07web: analyst assistant actions in the node cardyuriispirin
  • 2026-09-23 16:04web: coverage plan and level short-lists tabsyuriispirin
  • 2026-09-23 16:03web: node card with the six contract blocksyuriispirin
  • 2026-09-23 16:00web: money map with gid search and neighbour highlightyuriispirin
  • 2026-09-23 15:57web: review queue (top-30 by priority)yuriispirin
  • 2026-09-23 15:49Record clean checkout validation and reproducibility checksgansrichin-ux
  • 2026-09-23 15:47Add reproducible financial graph pipeline and sample outputsgansrichin-ux
  • 2026-09-23 17:13merge origin/main: JSON-маршруты /api/agent и /api/approve от Никиты + загрузка выгрузок /api/projectsyuriispirin
  • 2026-09-23 17:10merge origin/main: healthcheck и статус базы от Никиты + загрузка выгрузок через интерфейсyuriispirin
  • 2026-09-23 17:00product: свои выгрузки через интерфейс — загрузка трёх parquet, расчёт на сервере, список проектов; без демонстрационных заглушек; компактная очередьyuriispirin
  • 2026-09-23 16:34docker: compose с PostgreSQL, serve.py (пайплайн + карта + ассистент на одном порту), db_load.py, DOCKER.md; supabase убран по указанию организаторовyuriispirin
  • 2026-09-23 16:15assistant: четыре сцены аналитика, replay без ключа, HTTP для карточки; supabase: схема таблиц и RLSYurii
Структура репозитория (42 файлов)
.dockerignore
.env.example
.gitattributes
.gitignore
API_CONTRACT.md
DATA_CONTRACT.md
DOCKER.md
Dockerfile
PROGRESS.md
README.md
RULES.md
SPEC.md
addons_v3.py
assistant.py
check.py
check_integration.py
compose.yaml
data/edges.parquet
data/nodes.parquet
data/transactions.parquet
db/init/001_schema.sql
db_load.py
docs/validation-output.txt
docs/validation.json
out_sample/block_plan.csv
out_sample/clusters.csv
out_sample/graph.json
out_sample/levels.csv
out_sample/nodes_flags.csv
out_sample/nodes_roles.csv
out_sample/plan_meta.json
out_sample/run_meta.json
out_sample/top_nodes.csv
pipeline.py
requirements-docker.txt
requirements.txt
run.py
serve.py
web/app.js
web/graph.json
web/index.html
web/style.css

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.