AAYMA
RU
Войти

BenzTeam

Анализ транзакционного графа для AML: C++17-ядро считает метрики узлов, Python назначает роли, находит кластеры, ранжирует узлы и выдаёт три CSV и…

Рабочий MVP
Команда «BenzTeam» 14💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: BenzTeam
Кейс HackAlem.ai: Финансы — Freedom
Стадия: Рабочий MVP

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

Суть

Анализ транзакционного графа для AML: C++17-ядро считает метрики узлов, Python назначает роли, находит кластеры, ранжирует узлы и выдаёт три CSV и локальный UI с карточкой узла.

Как устроено

Python читает parquet и передаёт граф в C++17-ядро через JSON на stdin/stdout. Ядро считает degree, контрагентов, суммы в тиынах (int64), число переводов, pass-through и retention. Затем Python считает PageRank, betweenness (k=300) и Louvain на ненаправленной проекции. Роли (consolidator, distributor, transit, coordinator, terminal, peripheral) задаются прозрачными процентильными правилами. priority_score = PageRank 35% + betweenness 18% + оборот 14% + прочие компоненты; к каждому узлу пишется числовое evidence.

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

  • PageRank
  • betweenness centrality
  • Louvain
  • rule-based роли по процентилям
  • взвешенный priority_score
  • обработка обрыва depth=4

Особенности

Метрики считаются в C++17 с суммами в int64-тиынах, есть Python-fallback. Узлы на 4-м колене без исходящих не считаются terminal. README во многом повторяет стартовый кит, весь проект залит одним коммитом в 17:48.

Стек

Python · C++17 · CMake · nlohmann/json · NetworkX · pandas · Parquet


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

README

README.md

Стартовый код — кейс «Граф денег»

Чтобы вы не тратили час из пяти на загрузку parquet и сборку графа.

Быстрый запуск MVP

python -m pip install -r repos/starter/requirements.txt
python repos/starter/starter.py

В консоли обязательно должна появиться строка Metrics engine: C++17 (...graph_core.exe). Если бинарник отсутствует, приложение завершится с понятной ошибкой и командой сборки.

Сборка C++17-ядра

Из корня проекта в Developer PowerShell for Visual Studio:

cmake -S cpp_core -B cpp_core/build -A x64
cmake --build cpp_core/build --config Release

Готовый бинарник: cpp_core/build/bin/graph_core.exe. В обычном PowerShell на этой машине cmake может отсутствовать в PATH; тогда откройте папку cpp_core через File → Open → Folder в Visual Studio и соберите конфигурацию x64-Release. nlohmann/json v3.11.3 уже сохранён в cpp_core/third_party, сеть для сборки не нужна.

Ядро получает единственный JSON из stdin и пишет единственный JSON в stdout; ошибки пишутся в stderr. Суммы внутри ядра хранятся как int64_t в тиынах, что сохраняет дробные KZT без ошибок округления. Плотный индекс узлов хранится в vector, а gid → index — в unordered_map.

Отдельная smoke-проверка ядра из корня проекта:

'{"nodes":[1,2],"edges":[{"src":1,"dst":2,"sum_tiyn":125050,"n_tx":2}]}' | .\cpp_core\build\bin\graph_core.exe

Ответ должен содержать "engine":"cpp17", для узла 1 out_kzt:1250.5, а для узла 2 in_kzt:1250.5. Аварийный запуск сохранённой Python-реализации:

python repos/starter/starter.py --python-metrics

Он явно печатает Metrics engine: Python fallback. Контрольная точка исходного MVP находится в backup_python_mvp_20260923.

Одна команда после установки зависимостей пересчитывает все результаты и открывает UI на http://127.0.0.1:8000. Введите полный gid: карточка показывает роль, приоритет, числовое evidence, кластер и входящие/исходящие связи. Только генерация CSV без UI:

python repos/starter/starter.py --no-ui

Результаты находятся в repos/starter/out/: nodes_roles.csv, clusters.csv, top_nodes.csv. Пути можно переопределить через --data и --out.

Критерии ролей MVP

Роли задаются прозрачными правилами по направленному графу. Пороговые процентили считаются на текущей выгрузке, поэтому не зависят от абсолютного масштаба сумм:

  • consolidator: высокий входной degree (от 75-го процентиля), минимум два источника, входных связей заметно больше исходящих;
  • distributor: высокий выходной degree (от 75-го процентиля), минимум три получателя, исходных связей заметно больше входящих;
  • transit: есть вход и выход, отношение out_kzt/in_kzt лежит в диапазоне 0.65–1.35;
  • coordinator: есть вход и выход, высокое направленное betweenness либо одновременно высокие входной и выходной degree;
  • terminal: есть вход, нет выхода, но только если узел не обрезан глубиной 4;
  • peripheral: изолированный, слабоструктурный или обрезанный на depth=4 узел.

role_score — уверенность правила. Итоговый priority_score ограничивается диапазоном [0, 1] и состоит из: PageRank 35%, betweenness 18%, оборот 14%, структурная значимость 8%, значимость роли 10%, близость к seed 8% и значимость кластера 7%. Все величины нормализованы процентилями или непосредственно лежат в [0, 1]. evidence — человекочитаемая гипотеза с числами, а не утверждение о нарушении.

Кластеры — Louvain на ненаправленной проекции графа (только для community detection; роли и денежные метрики сохраняют направление). Seed без рёбер также попадают в CSV отдельными кластерами. Расчёт детерминирован (seed=42).

Пайплайн обрабатывает все узлы и формирует заполненные роли, кластеры и приоритеты.

Что уже посчитано

КолонкаСмысл
in_deg / out_degот скольких разных клиентов получил / скольким разным отправил
in_kzt / out_kztсколько получил / отправил в тенге внутри графа
in_tx / out_txколичество переводов
pagerankвзвешенный по сумме PageRank
pass_throughout_kzt / in_kzt — доля полученного, ушедшая дальше
truncated_by_depthузел на 4-м колене без исходящих
depth, is_seedиз nodes.parquet

Четыре ловушки, на которых ломаются наивные решения

1. out_deg == 0 не значит «деньги осели». 444 узла лежат на 4-м колене — обход просто закончился на них. Настоящий ли это конечный получатель или у него есть переводы дальше, которых нет в выгрузке, — по этим данным напрямую не видно. Придумайте, как их различить, и объясните логику.

2. У seed-клиентов in_kzt занижен. Граф собран от seed по исходящим. Деньги, которые seed получил извне выборки, в данных отсутствуют. Поэтому pass_through у них бывает 20 и больше — это не аномалия, это устройство выгрузки. Не стройте роль на «сколько получил» для seed вслепую.

3. Суммы и количество переводов — разные сигналы. Один перевод на 4 млн и сорок переводов по 100 тыс. дают одинаковый in_kzt, но означают разное поведение. Смотрите обе величины.

4. Граф направленный и взвешенный. nx.degree_centrality на неориентированной проекции сотрёт всю суть кейса — направление движения денег. Если используете ненаправленные методы (например, кластеризацию), оговорите это явно.

Требования к выгрузкам

Схема зафиксирована в ТЗ, жюри проверяет её механически:

  • nodes_roles.csv — ровно 2 248 строк, все колонки заполнены, evidence содержит числа, а не «высокий скор»;
  • clusters.csv — строка на кластер, включая гипотезу о его назначении;
  • top_nodes.csv — не менее 20 строк, отсортированных по приоритету.

Лишние колонки добавлять можно, обязательные удалять — нет.

Подсказки по инструментам

import networkx as nx

nx.pagerank(G, weight="sum_kzt")            # влияние узла
nx.hits(G)                                   # хабы и авторитеты — разделяет источники и сборщиков
nx.betweenness_centrality(G, weight=...)     # посредничество: кто «между»
nx.community.louvain_communities(UG)         # кластеры (на неориентированной проекции)
nx.simple_cycles(G, length_bound=6)          # возвратные потоки
nx.weakly_connected_components(G)            # изолированные фрагменты сети

transactions.parquet используется для проверки агрегации рёбер и расчёта числа активных дней узла. Временные лаги и сложные последовательности переводов в MVP не моделируются.

Схема решения

edges.parquet + nodes.parquet + transactions.parquet
                         │
                         ▼
              Python: чтение и проверки
                         │ JSON через stdin
                         ▼
       C++17: degree, контрагенты, суммы, tx,
              pass-through и retention
                         │ JSON через stdout
                         ▼
       Python analytics: PageRank, betweenness,
       Louvain, роли, evidence и priority_score
                         │
               ┌─────────┴──────────┐
               ▼                    ▼
         три CSV-файла       локальный web UI

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

  • Выгрузка построена от seed только по исходящим переводам до depth=4, поэтому это наблюдаемая часть сети, а не полный банковский граф.
  • Переводы менее 5 000 KZT отсутствуют; период ограничен июлем 2026 года.
  • Входящие потоки seed неполны, поэтому in_kzt нельзя трактовать как полный оборот.
  • Depth=4 без исходящих не считается terminal автоматически: такие узлы получают peripheral и пометку о границе наблюдения.
  • Роли являются аналитическими гипотезами для проверки, а не выводами о виновности.
  • Betweenness приближённый (k=300), Louvain использует ненаправленную проекцию только для поиска сообществ.

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

Текущий JSON и NetworkX рассчитаны на хакатонный набор. Для порядка 1 млн узлов сохраняется C++-модель с плотными индексами, но обмен следует заменить потоковым или бинарным форматом (Arrow/Parquet), граф и метрики полностью перенести в C++, а UI оставить на серверных локальных подграфах. PageRank считать итеративно на CSR-графе, betweenness — выборочно, сообщества — пакетным Louvain/Leiden. Полный граф в браузер не передаётся: поиск загружает только выбранный узел и ограниченное окружение.

Пятиминутная демонстрация

  1. Запустить python repos/starter/starter.py и показать строку Metrics engine: C++17.
  2. Показать три заполненных CSV и 2 248 строк в nodes_roles.csv.
  3. В UI найти distributor, consolidator и coordinator, разобрать числовое evidence.
  4. Показать стрелки переводов, цвета ролей, cluster_id, фильтры и top-20.
  5. Объяснить ловушки seed/depth=4 и завершить архитектурной схемой выше.

Что оценивается выше всего

Не количество метрик, а то, сможете ли вы за минуту объяснить жюри, почему конкретный gid получил свою роль. Считайте столько, сколько сможете защитить.

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

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

StartBenz 1

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

  • 2026-09-23 17:48Разработал систему анализа транзакционного графа на C++17 и Python, которая определяет роли участников, выявляет финансовые кластеры и ранжирует узлы для AML-проверки.StartBenz
Структура репозитория (15 файлов)
.gitignore
AGENTS.md
README.md
cpp_core/CMakeLists.txt
cpp_core/src/main.cpp
cpp_core/third_party/nlohmann/json.hpp
repos/data/edges.parquet
repos/data/nodes.parquet
repos/data/transactions.parquet
repos/starter/out/clusters.csv
repos/starter/out/nodes_roles.csv
repos/starter/out/top_nodes.csv
repos/starter/requirements.txt
repos/starter/starter.py
run.ps1

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

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

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