О проекте
Команда: 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.mdAML 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 должен получать подготовленные локальные подграфы, агрегированный граф кластеров и постраничные таблицы, а не весь граф. Предрасчёт, кэш и версии данных остаются основой; добавление этих технологий в текущий проект не требуется.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.