AAYMA
RU
Войти

Граф денег

AML-инструмент: по сети переводов присваивает каждому из 2 248 клиентов объяснимую роль, выделяет кластеры и ранжирует, кого проверять первым.

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

О проекте

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

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

Суть

AML-инструмент: по сети переводов присваивает каждому из 2 248 клиентов объяснимую роль, выделяет кластеры и ранжирует, кого проверять первым. Аналитик работает с интерактивным графом, карточками узлов, таймлайном переводов по дням и AI-чатом, а на выходе получает отчёт с обоснованиями.

Как устроено

Конвейер app.pipeline читает три Parquet-файла, строит направленный граф в NetworkX и назначает роли по явным правилам: role_score, priority_score и объяснение с числами. Кластеры выделяются алгоритмом Louvain. Дополнительно ищутся направленные циклы длиной до 6, сильно связные компоненты, повторяющиеся цепочки A→B→C с согласованными датами и обрыв на 4-м колене, а также проверяется устойчивость сети после удаления топ-5/10/20 узлов. FastAPI отдаёт CSV, graph.json и API. Агент на OpenAI Agents SDK отвечает через 8 инструментов графа, без ключа работает mock по графу.

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

  • NetworkX
  • Louvain
  • strongly connected components
  • directed cycles ≤6
  • rule-based roles
  • robustness after top-k removal
  • OpenAI Agents SDK tools
  • vis-network

Метрики

2 248 узлов, 3 119 связей, 4 840 транзакций, 72 кластера, SCC из 85 узлов; расчёт 3,9 с; 21 проверка выгрузок + 24 проверки API; побайтная воспроизводимость Windows/Linux

Особенности

У каждой роли есть числовое обоснование и список недостающих данных (gaps), которые аналитику нужно запросить. Есть оценка устойчивости сети после удаления топ-узлов и жадная эвристика «кто даст больше новых операций» при формировании перечня на проверку. Воспроизводимость подтверждена SHA-256 на двух ОС. Первые коммиты 22.09 содержат только заготовку, код кейса написан 23.09.

Стек

Python · pandas · PyArrow · NetworkX · FastAPI · HTML/CSS/JS · vis-network · Tailwind · PowerShell

Модели

OpenAI (Agents SDK) · NVIDIA (опционально)


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

README

README.md

Граф денег

HackAlem AI 2026 · кейс Freedom · команда Kazhydromet AI

AML-аналитику известен нижний уровень из 81 «курьера», а сеть переводов охватывает 2 248 клиентов. Инструмент показывает, кого проверить первым и на каких наблюдаемых переводах основана гипотеза о роли каждого участника. Роль и приоритет не означают виновность; решение принимает аналитик.

Для жюри: запуск и проверка

Впервые в репозитории? Проверка за 5 минут: от клонирования до контрольных результатов.

Что делает решение

По исходным переводам назначает каждому клиенту объяснимую роль, выделяет кластеры и ранжирует проверку. Аналитик изучает связи и переводы по дням, выбирает клиентов и скачивает перечень с обоснованиями и запросами недостающих данных.

Как запустить

Windows PowerShell, из корня репозитория; нужны Python 3.12+ и uv. Установка зависимостей один раз:

uv venv .venv
uv pip install --python .venv\Scripts\python.exe -r requirements.txt

Затем одна команда пересчитывает данные и запускает приложение:

.\run.ps1

Откройте http://127.0.0.1:8000. Запасной экран — /graph.html. Для отдельного проверочного сервера: .\run.ps1 -Port 8012. Расчёт, интерфейс и локальные ответы чата работают без ключа. Для живого AI-чата нужен ключ выбранного провайдера на сервере и доступ к API; без ключа или при ошибке запросов используется локальный mock по графу. Ключ не передаётся браузеру и не добавляется в Git.

Общий AI для команды и жюри: приложение размещается на одном сервере, посетители открывают его URL без собственных ключей. Инструкция — docs/DEPLOY.md. Облачный адрес пока не опубликован; локальный адрес доступен только на компьютере, где запущен сервер. По умолчанию выбран OpenAI (LLM_PROVIDER=openai); в коде также есть опциональный адаптер NVIDIA. Пример настроек — .env.example; после изменения нужен перезапуск сервера. Выбор NVIDIA без её ключа включает локальные ответы, не расходуя ключ OpenAI. /api/health.live означает готовность настроек; доступ к модели и баланс проверяет реальный запрос.

Какие технологии использованы

Python, pandas, PyArrow и NetworkX — данные и расчёт; FastAPI — API; HTML/CSS/JavaScript, vis-network и локальный Tailwind — экран; OpenAI Agents SDK — агент с инструментами графа. Codex и Claude Code использовались как AI-агенты разработки под контролем команды.

Как проверить результат

Полная механическая проверка одной командой, без ключей AI:

.\.venv\Scripts\python.exe scripts\verify.py

Она запускает 21 проверку выгрузок и 24 проверки HTTP API на отдельном локальном порту 8013, останавливает свой сервер и создаёт out/verification.json: реальные коды завершения, вывод проверок, время, версия Git и SHA-256 исходных данных и выгрузок. overall_passed: true означает успешное выполнение этих проверок; это не оценка точности ролей. На macOS/Linux команда: .venv/bin/python scripts/verify.py. Порт можно изменить через --port.

Сохранённые результаты версии 5493619: Windows, Linux / WSL2, сравнение файлов. На обеих системах прошли 21 + 24 проверки; три CSV и graph.json совпали побайтно. В summary.json отличается только замер времени: 4,0 с на Windows и 4,1 с на Linux.

Только выгрузки:

.\.venv\Scripts\python.exe scripts\check_outputs.py

Ожидаемый результат: 21/21 OK, код завершения 0. Валидатор проверяет сохранённые выгрузки и новый расчёт из Parquet, включая лимит пяти минут. Реальный сохранённый вывод проверки: docs/VALIDATION.txt. В интерфейсе нажмите «Проследить переводы», откройте карточку клиента, добавьте его в «Перечень на проверку» и нажмите «Открыть отчёт / PDF». Покажите краткую сводку и раздел «Следующее действие аналитика»; PDF сохраняется через печать браузера. Скачивание Markdown доступно как альтернатива. Сверить числа можно с out/nodes_roles.csv, out/clusters.csv и out/top_nodes.csv. Подробные команды для Windows/macOS/Linux, критерии ролей, API и ограничения — ниже.

1. Что реализовано

  • Два представления связей: направленные «Потоки» (5 крупнейших плательщиков → клиент → 5 получателей) и полная схема «Сеть». Суммы в заголовке потоков учитывают всех контрагентов; выбранный день меняет показанные переводы.

  • Один локальный запуск app.pipeline читает три Parquet-файла, строит направленный граф и создаёт три обязательных CSV, graph.json и summary.json. Результат: 2 248 узлов, 3 119 связей, 72 кластера и топ-50. Исходные данные содержат 4 840 транзакций. При исходном контрольном запуске 23.09.2026 расчёт занял 3,9 с; свежий замер — поле seconds в out/summary.json. На другом ноутбуке время нужно измерить снова.

  • Роли и приоритет задаются явными правилами. Для каждого узла записаны role_score, priority_score, кластер и объяснение с числами.

  • Реализованы учёт обрыва четвёртого колена; доля исходящей суммы при наличии поступления в тот же или один из двух предыдущих дней; порядок внутри дня неизвестен; максимум разных плательщиков за день; направленные циклы длиной до шести и крупнейшая сильно связная компонента из 85 узлов; расчёт размера крупнейшей компоненты после исключения топ-5, топ-10 и топ-20 узлов. Эти 85 узлов не образуют одно простое кольцо.

  • Повторяющиеся цепочки A→B→C: обе пары имеют не менее двух переводов, и можно подобрать не менее двух независимых пар с неубывающими датами. Даты внутри дня неизвестны; это гипотеза маршрута, не доказательство происхождения одних и тех же средств. Блок свёрнут в карточке узла.

  • Сервер предоставляет карточку узла с входящими и исходящими связями, датами и списком недостающих данных (gaps: что запросить у аналитика), общий список приоритетов, кластеры и путь по связям. Агент на OpenAI Agents SDK использует восемь инструментов для ответов по графу, переводам по дням и охвату выбранного перечня.

  • Главный экран web/index.html объединяет схему сети, список приоритетов, карточку узла, кластеры и чат. web/graph.html остаётся запасным экраном с блоком устойчивости. Отдельный отчёт помогает передать аналитику перечень узлов и запросить недостающие сведения.

Экран аналитика

Адрес http://127.0.0.1:8000 открывает главный web/index.html: app/main.py раздаёт папку web/ с html=True и не перенаправляет /. Ссылка /#<gid> открывает карточку конкретного узла. Запасной экран доступен по /graph.html и тоже поддерживает /graph.html#<gid>. Команды запуска ниже привязывают сервер к 127.0.0.1, поэтому он виден только с этого ноутбука.

  • Первый экран. Три числа: 2 248 клиентов, 17 кандидатов в организаторы, 37 узлов с признаками аномалий. Под ними — первые десять участников по приоритету и кнопка «Показать ещё». Каждая строка содержит роль словами и короткое объяснение plain; полный gid доступен при наведении и в карточке.
  • Схема сети. Стрелки показывают направление перевода. По умолчанию виден топ-50; выбор узла показывает его прямых плательщиков и получателей, выбор кластера — узлы этой группы. Раскраска и «Вся сеть» находятся в свёрнутых «Настройках схемы». Вся сеть из 2 248 узлов может нагружать браузер.
  • Обозначения. Цвет узла задаёт роль; можно переключить раскраску на кластеры. Белая рамка на главном экране (чёрная на запасном) означает исходного клиента (seed), золотая — принадлежность к крупнейшей сильно связной компоненте («кольцу возвратных потоков»), пунктир — обрыв исходящих на четвёртом колене. Размер узла отражает относительный приоритет.
  • Карточка. Роль и простое объяснение, затем три факта: от скольких людей и сколько получил, скольким и сколько отправил, место в списке проверки. Ниже — «Почему такая роль», «Что проверить дальше» и аномалии. Остальные показатели находятся в свёрнутых «Подробных метриках».
  • Кластеры и чат. Кластеры свёрнуты внизу экрана. Чат предлагает подсказки, показывает ответ и вызванные инструменты; без ключа работает локальный демо-режим. На запасном graph.html есть также блок устойчивости после исключения топ-5, топ-10 и топ-20 узлов и поиск по однозначному суффиксу gid.
  • Перечень и отчёт. В карточке нажмите «В перечень на проверку». Кнопка в шапке открывает выбранных клиентов: можно убрать клиента, отменить удаление и нажать «Открыть отчёт / PDF». Готовый документ показывает краткую сводку, основания проверки и следующий запрос недостающих сведений. До 100 идентификаторов сохраняются только в этом браузере; карточки при открытии перечня обновляются из API. Печатная версия /api/report/view?gids=... позволяет сохранить тот же отчёт в PDF через диалог печати браузера. Альтернатива — скачать Markdown для выбранных gid через GET /api/report?gids=.... Ссылка «Скачать топ-20 по приоритету» внутри окна даёт исходный автоматический список.
  • Что даст следующая проверка. В перечне считаются уникальные видимые переводы; операция между двумя выбранными клиентами учитывается один раз. Следующий кандидат выбирается среди невыбранных клиентов топ-50 без seed: максимум новых операций, затем больший приоритет, затем меньший gid. Пустой перечень начинается с первого по приоритету. Это последовательная эвристика, не гарантия оптимального набора. Охват сохраняется в Markdown. Он не означает преступный оборот или эффект блокировки.
  • Навигация. Боковая панель ведёт к схеме, AI-аналитику и кластерам. У схемы есть масштаб, центрирование и полноэкранный режим. Кнопка «Назад» возвращает к предыдущему клиенту. Чат сохраняет контекст текущего диалога; явные gid в вопросе имеют приоритет перед открытой карточкой.
  • Проследить переводы. Кнопка на первом экране открывает календарный режим выбранного клиента (или первого в рейтинге). Шкала показывает реальные суммы по дням июля; воспроизведение меняет дату, наблюдаемые связи и суммы. Синие стрелки — входящие, зелёные — исходящие. Бледные узлы связаны в другие дни. «Весь период» возвращает месячную схему. Порядок внутри дня и происхождение одних и тех же средств не установлены; для обрыва исходящие обозначены неизвестными. Источник — GET /api/timeline/{gid}, агрегирование исходных транзакций по дню и паре.

Главный экран: приоритет проверки, схема связей и карточка клиента

Карточка координатора · Пример аномалии · Обрыв четвёртого колена · Перечень выбранных клиентов

Переводы по дням

ТЗ · План · Схема на один слайд · Сценарий питча

2. Быстрый старт

Нужен Python 3.12 или новее; на Windows используем uv. Команды выполняются из корня репозитория. После однократной установки пакетов одна команда пересчитывает результаты из data/raw/*.parquet в out/. Интернет и ключ API для расчёта не нужны.

macOS / Linux:

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m app.pipeline

Windows PowerShell:

uv venv .venv
uv pip install --python .venv\Scripts\python.exe -r requirements.txt
.\.venv\Scripts\python.exe -m app.pipeline

Не пересоздавайте существующую .venv во время показа. Параметры расчёта: --data <папка> и --out <папка>. Повторный запуск заменяет результаты в выходной папке. Перед показом команда должна запустить тот же расчёт на чистом окружении и зафиксировать время: требование ТЗ — не более пяти минут.

Запуск интерфейса на macOS / Linux:

.venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8000

Одна команда запуска экрана для жюри на Windows после установки пакетов:

.\run.ps1

Скрипт сначала пересчитывает исходные Parquet, затем запускает экран; при ошибке расчёта сервер не запускается. Для отдельной проверки можно задать порт: .\run.ps1 -Port 8012. Адрес по умолчанию: http://127.0.0.1:8000. Остановить сервер: Ctrl+C. Ключ для необязательных ответов модели задаётся только в .env; образец .env.example содержит пустую переменную OPENAI_API_KEY. Если ключа нет или запрос к API не удался, чат возвращает ответ в демо-режиме (mode: mock) по локальным данным графа. /api/health проверяет наличие ключа, но не успешность ответа модели. Модель задаётся переменной OPENAI_MODEL; в коде по умолчанию gpt-5-mini. Текущее имя возвращает /api/health; наличие настройки не подтверждает живой ответ. В финальной независимой проверке проверен mock, без внешней модели; это не проверка доступа к выбранной модели. Модель используется только в чате, роли рассчитываются локально. Секретный ключ не добавлять в Git. Основной графовый экран загружает Tailwind и vis-network из web/vendor/, поэтому CDN для схемы не нужен.

3. Как проверить

Из корня репозитория после установки зависимостей:

Windows PowerShell:

.\.venv\Scripts\python.exe scripts\check_outputs.py

macOS / Linux:

.venv/bin/python scripts/check_outputs.py

Скрипт scripts/check_outputs.py выполняет 21 проверку по основным условиям ТЗ: пересчёт менее пяти минут, схема и полнота трёх CSV, уникальность и покрытие gid, допустимость оценок, ранжирование топа и отсутствие роли terminal у обрезанного четвёртого колена. Он пересчитывает данные во временную папку и применяет проверки к двум наборам: сохранённым out/*.csv и только что полученным CSV. Результат каждого пункта объединяет оба набора; при любом FAIL скрипт завершается с кодом 1. Два прогона не сравниваются побайтно.

Louvain получает seed=42, поэтому случайность кластеризации зафиксирована. При равной оценке приоритета строки упорядочиваются по полному gid по возрастанию, включая топ-50 и лидеров кластеров. Это не гарантия побайтного совпадения всего out/ в любом окружении: summary.json содержит время выполнения, а последние разряды чисел могут зависеть от версий численных библиотек. Проверяйте значения, порядок и инварианты скриптом; межплатформенное сравнение выше относится к указанной версии.

Проверка HTTP API без ключей и внешней модели:

.\.venv\Scripts\python.exe scripts\smoke_api.py

Скрипт использует стандартную библиотеку, запускает собственный сервер на 127.0.0.1:8013 и останавливает его после проверки. Чтение .env отключено, ключи в окружении дочернего сервера пустые; существующий сервер на занятом порту не затрагивается. Другой порт: --port 8014. Проверяются статические файлы, графовые API, отчёты, локальный чат и запрет изменений при PUBLIC_DEMO=1.

4. Выходные файлы

ФайлОбязательные поля ТЗЧто добавлено
out/nodes_roles.csv — 2 248 строкgid, role, role_score, cluster_id, priority_score, evidence (до 200 символов)depth, is_seed, truncated, степени и суммы входа/выхода, числа переводов, pass_through, PageRank, HITS, посредничество, метрики исходных клиентов, циклы, временные признаки, anomaly_count, anomaly_flags
out/clusters.csv — 72 строкиcluster_id, n_nodes, n_seed, sum_kzt_internal, top_gids, hypothesisroles — число участников по каждой роли
out/top_nodes.csv — 50 строкrank, gid, role, priority_score, whycluster_id, is_seed

out/graph.json содержит узлы с простым объяснением plain, направленные рёбра, пороги и расчёт устойчивости; out/summary.json — счётчики и время. Они не заменяют CSV. В JSON и API gid передаётся строкой: 18-значное число нельзя точно представить обычным числом JavaScript.

Отчёт out/report.md создаётся отдельно, не командой app.pipeline:

.venv/bin/python -m app.report

На Windows: .\.venv\Scripts\python.exe -m app.report. По умолчанию отчёт содержит топ-10; параметры --top, --gids (список через запятую) и --out меняют выбор узлов и путь файла. GET /api/report?top=10 скачивает такой же Markdown-отчёт. В нём есть рейтинг, карточки узлов с метриками и крупнейшими контрагентами, пробелы в данных, запросы для аналитика и ограничения метода. GET /api/report/view?top=10 открывает HTML-версию того же отчёта без внешних ресурсов: кнопка «Печать / сохранить PDF» вызывает печать браузера. Для этого эндпоинта top — от 1 до 100, gids — до 100 разных идентификаторов через запятую. Существующий Markdown API не изменён.

5. Критерии ролей

Источник истины: THRESHOLDS и assign_role в app/pipeline.py. Правила проверяются сверху вниз: первая подходящая ветка назначает единственную основную роль. Здесь I и O — число разных отправителей и получателей; A и B — наблюдаемые входящая и исходящая суммы; p=B/A; S — число известных исходных клиентов в двух шагах выше; C=1 — член крупнейшей сильно связной компоненты; F — доля исходящей суммы, для которой есть поступление в тот же или один из двух предыдущих календарных дней. Порядок операций внутри дня неизвестен. Это доля суммы, а не количества операций; даты не доказывают перевод тех же средств. min(1; x) ограничивает оценку сверху единицей.

Первая подходящая проверкаРольrole_scoreФрагмент настоящего evidence
I=0, O=0peripheral0,9«seed; нет переводов ≥5 000 ₸ внутри выгрузки»
(I≥5 и O≥10) или (S≥3 и O≥3)coordinatormin(1; 0,5+0,03I+0,005O+0,05S+0,1C)«собирает от 5 плательщ. (984 635 ₸), раздаёт 99 получ.»
O≥10distributormin(1; 0,5+O/100)«46 переводов на 34 разных получателей, 3 002 628 ₸»
I≥3consolidatormin(1; 0,45+0,05I+0,1 при ≥3 разных плательщиках за день)«получает от 8 разных плательщиков (15 переводов, 2 160 500 ₸)»
depth=4, O=0peripheral, truncated=True0,3«4-е колено: получил 2 226 000 ₸ от 1 плательщ.; исходящие не выгружались»
I>0, O=0 и (A≥100 000 ₸ или I≥2)terminalmin(1; 0,6+A/5 000 000)«получил 569 251 ₸ от 2 плательщ. (2 переводов), дальше ничего»
I>0, O=0, порог выше не достигнутperipheral0,7«получил 70 020 ₸ (1 перев.) и дальше не переводил»
I>0, O>0, p<0,2, не seedterminal0,55«дальше ушло лишь 51 800 ₸ (16%, 2 получ.)»
O>0, seedtransit0,45«seed: отправил 1 505 966 ₸ 4 получ.; полнота входящих неизвестна — роль по исходящим»
O>0, 0,5≤p≤2transitmin(1; 0,55+0,4F−0,2abs(1−p))«отдал дальше 81% полученного (3 получ.); 85% исходящих в дату входа или +1–2 дня»
O>0, p>2transit0,35«отдал 1 668 800 ₸ при видимых входящих 192 930 ₸»
Остальные случаиperipheral0,5«отдал дальше 34% полученного; ни сбора, ни веера, ни чистого транзита»

Оценки округлены до трёх знаков; это сила правила, не вероятность преступления. Таблица показывает применяемое правило, а не доказанную природу операций. Суммы и количества переводов приводятся в evidence; знаки направления учитываются в I/O, A/B. Для seed входящая сумма неполна: коэффициент p в их роли не применяется. Если A=0, pass_through=-1 в CSV означает «не определён».

В текущем расчёте: 17 coordinator, 161 consolidator, 49 distributor, 367 transit, 401 terminal, 1 253 peripheral. Все 2 248 узлов включены, в том числе 19 исходных клиентов без единого видимого перевода.

6. Ловушки данных

  1. Обрыв четвёртого колена. Из него исходящие не выгружались. У 444 узлов truncated=True: 441 получил peripheral с явной оговоркой, а 3 — consolidator, потому что правило I≥3 стоит выше правила обрыва. Их дальнейшее движение неизвестно; по нулевым исходящим нельзя делать вывод об удержании средств. У узлов колен 0–3 исходящие вошли в выгрузку, поэтому отсутствие исходящих поддерживает гипотезу terminal в пределах банка, месяца и порога. У трёх consolidator четвёртого колена evidence прямо указывает: «исходящие не выгружались».
  2. Неполный вход у seed. Поиск начинается от 81 известных клиентов; поступления извне графа не видны. Отношение B/A для них не используем. У остальных узлов входящие также могут быть неполными; неизвестен и остаток до начала июля. B/A>1 не доказывает «приумножение» и не позволяет выбрать единственную причину превышения. При B>A (включая нулевой видимый вход) карточка предлагает запросить полную выписку и остаток на 01.07.2026; для seed — всегда. Для проверки баланса нужны полная выписка и остаток на начало периода.
  3. Сумма и число переводов различаются. Входящие/исходящие суммы и числа контрагентов влияют на роль и приоритет; evidence дополнительно показывает число отдельных переводов. Равный оборот может скрывать разную структуру операций.
  4. Направление. Степени, роль, пути и PageRank вычисляются на направленном графе. Louvain для сообществ использует неориентированную проекцию; суммы встречных рёбер складываются. Анализ устойчивости тоже использует неориентированную проекцию: оценивает связность, а не движение денег.

7. Детекция аномалий

Это дополнительный пункт ТЗ: anomaly_flags() в app/pipeline.py назначает наблюдательные флаги по трём правилам из THRESHOLDS.

ФлагФормальное условие
ДроблениеУ получателя не менее 5 входящих переводов, и не менее 70% из них на сумму от 5 000 ₸ включительно до 10 000 ₸ не включительно.
Повтор суммыОдин отправитель совершил не менее 8 переводов на одинаковую сумму; получатели могут быть разными.
Нетипичный оборотДля узла z-score от log1p(входящая + исходящая сумма) среди узлов того же колена больше 2,5.

anomaly_flags в out/nodes_roles.csv содержит текст сработавших флагов (или «нет»), anomaly_count — их число. В out/summary.json anomalies=37 означает 37 узлов хотя бы с одним флагом, не 37 доказанных нарушений. Например, 100000000332284100 имеет один флаг дробления: 10 из 12 входящих переводов на 5 000–9 999 ₸ от 8 разных плательщиков. Во всех 12 входящих переводах — 10 разных плательщиков; число отправителей малых переводов посчитано отдельно по исходным операциям. Эти признаки помогают выбрать следующий запрос, но не участвуют в assign_role() или priority() и не меняют роль и приоритет узла. Главный экран показывает флаги в карточке узла.

8. Приоритет проверки

При равном priority_score клиенты упорядочены по полному gid по возрастанию. Это фиксирует границу топ-50 и лидеров кластеров между версиями pandas/NumPy; баллы и роли не меняются. Валидатор проверяет также этот порядок.

priority() считает предварительную оценку:

P = 0,40 × вес_роли × role_score
  + 0,20 × процентиль(входящая + исходящая сумма)
  + 0,15 × процентиль(PageRank)
  + 0,15 × процентиль(seed в двух шагах выше)
  + 0,05 × процентиль(посредничество)
  + 0,05 × признак крупнейшей сильно связной компоненты

Веса ролей: coordinator=1, consolidator=0,85, distributor=0,75, transit=0,5, terminal=0,35, peripheral=0,05. Процентиль — относительное место в текущих 2 248 узлах. Известным seed-клиентам P умножается на 0,8, чтобы сместить внимание с уже известных «курьеров» на неизвестных посредников и сборщиков. Для truncated тоже применяется ×0,8; при обоих признаках множители перемножаются. После этого P делится на максимум по сети и округляется до четырёх знаков. Это приоритет проверки, не вероятность виновности; при другой выборке оценка изменится.

HITS (hub, authority) сохраняется как вспомогательная метрика: он не входит в правила ролей и формулу приоритета. Номер кластера Louvain также не влияет на роль или приоритет.

9. Кластеры и гипотезы

Сообщества строятся алгоритмом Louvain на неориентированной проекции с весом sum_kzt, seed=42 и resolution=1. Кластер 0 объединяет 19 изолированных seed только для учёта в результате: связи между ними не предполагаются. Остальные сообщества нумеруются с 1 по убыванию размера; всего в результате 72 кластера.

Для каждого кластера считаются размер, число исходных клиентов, сумма переводов внутри кластера, пять первых узлов по приоритету и состав ролей. hypothesis формируется из числа координаторов, накопителей, распределителей и транзитных узлов, числа seed (общая инфраструктура / цепочка одного seed / дальнее окружение) и первого по приоритету узла. Это текстовое описание правил, не независимое доказательство назначения кластера. Кластеры — группы связей; роли — признаки отдельных участников. Слабосвязных компонент 35 с учётом 19 изолированных; это другое разбиение, чем Louvain.

10. Архитектура и API

flowchart LR
    A[3 Parquet: узлы, связи, переводы] --> B[Направленный граф и метрики]
    B --> C[Правила ролей]
    B --> D[Louvain-кластеры]
    C --> E[Приоритет]
    B --> E
    E --> F[3 CSV, graph.json, summary.json]
    D --> F
    F --> G[FastAPI: методы графа]
    G --> H[Графовый экран аналитика]
    G --> I[Агент: 8 инструментов]
    H --> J[Решение принимает аналитик]
    I --> J

Реализованные методы FastAPI: GET /api/overview, /api/graph, /api/node/{gid}, /api/top?n=&role=, /api/clusters, /api/clusters/{cluster_id}, /api/path?src=&dst=, POST /api/recompute, POST /api/chat, GET /api/health, GET /api/report?top=&gids= (скачивание Markdown-отчёта). GET /api/report/view?top=&gids= — печатный HTML для сохранения в PDF; top от 1 до 100 или до 100 разных gids через запятую. GET /api/review-coverage?gids=a,b — объединённый охват до 100 клиентов и следующий кандидат; возвращает числа операций/связей/контрагентов, сумму, доли и next_candidate с добавляемыми операциями. GET /api/routes?gid=&limit=10 возвращает повторяющиеся цепочки; gid необязателен, limit ограничен 100. В ответе total, routes и note. В карточке добавлены repeated_routes (до трёх примеров), repeated_routes_count и repeated_routes_note. На роли и баллы это не влияет. На предоставленном наборе найдено 429 цепочек. Внутри одной цепочки каждый перевод используется один раз; между цепочками переводы могут повторяться, поэтому суммы разных цепочек нельзя складывать. GET /api/timeline/{gid} — наблюдаемые переводы выбранного клиента по календарным дням (даты, суммы, количества, пары отправитель → получатель, признак обрыва). Дополнительно сервер сохраняет методы табличного датасета и прежней аналитики: /api/summary, /api/anomalies, /api/refusals, /api/dataset, POST /api/dataset/upload и POST /api/dataset/reset. /api/overview включает число узлов с аномалиями (anomalies). /api/graph возвращает идентификаторы строкой и объяснения plain; /api/node/{gid} даёт plain, evidence, соседей и gaps. /api/path отдаёт до пяти кратчайших направленных путей с признаком chronology_ok: маршрут считается возможным по времени только при неубывающих датах отдельных переводов на каждом шаге. Суммы переводов между шагами не сопоставляются, поэтому даже путь с подходящими датами не доказывает движение именно тех же денег. /api/top возвращает plain, evidence, why и глобальный rank даже при фильтре по роли.

Восемь инструментов агента: network_overview (сводка и устойчивость), top_priority (рейтинг), node_card (карточка и пробелы в данных), common_receivers (общие получатели до трёх шагов), money_path (структурная направленная цепочка), cluster_info (группа и гипотеза), daily_transfers (переводы клиента за день или дневная сводка месяца) и review_scope (охват перечня и следующий кандидат). При вопросе агента об общих получателях он опирается на рассчитанный граф; в режиме mock ответ формируется локально, без вызова языковой модели. Инструмент node_card передаёт модели metrics_glossary со значениями метрик: «отдал дальше» — отношение исходящей суммы к видимой входящей, а «скорость транзита» — доля исходящей суммы, для которой есть поступление в тот же или один из двух предыдущих календарных дней. Внутридневной порядок и происхождение средств неизвестны. Инструкция агенту требует не смешивать эти показатели. Живому ответу отведено не более 25 секунд; при таймауте или ошибке возвращается локальный ответ по графу. /api/chat принимает непустую историю до 50 сообщений с ролями user/assistant и текстовым content; служебные роли из клиентского запроса не принимаются. Опциональный объект context передаёт выбранную карточку (selected_gid, строка из 18 цифр), день (selected_date, существующая дата 2026-07-DD) и перечень (review_gids, до 100 строк из 18 цифр). Например:

{"messages":[{"role":"user","content":"Что произошло в этот день?"}],"context":{"selected_gid":"100000008603629100","selected_date":"2026-07-23","review_gids":["100000008603629100","100000003037476100"]}}

Явные gid и даты в вопросе имеют приоритет. Пустой review_gids: [] означает пустой перечень; отсутствие поля — перечень не передан. Запросы без context сохраняют прежний формат. Примеры: «Что произошло 23 июля у 100000008603629100?» и «Какой охват перечня и кого добавить?». Дневные суммы берутся из транзакций; охват — объединение видимых операций без двойного счёта, не преступный оборот.

11. Три узла для проверки жюри

Все примеры взяты из сохранённого out/top_nodes.csv и сверены с out/nodes_roles.csv. Они не зашиты в правила.

Ранг, gidПочему такая роль
1, 100000000331309100coordinator: 5 разных плательщиков, вход 984 635 ₸; 99 получателей, выход 23 001 375 ₸; 3 seed в двух шагах выше. Проходит первую составную проверку I≥5, O≥10. role_score=1, priority_score=1. Член крупнейшей сильно связной компоненты; через него проходит 32 найденных коротких цикла.
11, 100000003115284100consolidator: 8 плательщиков, 15 входящих переводов на 2 160 500 ₸, 2 получателя и выход 517 000 ₸; I≥3, предыдущие проверки не подходят. За один день было до 7 разных плательщиков. role_score=0,95, priority_score=0,8715.
16, 100000005933757100distributor: 4 плательщика и 34 получателя; 46 исходящих переводов на 3 002 628 ₸. Порог O≥10 сработал раньше правила накопителя I≥3. role_score=0,84, priority_score=0,8139.

При тех же оценках равенства разрешаются по gid; новые данные или изменение правил могут изменить ранги. Перед выступлением сверить значения по свежим CSV. Жюри может назвать любой gid: правила применяются ко всем 2 248 узлам.

12. Ограничения

Видны только внутрибанковские исходящие переводы на четыре колена за 01.07.2026–31.07.2026, с порогом 5 000 ₸. Внешние и более мелкие операции не видны, как и последующие переводы с четвёртого колена. Нет ФИО, ИИН, остатков и размеченных правильных ролей (ground truth). Правила и role_score не валидированы на реальных расследованиях. «Признаки консолидации», «возможно требует проверки» — корректные формулировки; обвинение и блокировка требуют решения человека. Общая сумма переводов 365 890 012,01 ₸ — оборот рёбер, не уникальные деньги и не оценка ущерба. Данные обезличены и предоставлены для хакатона.

Временной признак отмечает исходящий перевод, если любое поступление датировано тем же или одним из двух предыдущих календарных дней. Порядок операций внутри дня неизвестен. Признак не сопоставляет входящую и исходящую суммы и не доказывает прохождение именно тех же средств. Поиск общих получателей удаляет повторяющиеся входные gid; на сцене всё равно лучше задавать пять разных участников. Основной экран использует локальные библиотеки из web/vendor/; для ответов внешней языковой модели сеть всё ещё нужна, но демо-режим чата работает локально. Режим «вся сеть» может быть тяжёлым для браузера.

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

Это план, а не уже реализованная производительность. Для крупного графа нужны измерение памяти и потоковая обработка рёбер. NetworkX заменить на igraph или graph-tool; полное посредничество убрать либо оценивать по выборке из k узлов. Циклы искать только внутри сильно связных компонент и с ограничением длины. Для устойчивых сообществ рассмотреть Leiden вместо Louvain. Рёбра хранить в Parquet с аналитическими запросами через DuckDB либо в графовой базе Neo4j — выбор зависит от сценария. На экране показывать агрегированные кластеры и окрестность выбранного участника вместо миллиона точек. Обещание пяти минут на этом масштабе нужно подтверждать отдельным замером. Повторяющиеся цепочки считать в выбранных окрестностях с ограничением числа кандидатов: полный перебор сочетаний входящих и исходящих звеньев у крупных узлов дорог.

14. Технологии и вклад команды

Python, pandas, pyarrow, NetworkX, SciPy, FastAPI и OpenAI Agents SDK используются в текущем коде. Оба графовых экрана (index.html и graph.html) применяют vis-network и Tailwind и берут библиотеки из web/vendor/.

Codex и Claude Code — AI-агенты разработки под контролем команды. Согласно истории проекта, Ali с Claude Code подготовили пайплайн ролей, кластеров и приоритета, API, первоначального агента, запасной экран, офлайн-библиотеки, словарь метрик и флаги аномалий. Ali с Codex подготовили валидатор, ревью и отчёт аналитика; при финальной проверке Codex также упростил главный экран и сверил документацию. Yerbu создал главный экран и раскладку графа, Abylaikhan — первоначальные README, питч и вопросы жюри. Рабочий AML-ассистент на OpenAI Agents SDK отвечает по инструментам графа; роли назначает детерминированный код.

Состояние работ · Сценарий на пять минут.

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

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

Ali 61Abylaikhan 17Yerbu 9

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

  • 2026-09-23 17:48docs: verify final demo and qualify evidenceAbylaikhan
  • 2026-09-23 17:45Record confirmed Astana Hub submissionAli
  • 2026-09-23 17:44Clarify small-transfer payers and calendar-date evidenceAli
  • 2026-09-23 17:39Address final jury review and clarify incomplete-flow evidenceAli
  • 2026-09-23 17:32Finish verification record and test environment cleanupAli
  • 2026-09-23 17:30Record cross-platform verification and prepare submissionAli
  • 2026-09-23 17:28docs: confirm stable ranking verification on macOSAbylaikhan
  • 2026-09-23 17:27docs: record macOS release verificationAbylaikhan
Все коммиты (86)
  • 2026-09-23 17:25Stabilize equal-priority rankings across platformsAli
  • 2026-09-23 17:18Add contextual AI, printable reports and reproducible verificationAli
  • 2026-09-23 17:10data: save validated local pipeline outputsAbylaikhan
  • 2026-09-23 16:55Record final verification and jury handoffAli
  • 2026-09-23 16:53auto: hourly snapshot (ALI)Ali
  • 2026-09-23 16:46Add directed money flows and a professional analyst workspaceAli
  • 2026-09-23 16:44Harden AI provider selection and prepare shared demo deploymentAli
  • 2026-09-23 16:34Fix timeline state and early search; clarify jury quickstartAli
  • 2026-09-23 16:29Optional NVIDIA provider (OpenAI-compatible API), off by defaultAli
  • 2026-09-23 16:26QA: verify daily transfer timeline on PC2Yerbu
  • 2026-09-23 16:25docs: timed analyst demo storyAbylaikhan
  • 2026-09-23 16:19Document verified analyst experience and final team tasksAli
  • 2026-09-23 16:16Add observed daily transfer playback for analyst investigationsAli
  • 2026-09-23 16:07Polish analyst workspace and complete selected review workflowAli
  • 2026-09-23 16:02QA: independent analyst workflow verification on PC2Yerbu
  • 2026-09-23 15:57report: clarify analyst findings and follow-up requestsAbylaikhan
  • 2026-09-23 15:53auto: hourly snapshot (ALI)Ali
  • 2026-09-23 15:46Assign independent UX verification and report refinementAli
  • 2026-09-23 15:41Verify organizer materials and document submission stepAli
  • 2026-09-23 15:37Document final verification and team handoffAli
  • 2026-09-23 15:30Load environment only from the current checkoutAli
  • 2026-09-23 15:28Add repeated chronological routes to node cardsAli
  • 2026-09-23 15:22Remove unused legacy generators and query helpersAli
  • 2026-09-23 15:22Align five-minute demo and solution diagram with verified UIAli
  • 2026-09-23 15:21Verify README and provide one-command startupAli
  • 2026-09-23 15:13docs: align demo and scheme with analyst screenAbylaikhan
  • 2026-09-23 15:08Harden graph explanations and chat fallbackAli
  • 2026-09-23 15:02Make analyst screen explain priorities in plain languageAli
  • 2026-09-23 14:53auto: hourly snapshot (ALI)Ali
  • 2026-09-23 14:40docs: document anomaly flags and current demo screenAbylaikhan
  • 2026-09-23 14:40docs: project history for agents joining mid-hackathonAli
  • 2026-09-23 14:35UI: clarify node metrics and anomaly signalsYerbu
  • 2026-09-23 14:30docs: update offline pitch and submission copyAbylaikhan
  • 2026-09-23 14:27Serve Yerbu's index.html as main screen; graph.html stays as backupAli
  • 2026-09-23 14:27Add rule-based anomaly flags: structuring, repeated amounts, depth outliersAli
  • 2026-09-23 14:26UI: readable graph layout, deep links, report buttonYerbu
  • 2026-09-23 14:23Agent: metrics glossary in node card, clearer pass-through and speed wordingAli
  • 2026-09-23 14:21docs: document graph demo and output checksAbylaikhan
  • 2026-09-23 14:19Bind dev server to 127.0.0.1 explicitlyAli
  • 2026-09-23 14:14Vendor Tailwind and vis-network so the screen works offlineAli
  • 2026-09-23 14:13Remove tmp PDF renders from repo, ignore tmp/Ali
  • 2026-09-23 14:12docs: README, scheme, pitchAbylaikhan
  • 2026-09-23 14:10auto: hourly snapshot (2005e)Yerbu
  • 2026-09-23 14:08docs: update frontend demo statusYerbu
  • 2026-09-23 14:08UI: money graph screenYerbu
  • 2026-09-23 14:05Add analyst report: node cards and request listAli
  • 2026-09-23 14:02Redirect root to money graph screenAli
  • 2026-09-23 14:00Add money graph screen: vis-network graph, top list, node card, clusters, chatAli
  • 2026-09-23 13:55Apply Codex review: chronology-checked money paths, truncated evidence, global rank and why in top, labelsAli
  • 2026-09-23 13:51Add output validator and pipeline reviewAli
  • 2026-09-23 13:45docs: document verified graph rules and add required scipy dependencyAbylaikhan
  • 2026-09-23 13:40docs: prepare Money Graph README, customer questions and pitchAbylaikhan
  • 2026-09-23 13:37Add graph API endpoints and AML graph agent tools with graph-based mockAli
  • 2026-09-23 13:34Add money-graph pipeline: rule-based roles, Louvain clusters, priority, outputsAli
  • 2026-09-23 13:28docs: case 'Money graph' plan, spec, raw data; add networkx/pyarrowAli
  • 2026-09-23 13:15Force tool use, retry transient API errors, faster demo modelAli
  • 2026-09-23 12:30docs: update product and pitch statusAbylaikhan
  • 2026-09-23 12:17Add fallback bank dataset and market-track scenariosAli
  • 2026-09-23 12:11docs: отметить настройку ноутбука роли BYerbu
  • 2026-09-23 12:03docs: add kickoff questions for customerAbylaikhan
  • 2026-09-23 12:03chore: enable pre-commit hookAbylaikhan
  • 2026-09-23 12:03docs: identify product and pitch ownerAbylaikhan
  • 2026-09-23 11:54Обновить статус роли BYerbu
  • 2026-09-23 11:17Rebuild demo around public finance: charges, payments, benefitsAli
  • 2026-09-23 11:08Align security section and README with the gov services trackAli
  • 2026-09-23 11:07Refocus docs on public finance inside e-government trackAli
  • 2026-09-23 10:58Assign backend role in team statusAli
  • 2026-09-23 10:56Add shared team status file and sync scriptAli
  • 2026-09-23 00:20Keep shell scripts and git hooks as LFAli
  • 2026-09-23 00:19Add pre-commit hook that blocks committing secretsAli
  • 2026-09-23 00:19hook check: normal commit passesAli
  • 2026-09-22 23:25Add generic dataset ingestion so the agent works on any finance datasetAli
  • 2026-09-22 22:54Fix Codex prompt passing in autocommit; document Codex limitsAli
  • 2026-09-22 22:53auto: hourly snapshot (ALI)Ali
  • 2026-09-22 22:52auto: hourly snapshot (ALI)Ali
  • 2026-09-22 22:51auto: hourly snapshot (ALI)Ali
  • 2026-09-22 22:51Add hourly autocommit scripts and Claude/Codex shared setupAli
  • 2026-09-22 22:42Add pre-hackathon starter scaffoldAli
Структура репозитория (102 файлов)
.env.example
.gitattributes
.githooks/pre-commit
.gitignore
.python-version
AGENTS.md
CLAUDE.md
README.md
app/__init__.py
app/agent.py
app/coverage.py
app/dataset.py
app/graph.py
app/main.py
app/pipeline.py
app/report.py
app/report_html.py
app/routes.py
app/timeline.py
app/tools.py
data/payments.csv
data/raw/README.md
data/raw/edges.parquet
data/raw/nodes.parquet
data/raw/transactions.parquet
docs/DEMO-STORY.md
docs/DEPLOY.md
docs/DESIGN-codex.md
docs/FINAL-CHECK.md
docs/FINAL-DEMO-CHECK.md
docs/HISTORY.md
docs/JURY-CAVEATS-VERIFICATION.json
docs/JURY-CHECK.md
docs/JURY-FINAL-VERIFICATION.json
docs/JURY-SELF-REVIEW.md
docs/PITCH.md
docs/PLAN.md
docs/PROMPT-ABYLAYKHAN-DEMO.md
docs/PROMPT-PC2-QA.md
docs/PROMPT-PC3-REPORT.md
docs/PROMPT-YERBU-FINAL.md
docs/QA-PC2.md
docs/QA-TIMELINE-YERBU.md
docs/QUESTIONS.md
docs/README.md
docs/REPORT-PC3.md
docs/REPRODUCIBILITY.json
docs/REVIEW-codex-2.md
docs/REVIEW-codex.md
docs/SCHEME.md
docs/STATUS.md
docs/SUBMISSION.md
docs/VALIDATION.txt
docs/VERIFICATION-LINUX.json
docs/VERIFICATION-WINDOWS.json
docs/case/STARTER.md
docs/case/TZ.md
docs/case/starter_reference.py
docs/screenshots/anomaly.png
docs/screenshots/coordinator.png
docs/screenshots/coverage.png
docs/screenshots/main.png
docs/screenshots/report.png
docs/screenshots/review-list.png
docs/screenshots/routes.png
docs/screenshots/timeline.png
docs/screenshots/truncated.png
out/clusters.csv
out/graph.json
out/nodes_roles.csv
out/report.md
out/summary.json
out/top_nodes.csv
railpack.json
requirements-extra.txt
requirements.txt
run.ps1
scripts/autocommit.ps1
scripts/autocommit.sh
scripts/check_outputs.py
scripts/install-autocommit.ps1
scripts/smoke_api.py
scripts/sync.ps1
scripts/verify.py
web/app.css
web/chat-context.js
web/coverage.css
web/coverage.js
web/favicon.ico
web/favicon.svg
web/flows.css
web/flows.js
web/graph.html
web/index.html
web/report-actions.css
web/report-actions.js
web/review-list.js
web/scenarios.css
web/scenarios.js
web/terminal.css
web/timeline.css
web/timeline.js

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

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

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