О проекте
Команда: 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, hypothesis | roles — число участников по каждой роли |
out/top_nodes.csv — 50 строк | rank, gid, role, priority_score, why | cluster_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=0 | peripheral | 0,9 | «seed; нет переводов ≥5 000 ₸ внутри выгрузки» |
(I≥5 и O≥10) или (S≥3 и O≥3) | coordinator | min(1; 0,5+0,03I+0,005O+0,05S+0,1C) | «собирает от 5 плательщ. (984 635 ₸), раздаёт 99 получ.» |
O≥10 | distributor | min(1; 0,5+O/100) | «46 переводов на 34 разных получателей, 3 002 628 ₸» |
I≥3 | consolidator | min(1; 0,45+0,05I+0,1 при ≥3 разных плательщиках за день) | «получает от 8 разных плательщиков (15 переводов, 2 160 500 ₸)» |
depth=4, O=0 | peripheral, truncated=True | 0,3 | «4-е колено: получил 2 226 000 ₸ от 1 плательщ.; исходящие не выгружались» |
I>0, O=0 и (A≥100 000 ₸ или I≥2) | terminal | min(1; 0,6+A/5 000 000) | «получил 569 251 ₸ от 2 плательщ. (2 переводов), дальше ничего» |
I>0, O=0, порог выше не достигнут | peripheral | 0,7 | «получил 70 020 ₸ (1 перев.) и дальше не переводил» |
I>0, O>0, p<0,2, не seed | terminal | 0,55 | «дальше ушло лишь 51 800 ₸ (16%, 2 получ.)» |
O>0, seed | transit | 0,45 | «seed: отправил 1 505 966 ₸ 4 получ.; полнота входящих неизвестна — роль по исходящим» |
O>0, 0,5≤p≤2 | transit | min(1; 0,55+0,4F−0,2abs(1−p)) | «отдал дальше 81% полученного (3 получ.); 85% исходящих в дату входа или +1–2 дня» |
O>0, p>2 | transit | 0,35 | «отдал 1 668 800 ₸ при видимых входящих 192 930 ₸» |
| Остальные случаи | peripheral | 0,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. Ловушки данных
- Обрыв четвёртого колена. Из него исходящие не выгружались. У 444 узлов
truncated=True: 441 получилperipheralс явной оговоркой, а 3 —consolidator, потому что правилоI≥3стоит выше правила обрыва. Их дальнейшее движение неизвестно; по нулевым исходящим нельзя делать вывод об удержании средств. У узлов колен 0–3 исходящие вошли в выгрузку, поэтому отсутствие исходящих поддерживает гипотезуterminalв пределах банка, месяца и порога. У трёхconsolidatorчетвёртого коленаevidenceпрямо указывает: «исходящие не выгружались». - Неполный вход у seed. Поиск начинается от 81 известных клиентов;
поступления извне графа не видны. Отношение
B/Aдля них не используем. У остальных узлов входящие также могут быть неполными; неизвестен и остаток до начала июля.B/A>1не доказывает «приумножение» и не позволяет выбрать единственную причину превышения. ПриB>A(включая нулевой видимый вход) карточка предлагает запросить полную выписку и остаток на 01.07.2026; для seed — всегда. Для проверки баланса нужны полная выписка и остаток на начало периода. - Сумма и число переводов различаются. Входящие/исходящие суммы
и числа контрагентов влияют на роль и приоритет;
evidenceдополнительно показывает число отдельных переводов. Равный оборот может скрывать разную структуру операций. - Направление. Степени, роль, пути и 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, 100000000331309100 | coordinator: 5 разных плательщиков, вход 984 635 ₸; 99 получателей, выход 23 001 375 ₸; 3 seed в двух шагах выше. Проходит первую составную проверку I≥5, O≥10. role_score=1, priority_score=1. Член крупнейшей сильно связной компоненты; через него проходит 32 найденных коротких цикла. |
11, 100000003115284100 | consolidator: 8 плательщиков, 15 входящих переводов на 2 160 500 ₸, 2 получателя и выход 517 000 ₸; I≥3, предыдущие проверки не подходят. За один день было до 7 разных плательщиков. role_score=0,95, priority_score=0,8715. |
16, 100000005933757100 | distributor: 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 отвечает по инструментам графа; роли назначает детерминированный код.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.