AAYMA
RU
Войти

AML Graph Analytics

Детерминированный Python-пайплайн и Streamlit-дашборд для AML-кейса: из трёх parquet строит граф, считает признаки, назначает роли, выделяет…

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

О проекте

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

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

Суть

Детерминированный Python-пайплайн и Streamlit-дашборд для AML-кейса: из трёх parquet строит граф, считает признаки, назначает роли, выделяет сообщества, считает приоритет и выгружает CSV; опциональный AI-ассистент только читает результаты.

Как устроено

pipeline.py валидирует parquet (схемы, ссылки на GID, согласованность транзакций с рёбрами) и строит networkx.DiGraph. Считаются PageRank, betweenness, HITS, HHI-концентрация, короткие циклы до 6 и дневные признаки: отток в окне D..D+2, синхронные отправители. Роли выбираются по формулам из перцентильных рангов с общим порогом 0.65 и правилами разрешения ничьих; затем кластеризация и priority с объяснением priority_why. Streamlit читает готовые CSV, граф рисует PyVis; OpenAI-ассистент работает через read-only инструменты.

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

  • NetworkX DiGraph
  • PageRank
  • betweenness centrality
  • HITS
  • HHI
  • поиск циклов
  • перцентильные ранги
  • кластеризация сообществ
  • PyVis
  • SHA-256 provenance
  • read-only tools для ассистента

Особенности

SHA-256 исходных parquet сохраняется в diagnostics.json, и UI отказывается смешивать изменённые данные со старыми оценками. NaN сохраняется там, где признак не определён, вместо выдуманного нуля. Правила разрешения ничьих между ролями описаны явно.

Стек

Python · Streamlit · NetworkX · pandas · PyVis · pytest

Модели

OpenAI


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

README

README.md

AML Graph Analytics

Локальный инструмент HackAlem «Money Graph» для исследования наблюдаемых переводов: объяснимые роли, сообщества и очередь аналитической проверки. Работает без AI и внешней базы данных.

Problem — задача аналитика

В сети переводов трудно быстро увидеть сбор средств, распределение, промежуточные узлы и пересечение ветвей seed-клиентов. Одна большая сумма недостаточна для вывода. Аналитику нужны направление связей, несколько независимых признаков и понятные ограничения данных.

Solution — решение

parquet → directed graph → features → roles → clusters → priority → dashboard → optional AI

Детерминированный pipeline сохраняет метрики и объяснения. Streamlit читает готовые результаты; необязательный AI помогает их прочитать, но не присваивает роли и не вычисляет оценки.

Dataset — данные

По условиям задачи набор содержит только внутрибанковские переводы за июль 2026:

  • nodes.parquet: gid, depth, is_seed — полный список клиентов, включая изоляты.
  • edges.parquet: src, dst, sum_kzt, n_tx, depth — агрегаты направленных пар.
  • transactions.parquet: src, dst, date, sum_kzt — отдельные переводы с календарной датой.

Предоставленный набор: 2 248 узлов, 3 119 рёбер, 4 840 переводов, 81 seed. Эти значения проверены аудитом, а не зашиты в расчёт или UI. Поля банка нет, поэтому условие «внутрибанковские» берётся из описания набора, а не выводится алгоритмом.

Architecture — архитектура

data/nodes.parquet ─────────┐
data/edges.parquet ─────────┼→ utils/validation.py → pipeline.py
data/transactions.parquet ──┘                         │
                                      analytics/features.py
                                      analytics/graph_features.py
                                      analytics/temporal.py
                                      analytics/roles.py
                                      analytics/clustering.py
                                      analytics/priority.py
                                                    │
                                  utils/output_validation.py
                                                    │
                                            out/*.csv + JSON
                                                    │
                                app.py + ui/ + visualization/
                                                    │
                                  assistant/tools.py (read only)
                                                    │
                              assistant/service.py → OpenAI (опция)

Основной граф — networkx.DiGraph. Ребро src → dst сохраняет отдельно sum_kzt и n_tx; изолированные узлы не теряются. GID в интерфейсе и JSON передаётся строкой, чтобы JavaScript не округлял большие идентификаторы.

Installation — установка

Python 3.9+; проверено на Python 3.9, macOS. Из корня репозитория:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Положите предоставленные файлы nodes.parquet, edges.parquet, transactions.parquet в data/. Входные данные, результаты, виртуальное окружение и секреты исключены из Git.

Run Analytics — расчёт

python pipeline.py --data ./data --out ./out

Запускайте до dashboard и после изменения исходных данных. Каждый запуск полностью пересчитывает результаты из parquet. Проверяются схемы, уникальность узлов и пар рёбер, ссылки на GID, глубина, неотрицательные суммы и положительные количества, даты и согласованность транзакций с агрегатами рёбер. После записи CSV повторно считываются и проходят межфайловую проверку.

Границы июля и порог 5 000 KZT отражаются в диагностике, а не используются для незаметного удаления записей.

Run Dashboard — интерфейс

streamlit run app.py

Откройте локальный dashboard. Четыре вкладки:

  • Dashboard: четыре KPI, поиск GID, рейтинг Top 20/50/100, распределение ролей и краткая методика.
  • Client Analysis: сохранённые оценки и причины, наблюдаемые потоки, контрагенты, активность по датам, необязательный AI, направленное окружение 1 hop.
  • Network: сеть Top 20/50 или выбранного кластера.
  • Clusters: сводки, состав ролей и граф сообщества.

Поиск и выбор из рейтинга сохраняют общий GID. После «Open full analysis» откройте Client Analysis. Рейтинг ограничен содержимым top_nodes.csv: выбор Top 100 не создаёт недостающие строки.

PyVis встроен локально, без CDN и физической симуляции. Стрелки показывают отправитель → получатель; цвет — роль, размер — готовый приоритет, толщина — логарифмическое масштабирование суммы. Выбранный клиент выделен рамкой. Hover содержит метрики узла и агрегаты ребра.

Для читаемости каждый граф ограничен 100 узлами и 500 рёбрами. При превышении лимита сохраняются выбранный узел, затем узлы с наибольшим приоритетом и самые крупные рёбра; интерфейс явно сообщает о сокращении. Таблицы и сводки остаются полными. Изолированный клиент показывается отдельным узлом.

CSV/parquet кэшируются с учётом размера и времени изменения, граф и HTML также кэшируются. На взаимодействиях не пересчитываются PageRank, betweenness, Louvain, роли или приоритет. Все вкладки Streamlit выполняются при rerun, но дорогие чтения и отрисовка берутся из кэша.

Pipeline сохраняет SHA-256 исходных parquet в diagnostics.json. UI проверяет их до показа графа и дневных транзакций: изменённые данные нельзя смешивать со старыми оценками даже при совпадении GID. Для старых экспортов без отпечатков проверка ограничена GID; перед демонстрацией выполните новый pipeline.

Пути можно изменить в боковой панели или через MONEY_GRAPH_DATA_DIR и MONEY_GRAPH_OUT_DIR. Без обязательных CSV приложение показывает команду расчёта. Без исходных parquet доступны таблицы; соответствующие графы отключаются.

Outputs — результаты

  • out/nodes_roles.csv: одна строка на каждый исходный GID, role, role_score, cluster_id, priority_score, evidence до 200 символов; также признаки, компоненты и priority_why.
  • out/clusters.csv: cluster_id, n_nodes, n_seed, sum_kzt_internal, top_gids, hypothesis и состав ролей.
  • out/top_nodes.csv: rank, gid, role, priority_score, why; по умолчанию Top 50, для меньшего набора — все узлы.
  • out/node_features.csv: полный набор признаков и расчётных компонентов.
  • out/diagnostics.json: размеры, ограничения набора, конфигурации, SHA-256 источников, время этапов и результаты валидации.
  • out/final_audit.json: создаётся командой python scripts/audit_outputs.py; распределения, 18 примеров по ролям, Top 20 с компонентами и результаты независимой сверки.

Сортировка рейтинга: приоритет убывает, затем role_score убывает, затем GID возрастает. CSV можно скачать из sidebar без повторного расчёта.

Explainability — объяснимость

evidence — краткое объяснение выбранной роли по наблюдаемым числам, с оговорками seed/depth-4. priority_why описывает два крупнейших взвешенных вклада в приоритет; top_nodes.why повторяет это сохранённое объяснение. hypothesis описывает состав сообщества. Это шаблонные объяснения pipeline, независимые от LLM.

В обязательных результатах пропусков нет. NaN допустим только там, где признак не определён: отношение при нулевом знаменателе, среднее за активные дни без активности, временное окно seed; HITS может быть недоступен при сбое решателя, который отражается в диагностике. Неопределённость не подменяется выдуманным нулём.

Features — признаки

Контрагенты и число переводов считаются отдельно от объёма. PageRank использует сумму ребра; betweenness — точная направленная невзвешенная посредническая центральность. Дополнительно доступны HITS, слабые компоненты, достижимость от seed, расстояния, концентрация HHI и короткие циклы длиной до 6.

reachable_seed_count включает сам seed на расстоянии 0: это достижимость, а не доказательство координации. Дневные признаки описывают активность, повторные пары и синхронных отправителей. Отток в окне D…D+2 — ограниченная эвристика, а не трассировка конкретных денег; перекрывающиеся окна независимы. Для seed показатели такого оттока неопределены и не дают положительного сигнала.

Role Definitions — роли

Точные правила и веса: analytics/roles.py, ROLE_CONFIG. Нормировка P(x) — средний перцентильный ранг среди строго положительных значений; нули и NaN дают 0. Она зависит от выборки.

  • consolidator: не менее 3 входящих контрагентов и положительный входящий объём. Оценка: 0.40·P(in_deg) + 0.20·P(in_tx) + 0.10·P(in_kzt) + 0.10·seed_support + 0.10·P(max_unique_in_senders_per_day) + 0.10·incoming_spread.
  • distributor: не менее 3 исходящих контрагентов и положительный исходящий объём. Оценка: 0.55·P(out_deg) + 0.20·P(out_tx) + 0.10·P(out_kzt) + 0.15·outgoing_spread.
  • transit: положительные входящий и исходящий потоки, не seed, баланс потоков ≥0.5 и оконный отток ≥0.5. Оценка: 0.40·balance + 0.45·window + 0.15·repeat_support.
  • terminal: есть входящие средства, нет видимых исходящих; узел не усечён глубиной. Оценка: 0.55·endpoint + 0.20·P(in_kzt) + 0.15·P(active_days_in) + 0.10·P(in_deg).
  • coordinator: положительная betweenness с перцентилем ≥0.8, достижимость от ≥2 seed, входящие и исходящие связи. Оценка: 0.60·P(betweenness) + 0.20·P(pagerank) + 0.20·P(reachable_seed_count). Название обозначает структурный паттерн, не установленного организатора.
  • peripheral: ни один допустимый паттерн не достиг порога; role_score=0. Это не утверждение о безопасности клиента.

Общий порог сильного паттерна — 0.65. Среди близких оценок (разница ≤0.02) порядок: transit, consolidator, distributor, terminal. Coordinator заменяет другую допустимую роль только с преимуществом ≥0.05.

spread=1−HHI при наличии потока; seed_support=max(P(direct_seed_in), P(reachable_seed_count)); balance=min(out/in,in/out); window ограничен диапазоном [0,1]; repeat_support — среднее перцентилей числа повторных входящих и исходящих контрагентов. Одна основная роль не исключает других признаков.

Priority Score — приоритет проверки

Актуальная конфигурация: analytics/priority.py, PRIORITY_CONFIG.

priority_score = 0.35·structural + 0.25·seed_relevance
               + 0.15·role + 0.15·flow + 0.10·temporal

Все компоненты в [0,1]:

  • structural = 0.60·P(betweenness) + 0.30·P(pagerank) + 0.10·coordinator_score.
  • seed_relevance = 0.75·P(max(reachable_seed_count−1,0)) + 0.25·P(max(direct_seed_in−1,0)).
  • role = role_score × relevance: coordinator 1.0, consolidator/distributor 0.9, transit 0.85, terminal 0.35, peripheral 0.
  • flow = 0.20·P(in_kzt) + 0.20·P(out_kzt) + 0.15·P(in_tx) + 0.15·P(out_tx) + 0.15·P(in_deg) + 0.15·P(out_deg).
  • temporal = 0.35·window + 0.10·same_day + 0.25·P(max(max_unique_in_senders_per_day−1,0)) + 0.15·P(days_with_3plus_in_senders) + 0.15·repeat_support.

Оконные отношения ограничены [0,1]; для seed их вклад равен 0. Изоляты получают нулевой приоритет. Глубина 4 сама по себе не даёт бонуса или штрафа. Это очередь проверки, не вероятность преступления. UI показывает сохранённые компоненты и причины.

Clustering — сообщества

Louvain применяется внутри слабых компонент к отдельной неориентированной взвешенной проекции: встречные суммы складываются. Только выделение сообществ использует эту проекцию; анализ переводов остаётся направленным. seed=42, resolution=1, threshold=1e-7.

Каждый изолят получает собственный кластер. ID назначаются детерминированно по убыванию размера, затем минимальному GID. Внутренний оборот берётся по исходным направленным рёбрам и не включает межкластерные переводы. До 5 представителей выбираются по betweenness, PageRank, GID. Номера сообществ не являются устойчивыми бизнес-идентификаторами при изменении данных или версии алгоритма.

Аудит подтвердил, что разные исходные слабые компоненты не объединены. При этом у кластеров 31 и 51 подграфы внутри сообщества оказались несвязными (части 7+5 и 3+2 узла). Результат Louvain не трактуется как доказательство прямой связи всех участников или единой управляемой группы; фактические рёбра нужно проверять отдельно.

AI Assistant — необязательный разбор

В Client Analysis есть Ask about this client. Единственный провайдер — OpenAI Responses API, function calling. Модель по умолчанию gpt-4.1-mini; можно задать доступную в вашем аккаунте модель с поддержкой function calling через OPENAI_MODEL.

Ключ задаётся только в окружении OPENAI_API_KEY или локальном .streamlit/secrets.toml. Не добавляйте его в Git или исходники. Переменные окружения имеют приоритет; .env автоматически не загружается. Настройте секрет перед запуском Streamlit. Ключ не нужен для основной демонстрации.

Для локальной настройки откройте .streamlit/secrets.toml и вставьте ключ между кавычками в OPENAI_API_KEY = "". Если файла нет после клонирования, скопируйте .streamlit/secrets.example.toml в .streamlit/secrets.toml. Сохраните файл и перезапустите streamlit run app.py. Сейчас интеграция поддерживает ключ OpenAI; для другого провайдера нужен соответствующий адаптер.

Без ключа: AI Assistant unavailable. Core analytics remain available. Ошибки сети, лимита и авторизации показываются без трассировки и текста провайдера.

Инструменты только читают данные: get_node_profile, get_incoming_counterparties, get_outgoing_counterparties, get_node_priority_breakdown, get_node_temporal_profile, get_cluster_profile, get_top_nodes, find_nodes_by_role, find_multi_seed_nodes, get_cluster_with_most_seeds. Доступа к SQL, shell, веб-поиску и произвольным файлам у модели нет. GID и аргументы проверяются; выборки ограничены 50 строками, контрагенты — Top 20 с указанием полного числа.

Только после нажатия «Объяснить» вопрос и запрошенные инструментами результаты отправляются в OpenAI. Запрос включает профиль выбранного клиента; максимум 3 обращения API, 8 дополнительных вызовов инструментов, timeout 20 секунд на обращение, без автоматических повторов; store=False. Это не обещание отсутствия иных журналов провайдера. В интерфейсе доступны фактические результаты инструментов; внутренние рассуждения не показываются. Ответ привязан к GID и версии данных.

Формат: Finding / Evidence / Limitation / Next check. Системная инструкция запрещает выдуманные атрибуты, счета, связи, пересчёт оценок и обвинения. Однако генеративная модель может ошибиться: ответ необходимо сверять с показанными данными. AI не изменяет CSV и аналитический pipeline.

Dataset Limitations — ограничения

  • Только июль 2026 года; отсутствие событий в другом месяце не установлено.
  • Только внутрибанковские переводы по условиям набора; внешние банковские связи не наблюдаются.
  • Сбор обходом исходящих переводов seed, глубина до 4 переходов.
  • Переводы менее 5 000 KZT отсутствуют в предоставленном наборе.
  • Входящие переводы seed могут быть неполными.
  • Нулевой отток на глубине 4 не подтверждает конечное состояние: дальнейшие переводы не наблюдаются.
  • Есть только даты, без точной последовательности внутри дня.
  • Нет имён, ИИН, телефонов, адресов, демографии или остатков; разность потоков не является балансом счёта.
  • Нет эталонных ролей и подтверждённой разметки нарушений. Precision/recall качества AML-выводов не оценивались.

Interpretation — интерпретация

Результаты — гипотезы для дополнительной проверки аналитиком, не установление противоправности. Центральность, роль и принадлежность к кластеру не доказывают общий контроль, сговор или происхождение средств. Низкая оценка также не гарантирует отсутствие риска.

Testing and Demo — проверка и демонстрация

python -m unittest discover -s tests -q
python -m pip check
python scripts/audit_outputs.py --data ./data --out ./out

Тесты проверяют направления, изоляты, временные ограничения, правила ролей, формулу приоритета, Louvain, межфайловую согласованность, UI-сценарии и AI function calling с подменой провайдера. Они не отправляют запросы в OpenAI и не требуют ключа. Итог выполненной проверки и замеры: AUDIT.md. DEMO.md содержит три автоматически выбранных реальных примера и сценарий на 5 минут. Генератор: python scripts/prepare_demo.py после pipeline.

Scaling to 1M Nodes — развитие до миллиона узлов

Текущая реализация рассчитана на локальный исследовательский набор, а не на миллион узлов. Для такого объёма потребуются igraph/graph-tool или распределённый движок, приближённая центральность вместо точной betweenness, расчёт сообществ по компонентам, инкрементальные агрегаты и columnar storage с разбиением по датам/частям графа. Граф стоит хранить в разреженном представлении; центральности и достижимость считать пакетно с отдельным бюджетом ресурсов. Достижимость от seed и циклы нужно ограничивать и предварительно вычислять. UI должен получать подготовленные локальные подграфы, агрегированный граф кластеров и постраничные таблицы, а не весь граф. Предрасчёт, кэш и версии данных остаются основой; добавление этих технологий в текущий проект не требуется.

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

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

sanzh 9

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

  • 2026-09-23 17:14finalsanzh
  • 2026-09-23 16:49add aisanzh
  • 2026-09-23 15:47feat: добавить объяснимое ранжирование и проверку итоговых CSVsanzh
  • 2026-09-23 15:29кластеризация и прогон тестовsanzh
  • 2026-09-23 15:12классификация ролей,добавление формулы оценокsanzh
  • 2026-09-23 15:01добавил аналитику и графы и прогон тестов сделалsanzh
  • 2026-09-23 14:38first commit add the tests and utilssanzh
  • 2026-09-23 14:17delete the first commitsanzh
Все коммиты (9)
  • 2026-09-23 14:12first commitsanzh
Структура репозитория (43 файлов)
.gitignore
.streamlit/config.toml
.streamlit/secrets.example.toml
AUDIT.md
DEMO.md
README.md
analytics/__init__.py
analytics/clustering.py
analytics/explanations.py
analytics/features.py
analytics/graph_features.py
analytics/priority.py
analytics/roles.py
analytics/temporal.py
app.py
assistant/__init__.py
assistant/service.py
assistant/tools.py
pipeline.py
requirements.txt
scripts/audit_outputs.py
scripts/prepare_demo.py
tests/__init__.py
tests/fixtures.py
tests/test_advanced_features.py
tests/test_assistant.py
tests/test_clustering.py
tests/test_dashboard.py
tests/test_pipeline.py
tests/test_priority.py
tests/test_roles.py
tests/test_validation.py
ui/__init__.py
ui/assistant.py
ui/components.py
ui/data.py
ui/formatting.py
utils/__init__.py
utils/output_validation.py
utils/provenance.py
utils/validation.py
visualization/__init__.py
visualization/graph_view.py

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

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

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