О проекте
Команда: 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.mdFinPilot 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_through | out_kzt / in_kzt — доля полученного, ушедшая дальше. У seed недостоверна: входящие извне не выгружены |
truncated | 4-е колено без исходящих — обход обрезан, исходящие не выгружены |
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 ₸) |
Роли — проверяются по порядку, первое совпадение
| # | Роль | Правило |
|---|---|---|
| 0 | peripheral | нет переводов от 5 000 ₸ — 19 seed вне сети |
| 1 | coordinator | in_deg ≥ 5, out_deg ≥ 5, betweenness ≥ 99-го перцентиля, out_deg ≤ 10 · in_deg |
| 2 | distributor | out_deg ≥ 10 и out_deg ≥ 3 · in_deg |
| 3 | consolidator | in_deg ≥ 3 и out_deg ≤ in_deg / 2; для 4-го колена и seed — уверенность ниже |
| 3б | consolidator | не seed, по деньгам: seed_money ≥ 500 000 ₸ (99-й перцентиль) и дальше уходит ≤ 50% (или исходящих нет) — сюда сходятся уже собранные деньги курьеров, даже если плательщиков 1–2 |
| 4х | peripheral | условие 4 выполнено, но все исходящие раньше первого входящего — «временная последовательность не подтверждает транзит наблюдаемых поступлений»; один день — совместимо |
| 4 | transit | не seed, есть вход и выход, pass_through 0,8–1,2 или ≥ 70% исходящих покрыто входящими за ≤ 2 дня (по суммам), последний исходящий не раньше первого входящего |
| 5 | transit | seed с исходящими: передаёт деньги дальше |
| 6 | terminal | колено 0–3, исходящих нет, получено ≥ 100 000 ₸ или от 2+ плательщиков |
| 7 | peripheral | 4-е колено без исходящих — граница выборки, роль не определить |
| 8 | peripheral | остальное: разовые входящие < 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.csv | gid, role, rule_id, role_score, cluster_id, priority_score, evidence; метрики перечислены ниже |
clusters.csv | cluster_id, n_nodes, n_seed, sum_kzt_internal, top_gids, hypothesis |
top_nodes.csv | rank, 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.csv | step, gid, role, gain, cumulative, money_stopped_kzt |
levels.csv | level, 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.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.