AAYMA
RU
Войти

Граф денег (Money Graph)

Пайплайн AML-анализа транзакционной сети (2 248 узлов): роли, кластеры и приоритет проверки с объяснимыми правилами, команда LLM-агентов для…

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

О проекте

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

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

Суть

Пайплайн AML-анализа транзакционной сети (2 248 узлов): роли, кластеры и приоритет проверки с объяснимыми правилами, команда LLM-агентов для исследования и веб-интерфейс.

Как устроено

Parquet-данные превращаются в направленный взвешенный граф NetworkX; считаются признаки (PageRank, betweenness, временные признаки, атрибуция), Louvain-кластеризация, роли по правилам с порогами и RuleTrace, второй проход для координаторов, приоритет. LLM-агенты (планировщик, калибровщик порогов с симуляцией, следователь с function calling, критик, редактор с проверкой чисел, ревизор, аналитик) не присваивают роли, а только направляют исследование; есть офлайн-режим с теми же CSV. Интерфейс — статичный фронтенд с vis-network или Gradio/pyvis.

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

  • NetworkX
  • PageRank
  • betweenness centrality
  • Louvain
  • rule-based roles с RuleTrace
  • function calling
  • multi-agent
  • threshold calibration simulation

Особенности

Принцип «агенты исследуют, движок правил решает»: редактор может переписать объяснение только если каждое число выводится из RuleTrace; журнал аудита всех вызовов агентов; прогон ~90 с и ~$0.21; 83 теста.

Стек

Python · pandas · pyarrow · NetworkX · SciPy · PyYAML · Gradio · pyvis · vis-network · pytest

Модели

OpenAI


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

README

README.md

Граф денег (Money Graph)

Восстановление финансовой структуры организованной группы по транзакционной сети. Трек 2 — Финансы. Кейс HackAlem AI.

Языки: Русский · English · Қазақша

🎬 Видео-демонстрация решения

📦 Итоговые файлы для жюри — три обязательные выгрузки и схема решения

./agent_run.sh

Одна команда: установка зависимостей → расчёт → проверка выгрузок → отчёт по времени и стоимости → открытие интерфейса в браузере.


Быстрый старт — выберите один вариант

[!IMPORTANT]

🔑 Нужен ли ключ API?

Нужен ключ?
A — запустить всё с нуля, вместе с ИИ-агентами✅ Да, нужен ключ OpenAI API. Впишите его в .env до запуска.
B — просто посмотреть результаты, полученные на хакатоне❌ Нет. Ни ключа, ни аккаунта, ни интернета, настраивать нечего.
C — пересчитать всё вообще без ИИ❌ Нет. Получаются те же три CSV, те же роли и тот же порядок.

Если ключа нет и вы просто хотите увидеть результат работы инструмента, переходите сразу к варианту B — он работает сразу.

A · Запустить всё с нуля, вместе с ИИ-агентами

Впишите ключ в .env и выполните ровно эти две команды:

cp .env.example .env && echo "OPENAI_KEY=sk-ЗАМЕНИТЕ" >> .env
./agent_run.sh

Скрипт всё установит, запустит полную команду агентов, проверит три обязательные выгрузки, напечатает время и стоимость и откроет интерфейс. Около 90 секунд и примерно $0.21 на текущей модели.

B · Просто посмотреть результаты, полученные на хакатоне

Ключ не нужен, обращений к модели нет, настраивать нечего — открываются закоммиченные результаты как есть:

./agent_run.sh --use-existing

C · Пересчитать всё вообще без ИИ

Те же три CSV, те же роли и тот же порядок — меняются только формулировки:

./agent_run.sh --offline

Какой интерфейс?

По умолчанию — собственный фронтенд. Это статическая страница, которая раздаётся локально, сама открывается в браузере и не требует интернета.

./agent_run.sh                   # собственный фронтенд (по умолчанию)
./agent_run.sh --ui gradio       # Gradio, умеет публиковать общедоступную ссылку
./agent_run.sh --ui none         # без интерфейса, только выгрузки
./agent_run.sh --no-browser      # не открывать браузер

Оба интерфейса читают одни и те же выгруженные файлы, поэтому всегда показывают одинаковые числа. Оба открываются по-русски, переключение на English и қазақша — в один клик.

Сценарий демонстрации: docs/demo_script.md — сценарий живой демонстрации с реальными номерами счетов.

Содержание

  1. Описание решения и его назначения
  2. Описание архитектуры
  3. Используемые технологии
  4. Инструкции по установке
  5. Инструкции по запуску
  6. Необходимые зависимости
  7. Параметры окружения
  8. Порядок проверки основного сценария работы
  9. Критерии ролей
  10. Приоритет проверки
  11. Кластеризация
  12. Ограничения данных и как они учтены
  13. Схемы выгрузок
  14. Воспроизводимость
  15. Формулировки и приватность
  16. Ограничения подхода
  17. Масштабирование до ~1 млн узлов
  18. Структура репозитория

1. Описание решения и его назначения

Задача

Аналитику AML банка второго уровня правоохранительные органы передают список клиентов, получавших деньги от наркоторговли, — нижний уровень цепочки. Кто стоит выше: кто собирает эти деньги, через кого их перемещают и кто в итоге ими управляет — восстанавливается вручную, часами на один узел.

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

На входе — выгрузка исходящих переводов 81 известного клиента на глубину 4 колена (июль 2026, внутрибанковские переводы от 5 000 ₸): 2 248 узлов, 3 119 рёбер, 4 840 транзакций.

На выходе — ответ на один вопрос: кого из 2 248 клиентов проверять первым и почему. Конкретно:

  • каждому узлу присвоена роль из словаря, уверенность в роли (0–1), кластер и приоритет проверки (0–1);
  • к каждой роли приложено обоснование с числами — теми самыми, которые сравнивало правило;
  • три CSV-выгрузки фиксированной схемы;
  • веб-интерфейс, где по номеру счёта за секунды видно роль, сработавшее правило и карту движения денег.

Для кого

Аналитик финансового мониторинга. Сценарий: получить список gid → загрузить выгрузку → открыть список приоритетов → прочитать обоснование по каждой позиции → сформировать перечень клиентов на углублённую проверку и запрос в правоохранительные органы.

Ключевое свойство

Решение не является «чёрным ящиком». Каждая роль — результат формального правила с порогом, и это правило показывается в интерфейсе вместе с числами, которые оно сравнивало. ИИ-агенты участвуют, но не присваивают роли (см. раздел 2).


2. Описание архитектуры

Главный принцип

Агенты ведут исследование. Движок правил выносит решение.

Агенты решают, что изучать, насколько глубоко и какими должны быть пороги. Модуль roles.py определяет, какая роль, сравнивая метрику с порогом, и формирует RuleTrace — структуру с сработавшим правилом, использованными значениями и порогами. Требование ТЗ «роль без объяснимого правила не засчитывается» выполняется по построению.

Конвейер

загрузка → профилирование данных
        → [ПЛАНИРОВЩИК: какие проходы нужны на этих данных]
        → граф → признаки → временные признаки → атрибуция
        → [КАЛИБРОВЩИК: пороги ролей, с симуляцией и сохранением]
        → роли (проход 1) → кластеризация → роли (проход 2: координатор)
        → приоритет → обоснования
        → [СЛЕДОВАТЕЛЬ: досье] → [КРИТИК: аргументы против]
        → [РЕДАКТОР: формулировки] → [РЕВИЗОР: запросы данных]
        → выгрузки

Полная схема: docs/diagram.md.

Агенты и их полномочия

АгентЧто решаетЧто его ограничивает
планировщиккакие проходы запускать на этих данных и насколько глубокожёсткий лимит в config.yaml, который он не может превысить
калибровщиквсе пороги ролей, с письменным обоснованием каждогообязан остаться в диапазоне, выведенном из перцентилей; предложение симулируется на реальных данных и отклоняется, если обнуляет роль или отдаёт одной роли больше половины сети; результат сохраняется для воспроизводимости
следовательчто изучать вокруг каждого приоритетного счёта (многошаговый цикл вызова функций)каждое утверждение обязано исходить из результата функции; упомянутые gid должны существовать; обязан предложить правдоподобное безобидное объяснение
критикчто не так с итоговым списком: артефакты сбора, чувствительность порогов, слепые зоныможет ссылаться только на показанные ему счета; не может ничего понизить — вывод носит рекомендательный характер
редактортолько формулировкикаждое число в переписанной фразе обязано присутствовать в RuleTrace, иначе результат отбрасывается
загрузчиккакая колонка входного файла чему соответствуетответ перепроверяется по самим данным до принятия
ревизоркак сформулировать запросы недостающих данныхсами пробелы вычисляются из графа
аналитиккакой запрос к графу отвечает на вопрос пользователяотвечает только из вывода функций; протокол вызовов показывается рядом с ответом

Ни один из них не присваивает роль, оценку, кластер или место в списке. У каждого есть детерминированный запасной путь: ./agent_run.sh --offline формирует те же три CSV с теми же ролями.

Механизм проверки формулировок

  1. roles.py присваивает роль и формирует RuleTrace.
  2. evidence.py заполняет шаблон из этой структуры — всегда корректно.
  3. Редактор может переписать фразу. Переписанное принимается, только если проходит ограничение длины, список запрещённых слов и проверку чисел: каждое число в тексте обязано выводиться из RuleTrace.
  4. Отклонения подсчитываются и попадают в run_trace.md.

Журнал аудита

output_files/agent_log.md фиксирует план каждого агента, каждый вызов функции с аргументами и результатом, каждое отклонённое решение с причиной и каждый переход на запасной путь. В задачах комплаенса невозможность показать, как получен вывод, сама по себе является проблемой, поэтому журнал — часть результата, а не отладочный файл.


3. Используемые технологии

СлойТехнологииЗачем
ЯзыкPython 3.10+ (проверено на 3.13)рекомендован организаторами
Данныеpandas, pyarrow, numpyчтение parquet/CSV, векторные расчёты
Графыnetworkx 3.7, scipyнаправленный взвешенный граф, PageRank, betweenness, Louvain, компоненты связности
КонфигурацияPyYAMLвсе пороги и веса вне кода
Интерфейссобственный фронтенд (HTML/CSS/чистый JS, vis-network вендорится локально)по умолчанию: без сборки, без фреймворка, без сети
Интерфейс (альт.)gradio 5.50, pyvis 0.3.2второй интерфейс, умеет публиковать общедоступную ссылку
Агентыopenai (совместимый клиент), python-dotenvИИ-слой; опционален во время выполнения
Тестыpytest83 теста: контракт выгрузок, формулировки, агенты, интерфейс

Намеренно не используется библиотека построения графиков. Единственная диаграмма в интерфейсе нарисована на CSS: иначе её отображение зависит от совпадения версий JS-пакета и фронтенда Gradio.

Не требуется: облако, GPU, платные сервисы. Интернет нужен только для внешнего LLM API, и только если он включён.


4. Инструкции по установке

Быстрый путь (рекомендуется)

git clone https://github.com/BAITC-Hacks/hack-e26f0e66-max.git
cd hack-e26f0e66-max
./agent_run.sh

Скрипт сам найдёт Python 3.10+, создаст .venv, установит зависимости из requirements.txt, создаст .env из шаблона и продолжит расчёт. Отдельная установка не нужна.

Ручной путь

python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env               # ключ можно не заполнять

Проверка установки

.venv/bin/python -m pytest -q      # ожидается: 83 passed

Требования к машине: обычный ноутбук. Расчёт занимает менее 2 секунд без ИИ-слоя и около 30–90 секунд с ним, при лимите ТЗ в 5 минут.


5. Инструкции по запуску

Одна команда

./agent_run.sh

Выполняет шесть шагов: окружение → зависимости → проверка модели → расчёт → проверка выгрузок → веб-интерфейс с публичной ссылкой.

Режимы

./agent_run.sh                 # полный запуск с ИИ-слоем (нужен .env)
./agent_run.sh --offline       # без обращений к модели; выгрузки идентичны
./agent_run.sh --use-existing  # пропустить расчёт, открыть интерфейс на прошлом результате
./agent_run.sh --data ./my_export   # свои файлы вместо data/
./agent_run.sh --recalibrate   # перезапустить агента-калибровщика
./agent_run.sh --no-app        # остановиться после проверки выгрузок
./agent_run.sh --no-share      # интерфейс только на localhost
./agent_run.sh --port 7861

Коды возврата: 0 — успех, 1 — сбой расчёта, 2 — выгрузки отсутствуют или не соответствуют схеме, 3 — проблема окружения. Пригодно для CI.

По частям

.venv/bin/python run.py                  # только расчёт: data/ → output_files/
.venv/bin/python run.py --only-profile   # только отчёт по данным
.venv/bin/python app/app.py --share      # только интерфейс
make run | make offline | make existing | make test

Что создаётся в output_files/

ФайлСодержимое
nodes_roles.csvобязательно. Строка на каждый из 2 248 счетов: gid, role, role_score, cluster_id, priority_score, evidence
clusters.csvобязательно. Строка на кластер: размер, число seed, внутренний оборот, топ-gid, гипотеза
top_nodes.csvобязательно. 30 счетов по убыванию приоритета с текстовым обоснованием
node_features.parquetвсе метрики и rule_trace каждого узла — это читает интерфейс
graph_edges.parquetрёбра, чтобы интерфейс никогда не обращался к data/
profile_report.mdпрофиль данных: все заявленные факты проверены, перцентили
agent_log.mdжурнал аудита ИИ-агентов
review_notes.mdаргументы критика против итогового списка
data_requests.mdчего не хватает и что запрашивать дальше
dossiers.jsonдосье следователя по приоритетным счетам
ingest_report.jsonкакой файл чем прочитан и какие колонки как распознаны
extras_report.md / extras.jsonопциональные анализы: возвратные потоки, повторяющиеся маршруты, устойчивость, флаги аномалий
web_data.jsonвсё, что читает собственный фронтенд, на трёх языках
run_trace.md / .jsonвремя по этапам, токены, стоимость

6. Необходимые зависимости

Полный список с закреплёнными версиями — requirements.txt. Набор разрешён установщиком и проверен, а не выписан вручную: gradio ограничивает pandas версиями ниже 3.0, поэтому весь стек стоит на линии pandas 2.x.

pandas==2.3.3          pyarrow==25.0.1     numpy==2.5.3
networkx==3.7          scipy==1.18.1       PyYAML==6.0.3
gradio==5.50.0         pyvis==0.3.2
openai==3.19.0         python-dotenv==1.2.3
pytest==9.1.1

Замечания:

  • scipy обязателен: networkx передаёт ему расчёт взвешенного PageRank.
  • gradio закреплён ниже 6.0: app/app.py написан под API 5.x.
  • openai и python-dotenv нужны только для ИИ-слоя. Без них расчёт и интерфейс работают полностью, агенты переходят на детерминированный путь.

Перед изменением любой версии следует перепроверить набор:

pip install --dry-run -r requirements.txt

7. Параметры окружения

Файл .env

Создаётся автоматически из .env.example и не попадает в репозиторий. Поддерживаются три варианта.

1. OpenAI

OPENAI_KEY=sk-...

2. Свой или сторонний OpenAI-совместимый сервер — vLLM, Ollama, llama.cpp, TGI, LM Studio, OpenRouter, Together, внутренний шлюз:

MONEYGRAPH_BASE_URL=http://localhost:8000/v1
MONEYGRAPH_MODEL=Qwen/Qwen3-32B-Instruct
MONEYGRAPH_API_KEY=local        # многие локальные серверы ключ игнорируют

Дополнительно в config.yaml: llm.api: chat и пустой llm.reasoning_effort, если сервер не принимает этот параметр. Тарифы модели стоит добавить в llm.pricing — для локальной модели поставить 0, и отчёт честно покажет $0.00 вместо «не тарифицировано».

3. Без модели вообще

MONEYGRAPH_NO_LLM=1

Либо ./agent_run.sh --offline. Требование ТЗ «воспроизведение без платных сервисов» выполняется.

Переменные окружения

ПеременнаяНазначениеПо умолчанию
OPENAI_KEY / OPENAI_API_KEY / MONEYGRAPH_API_KEYключ API; берётся первый непустой—
MONEYGRAPH_BASE_URLадрес OpenAI-совместимого сервераAPI OpenAI
MONEYGRAPH_MODELимя модели, переопределяет config.yamlgpt-6-sol
MONEYGRAPH_NO_LLM1 — полностью детерминированный запускне задана
MONEYGRAPH_SHARE1 — публичная ссылка интерфейса0
MONEYGRAPH_HOST / MONEYGRAPH_PORTадрес и порт интерфейса127.0.0.1:7860

Файл config.yaml

Все пороги, веса, лимиты и тарифы, каждый с комментарием о происхождении. Ключевые разделы: roles (пороги ролей), priority (веса приоритета), clustering, llm (модель, лимиты, тарифы), agents (включение каждого агента), tracing (лимит времени), viewer, input (синонимы колонок).

Текущая модель: gpt-6-sol, reasoning_effort: low. Полный запуск с ИИ-слоем — около 53 вызовов и 76 000 токенов, примерно $0.21. Лимит llm.budget.max_usd действует на один запуск и равен $2.00, то есть запас десятикратный. Тарифы gpt-6-luna и gpt-6-astra также прописаны — смена модели занимает одну строку.

Файл config.calibrated.yaml

Пороги, выбранные агентом-калибровщиком, с обоснованием каждого. Файл коммитится намеренно: благодаря ему запуск на машине проверяющего воспроизводит ровно те числа, что были выбраны здесь. Перезапуск агента — ./agent_run.sh --recalibrate.


8. Порядок проверки основного сценария работы

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

Шаг 1. Развернуть и запустить

git clone https://github.com/BAITC-Hacks/hack-e26f0e66-max.git
cd hack-e26f0e66-max
./agent_run.sh --offline --no-app

Режим --offline выбран намеренно: он не требует ключа, ничего не стоит и проверяет именно детерминированное ядро.

Ожидается:

[1/6] Python environment      ✓
[2/6] Dependencies            ✓
[3/6] Agent layer             ! offline mode
[4/6] Pipeline                ✓ pipeline finished in 2s
[5/6] Deliverables
  nodes_roles.csv   2248  ok
  clusters.csv        88  ok
  top_nodes.csv       30  ok
  ✓ all three required CSVs are present and valid in output_files/

Шаг 2. Проверить обязательные выгрузки

wc -l output_files/nodes_roles.csv          # 2249 (2248 строк + заголовок)
head -2 output_files/nodes_roles.csv        # gid,role,role_score,cluster_id,priority_score,evidence,...
head -4 output_files/top_nodes.csv          # rank,gid,role,priority_score,why

Схема проверяется автоматически на шаге 5 скрипта: наличие файлов, порядок обязательных колонок, отсутствие пустых значений, уникальность gid, не менее 20 строк в top_nodes.csv.

Шаг 3. Проверить объяснимость (must-have 3 из ТЗ)

Сценарий «жюри называет три произвольных gid — команда за минуту объясняет роль». Из терминала:

.venv/bin/python - <<'PY'
import json, pandas as pd
f = pd.read_parquet("output_files/node_features.parquet").set_index("gid", drop=False)
for gid in pd.read_csv("output_files/top_nodes.csv").gid[:3]:
    tr = json.loads(f.at[gid, "rule_trace"])
    print(f"\ngid {gid}  →  {tr['role']}")
    print(f"  правило: {tr['gate']}")
    print(f"  значения: {tr['metrics']}")
    print(f"  пороги:   {tr['thresholds']}")
PY

Каждый узел возвращает сработавшее правило, сравнённые значения и пороги. То же самое показывается в интерфейсе на вкладке «Карточка счёта».

Шаг 4. Проверить воспроизводимость

.venv/bin/python -m pytest -q -m slow

Тест запускает расчёт дважды и сравнивает хеши CSV — они обязаны совпасть. Второй тест подтверждает, что выгрузки формируются без доступа к модели.

Шаг 5. Открыть интерфейс

./agent_run.sh --use-existing

Интерфейс откроется на http://127.0.0.1:7860 и выдаст публичную ссылку. Стартовый язык — русский; English и қазақша — справа вверху. При выборе қазақша показывается предупреждение о машинном переводе.

Первая вкладка «О решении» объясняет остальные восемь и содержит порядок демонстрации из трёх шагов.

Проверка основного сценария в интерфейсе:

  1. «Кого проверять первым» — список из 30 счетов, отсортированный по приоритету, с колонкой обоснования. Клик по строке раскрывает карточку.
  2. «Карточка счёта» — ввести любой gid (например, из top_nodes.csv). Должны появиться: роль, сработавшее правило с числами, карта связей со стрелками направления денег, таблицы плательщиков и получателей.
  3. «Как принято решение» — пороги, выбранные агентом, с обоснованиями; досье; аргументы критика против результата; полный журнал агентов.
  4. «Время и стоимость» — время по этапам против лимита в 300 секунд.

Шаг 6 (необязательно). Проверить ИИ-слой

cp .env.example .env && $EDITOR .env      # вписать OPENAI_KEY
./agent_run.sh

В выводе видны действия планировщика, калибровщика, следователя (параллельно), критика и ревизора, а также итог по токенам и стоимости.

Шаг 7 (необязательно). Проверить работу на других данных

./agent_run.sh --data /путь/к/своей/выгрузке

Читаются .parquet, .csv, .tsv, .json, .jsonl, .xlsx. Результат разбора колонок виден на вкладке «Ваши данные» и в ingest_report.json.


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

Все пороги — в config.yaml с указанием распределения, из которого они получены. MAX_DEPTH читается из данных, а не задан константой.

РольПравилоПорогОбоснование
consolidator (точка сбора)in_deg >= min_payers5p95 = 3, p99 = 6. Порог 5 отбирает 51 узел (2,3 %) — «необычно много разных плательщиков», а не «больше одного». Допустима на любом колене: сбор виден со стороны входа.
distributor (распределитель)out_deg >= min_recipients и out_deg >= fan_ratio × max(in_deg,1)10, ×3p95 = 5, p99 = 24. Порог 10 даёт 64 узла; множитель снижает до 56 и не даёт записать в распределители узел, который одновременно много собирает.
transit (транзит)обе стороны потока наблюдаемы, pass_ratio ∈ [0.8, 1.2], in_deg < 5, out_deg < 100.8–1.2в полосе 70 узлов (72 с учётом seed, которые исключены). Уверенность повышается, если ≥ 50 % суммы уходит в течение 2 дней.
terminal (конечный получатель)исходящие прослежены, in_sum > 0, out_sum <= max_pass × in_sum0.1никогда не присваивается на MAX_DEPTH. Роль широкая по природе данных, поэтому имеет низкий вес в приоритете.
coordinator (координатор)проходит ворота consolidator и n_key_payers >= 2 и seed_reach >= p902, p90точка сбора, собирающая с других точек сбора. Нужны все три условия (см. ниже). 10 счетов, 0,4 %.
cutoff (обрыв обхода)depth == MAX_DEPTH и ни одно правило не сработало—документированное расширение словаря, включается use_extended_roles. Фиксированная уверенность 0,7.
peripheral (периферия)ничего не сработало—включает 19 изолированных seed. Уверенность выражает уверенность в отсутствии сигнала: 0,9 при ≤ 1 ребре, 0,6 при нескольких.

Приоритет при совпадении: coordinator > consolidator > distributor > transit > terminal > cutoff > peripheral. Остальные сработавшие правила сохраняются в secondary_roles.

Уверенность (role_score): 0.5 + 0.5 × clip((метрика − порог) / (сильный − порог), 0, 1), затем ×0.8 на MAX_DEPTH (исходящие неизвестны) и ×0.8, если сторона входа ненадёжна.

Seed-клиенты никогда не получают transit и terminal: их вход занижен устройством выгрузки, соотношение бессмысленно. Могут быть distributor, consolidator или peripheral.

Почему координатору нужны все три условия

Стартовое правило из ТЗ (n_key_payers >= 2 И seed_reach >= 5) на этих данных присваивало роль 264 счетам — 11,7 % графа, из них 103 имели роль terminal, то есть деньги у них заведомо остаются. Это противоположность управляющему узлу. Две причины:

  1. seed_reach >= 5 отбирает 71 % графа. Диапазон метрики здесь p50 = 7, максимум 15: крупнейшая компонента содержит 46 seed, которые питают почти всё ниже по потоку. Абсолютный порог по метрике, диапазон которой целиком определяется данными, не несёт информации. Теперь это перцентиль (p90), который пересчитывается под любые входные данные.
  2. Получать платежи от двух сборщиков — не то же самое, что быть точкой сбора. Координатор обязан сам проходить ворота consolidator. Одно это условие снижает счёт с 264 до 10, и все десять — точки сбора, что и соответствует определению: собирает с других сборщиков.

Конвейер предупреждает, если coordinator превышает 2 % графа.

Фактическое распределение на данных организаторов

РольСчетов
terminal1 100широкая по природе данных: большинство — обычные листья, у которых просто не было переводов выше порога 5 000 ₸
peripheral540
cutoff444ровно заявленное число узлов 4-го колена без исходящих
transit67
distributor46
consolidator41
coordinator10верхний уровень, 0,4 % графа

Полный расчёт: 1,6 с при лимите 300 с.


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

Взвешенная сумма перцентильных рангов, а не сырых значений: оборот охватывает четыре порядка величины, поэтому ранжирование до взвешивания — это то, что делает веса осмысленными.

КомпонентВесСмысл
вес роли0.30coordinator 1.0 · consolidator 0.9 · distributor 0.6 · transit/terminal 0.5 · cutoff 0.3 · peripheral 0.05
seed_kzt_attributed0.25сколько денег от seed правдоподобно прошло через узел
seed_reach0.20сколько независимых цепочек до него дотягиваются
in_deg0.15сколько разных плательщиков
плотность seed в кластере0.10насколько насыщено окружение

Две документированные поправки:

  • Известные seed × 0.5. Правоохранительным органам эти 81 клиент уже известны; ценность — в том, кто над ними, поэтому статус seed понижает приоритет.
  • Сборщики на границе обхода × 1.1 (с ограничением 1.0). consolidator или coordinator на MAX_DEPTH — наиболее вероятное место продолжения цепочки и лучший кандидат на запрос данных 5-го колена.

top_nodes.csv содержит 30 счетов. Колонка why называет компоненты, которые действительно определили место, — они берутся из сохранённых вкладов, а не угадываются.


11. Кластеризация

Louvain (seed=42, разрешение из конфигурации) на неориентированной проекции с весами log1p(sum_kzt). Отбрасывание направления — реальная уступка, и она заявлена открыто: выделение сообществ требует неориентированного графа, а все роли, метрики и стрелки, показываемые аналитику, используют направленный.

Мелкие компоненты никогда не поглощаются крупными кластерами: всё, что меньше min_cluster_size, становится отдельным кластером, каждый изолированный seed — одиночным. Нумерация по убыванию размера с разрешением ничьих по наименьшему gid, поэтому она стабильна между запусками.

Устойчивость измеряется, а не предполагается: Louvain перезапускается с 5 разными сидами, и по каждому кластеру считается доля пар узлов, остающихся вместе. На данных организаторов — среднее 0,95, 78 кластеров с устойчивостью ≥ 0,80.


11a. Опциональные анализы (ТЗ §8)

Все четыре реализованы, детерминированы и занимают около 0,1 с. Ни один из них не меняет роль, оценку или место в списке — это флаги и контекст рядом со счётом, ровно как требует ТЗ.

АнализЧто даёт
Артефакт обрыва обходав ядре: роль cutoff, отсутствие terminal на MAX_DEPTH, повышение приоритета сборщиков на границе
Временные паттерныmedian_lag_days, fast_pass_share (по сумме, а не по количеству), max_payers_same_day для синхронного сбора
Возвратные потоки и маршрутынаправленные циклы в пределах длины и повторяющиеся цепочки A→B→C с передачей в течение трёх дней. Перебор ограничен, и отчёт пишет «не менее N», а не выдаёт за итог число, которое не считал
Устойчивость сетиудаление топ-5/10/20 по приоритету с измерением крупнейшей компоненты, числа компонент и дальности прослеживания денег — против базовой линии случайного удаления, потому что «сеть распадается» без неё ничего не значит
Флаги аномалийсуммы чуть выше порога отчётности (как выглядит дробление с видимой стороны), круглые суммы и счета, экстремальные для своего колена

Текущий запуск: 57 счетов с флагами, ≥200 циклов, ≥50 повторяющихся маршрутов; удаление топ-20 сокращает крупнейшую компоненту на 3,4 % против 1,7 % при случайном удалении — вдвое больший ущерб.


12. Ограничения данных и как они учтены

Заявленное ограничениеКак учтено
Обрыв на 4-м колене (444 узла без исходящих)outflow_observed = depth < MAX_DEPTH. Эти узлы никогда не получают terminal — это и есть решение проблемы 444 ложных «стоков». Им присваивается роль cutoff, уверенность умножается на 0,8, а если узел ещё и собирает — его приоритет повышается: точка сбора на границе видимых данных лучший кандидат на запрос 5-го колена.
Только исходящие переводыpass_ratio считается только там, где обе стороны надёжны, иначе NaN с записанной причиной — никогда не тихий ноль.
Занижен вход у seedseed получают pass_ratio = NaN и не допускаются к ролям transit и terminal. Их сырое отношение доходит до 53 — это артефакт выгрузки, а не поведение.
Порог 5 000 ₸дробление сумм ниже порога невидимо. Зафиксировано как пробел и превращено в конкретный запрос в data_requests.md.
19 seed отсутствуют в рёбрах, 12 только получаютвсе присутствуют как узлы, роль peripheral, обоснование прямо это указывает. Никогда не отбрасываются: в выгрузке обязана быть строка на каждого участника.
16 компонент + 19 изолированных seedкомпоненты не сливаются. Всё, что меньше min_cluster_size, — отдельный кластер, каждый изолированный seed — одиночный.
Нет атрибутов клиентовиспользуются только структура, суммы и даты. Правила формулировок запрещают выдумывать что-либо ещё.
Нет разметки ролейничего не сверяется с эталоном, ни один вывод не претендует на достоверность. Пороги обоснованы перцентилями, а не подгонкой.

13. Схемы выгрузок

nodes_roles.csv — строка на счёт, по возрастанию gid.

КолонкаТипСмысл
gidint64идентификатор клиента
rolestrодна из семи ролей
role_scorefloatуверенность в роли, 0–1
cluster_idint64номер кластера
priority_scorefloatприоритет проверки, 0–1
evidencestrобоснование с числами, ≤ 200 символов

Далее идут контекстные колонки (depth, is_seed, степени, обороты, pass_ratio, seed_reach, seed_kzt_attributed, secondary_roles, два флага наблюдаемости). Лишние колонки разрешены ТЗ; шесть обязательных идут первыми в заданном порядке.

clusters.csv — cluster_id, n_nodes, n_seed, sum_kzt_internal, top_gids, hypothesis. sum_kzt_internal суммирует рёбра с обоими концами внутри кластера; top_gids — пять счетов с наибольшим приоритетом через ;.

top_nodes.csv — rank, gid, role, priority_score, why, 30 строк, ранги 1..N.

Выгрузки формируются на английском языке: их схему жюри проверяет механически, а список запрещённых слов определён по-английски. Интерфейс переводит обоснования на лету, пересобирая их из rule_trace.


14. Воспроизводимость

  • Пороги и веса — в config.yaml, каждый с указанием распределения. В коде магических чисел нет.
  • Ни один gid не зашит в код. Тест ищет в src/ и app/ литералы из 5+ цифр и падает на всём, что не является объявленным значением конфигурации.
  • Один сид (seed: 42) управляет Louvain, выборкой для оценки устойчивости и раскладкой карт. Тест запускает расчёт дважды и сравнивает хеши файлов.
  • Пороги, выбранные агентом, сохраняются, а не пересэмплируются. config.calibrated.yaml коммитится, поэтому запуск на машине проверяющего использует ровно те числа, которые выбрал калибровщик здесь. --recalibrate — явное согласие их изменить.
  • Версии закреплены в requirements.txt. Облако, GPU и платные сервисы не требуются.
  • Заявленные факты проверяются, а не принимаются на веру: раздел 1 profile_report.md сверяет каждое число из ТЗ с файлами и громко сообщает о расхождениях.

15. Формулировки и приватность

Каждая порождаемая строка — evidence, why, hypothesis, текст запросов данных, любой ответ агента — сформулирована как гипотеза для проверки («признаки сбора», «характерно для транзита», «кандидат на проверку»), а не как утверждение о человеке. Слова criminal, guilty, launderer, organizer is, confirmed внесены в чёрный список в config.yaml, проверяются в валидаторе каждого агента, ещё раз перед записью выгрузок и в тестах.

Данные анонимизированы: gid — синтетический идентификатор. Имена, возраст, пол, доход и организации не используются, а промпты агентов прямо запрещают их выдумывать.


16. Ограничения подхода

  • Атрибуция денег от seed — эвристика. Деньги обезличены: после смешения двух входящих потоков ни одна выгрузка не скажет, какой тенге куда ушёл. seed_kzt_attributed — оценка подверженности сверху, а не бухгалтерский факт.
  • Пороги обоснованы, но не оптимальны. Они выведены из наблюдаемых перцентилей и намеренно круглые. Эталона нет, подгонять не к чему, а подгонка до десятых только выглядела бы точнее.
  • Роль terminal широкая. Ей соответствует половина графа, потому что у большинства листьев просто не было исходящих выше порога. Формально верно и почти неинформативно — отсюда низкий вес в приоритете.
  • Направление отбрасывается при кластеризации. Заявлено выше; влияет только на группировку.
  • Один месяц, один банк. Ничто здесь не отличает устойчивую структуру от совпадения в пределах одного месяца.
  • Проверки не было. При отсутствии разметки любой вывод — средство расстановки приоритетов для аналитика, а не установленный факт.

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

В порядке того, что упрётся первым:

  1. Betweenness убирается первым. Точный расчёт — O(VE), и это уже самый дорогой этап. На 1 млн узлов: приближение по выборке (k≈1000 опорных точек) либо отказ — он не участвует ни в одном правиле, только в контексте.
  2. Графовая библиотека. networkx хранит Python-объект на узел. Переход на python-igraph или graph-tool (ядра на C, ускорение в 10–100 раз) не затрагивает логику правил, которая читает только скаляры по узлу.
  3. Louvain → Leiden. Leiden гарантирует связность сообществ и быстрее на больших графах; есть в igraph. Оценка устойчивости перешла бы на выборку компонент вместо полного перезапуска.
  4. Атрибуция становится разреженной линейной алгеброй. Текущее распространение по раундам — это произведение разреженной матрицы на вектор: scipy.sparse с MAX_DEPTH+1 умножениями либо один spsolve для (I − αA). seed_reach превращается в произведение битовых множеств по компонентам.
  5. Ввод-вывод. DuckDB или Polars поверх parquet с проталкиванием предикатов, чтобы загрузка никогда не материализовала весь кадр.
  6. Параллелизм по компонентам. Граф уже несвязен — здесь 16 компонент. Признаки, роли и кластеризация локальны для компоненты.
  7. Пороги уже выведены из перцентилей, поэтому пересчитываются автоматически. Именно поэтому в отчёте они записаны как перцентили, а не как подогнанные константы.
  8. Интерфейс перестаёт загружать граф целиком. Предрасчитанный бэкенд — Neo4j или индекс смежности в parquet — отвечающий на запросы окрестности по требованию. Контракт интерфейса уже «читать только выгруженное», поэтому меняется только слой хранения.
  9. Слой агентов почти не меняется. Он уже ограничен не размером графа, а числом изучаемых узлов: короткий список плюс лидеры кластеров.

18. Структура репозитория

.
├── agent_run.sh                 # ОДНА команда: установка → расчёт → проверка → интерфейс
├── run.py                       # точка входа расчёта
├── config.yaml                  # пороги, веса, агенты, лимиты, тарифы
├── config.calibrated.yaml       # пороги, выбранные агентом (генерируется, КОММИТИТСЯ)
├── requirements.txt             # закреплённые версии
├── .env.example                 # три варианта настройки модели
├── Makefile                     # make run | offline | existing | test
├── task_guidelines.md           # рабочее техзадание
├── final_deliverables/          # итоговые файлы для жюри (генерируется)
├── data/                        # входные файлы организаторов        (только чтение)
├── starter/                     # стартовый код организаторов        (только чтение)
├── src/moneygraph/
│   ├── schema.py                # поиск файлов и распознавание колонок
│   ├── io.py                    # канонический Dataset и происхождение данных
│   ├── profile.py               # отчёт по данным
│   ├── graph.py                 # DiGraph по всем gid
│   ├── features.py              # степени, обороты, центральности
│   ├── temporal.py              # задержка, скорость транзита, синхронный сбор
│   ├── attribution.py           # seed_reach, seed_kzt_attributed
│   ├── roles.py                 # движок правил и RuleTrace
│   ├── clustering.py            # Louvain и оценка устойчивости
│   ├── priority.py              # взвешенная сумма перцентилей
│   ├── evidence.py              # шаблоны и проверка формулировок
│   ├── i18n.py                  # три языка интерфейса и обоснований
│   ├── extras.py                # опциональные анализы (ТЗ §8)
│   ├── webexport.py             # собирает web_data.json для фронтенда
│   ├── export.py                # выгрузки
│   ├── trace.py                 # время, токены, стоимость
│   ├── pipeline.py              # только оркестрация
│   └── agents/
│       ├── base.py              # каркас агента: план → действие → проверка → запасной путь
│       ├── orchestrator.py      # команда агентов и agent_log.md
│       ├── llm.py               # клиент провайдера с трассировкой и лимитами
│       ├── tools.py             # детерминированные функции по графу
│       ├── calibrator_agent.py  # выбирает пороги ролей
│       ├── investigator_agent.py# многошаговые досье
│       ├── critic_agent.py      # аргументы против списка
│       ├── ingest_agent.py      # незнакомые схемы данных
│       ├── narrator_agent.py    # RuleTrace → текст, с проверкой
│       ├── analyst_agent.py     # вопросы на естественном языке
│       └── review_agent.py      # запросы недостающих данных
├── web/                         # собственный фронтенд — интерфейс ПО УМОЛЧАНИЮ
│   ├── index.html · styles.css · app.js · serve.py
├── app/app.py                   # интерфейс Gradio (en / ru / kk)
├── docs/
│   ├── diagram.md               # схема решения (Mermaid)
│   ├── demo_script.md           # сценарий живой демонстрации
│   └── case_brief.docx          # описание кейса от организаторов
├── output_files/                # результаты расчёта
└── tests/test_outputs.py        # 83 теста
Звёзды
⭐ 1
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
8
Авторы
1
Файлы
83
Ветки
1

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

Akylbek Maxutov 8

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

  • 2026-09-23 17:41upload: finalAkylbek Maxutov
  • 2026-09-23 17:38upload: finalAkylbek Maxutov
  • 2026-09-23 17:23upload: fixesAkylbek Maxutov
  • 2026-09-23 16:58upload: fixesAkylbek Maxutov
  • 2026-09-23 16:31upload: fixesAkylbek Maxutov
  • 2026-09-23 15:45upload: visualizationAkylbek Maxutov
  • 2026-09-23 14:43upload: core_functionalityAkylbek Maxutov
  • 2026-09-23 13:56upload: starter and initial filesAkylbek Maxutov
Структура репозитория (83 файлов)
.env.example
.gitignore
CLAUDE.md
Makefile
README.en.md
README.kk.md
README.md
agent_run.sh
app/app.py
config.calibrated.yaml
config.yaml
data/README.md
data/edges.parquet
data/nodes.parquet
data/transactions.parquet
docs/case_brief.docx
docs/demo_script.md
docs/diagram.md
final_deliverables/README.md
final_deliverables/clusters.csv
final_deliverables/diagram.md
final_deliverables/nodes_roles.csv
final_deliverables/top_nodes.csv
output_files/agent_log.md
output_files/clusters.csv
output_files/data_requests.md
output_files/dossiers.json
output_files/extras.json
output_files/extras_report.md
output_files/graph_edges.parquet
output_files/ingest_report.json
output_files/node_features.parquet
output_files/nodes_roles.csv
output_files/profile_report.md
output_files/review_notes.md
output_files/run_trace.json
output_files/run_trace.md
output_files/top_nodes.csv
output_files/web_data.json
pytest.ini
requirements.txt
run.py
src/moneygraph/__init__.py
src/moneygraph/agents/__init__.py
src/moneygraph/agents/analyst_agent.py
src/moneygraph/agents/base.py
src/moneygraph/agents/calibrator_agent.py
src/moneygraph/agents/critic_agent.py
src/moneygraph/agents/ingest_agent.py
src/moneygraph/agents/investigator_agent.py
src/moneygraph/agents/llm.py
src/moneygraph/agents/narrator_agent.py
src/moneygraph/agents/orchestrator.py
src/moneygraph/agents/review_agent.py
src/moneygraph/agents/tools.py
src/moneygraph/attribution.py
src/moneygraph/clustering.py
src/moneygraph/deliverables.py
src/moneygraph/evidence.py
src/moneygraph/export.py
src/moneygraph/extras.py
src/moneygraph/features.py
src/moneygraph/graph.py
src/moneygraph/i18n.py
src/moneygraph/io.py
src/moneygraph/pipeline.py
src/moneygraph/priority.py
src/moneygraph/profile.py
src/moneygraph/roles.py
src/moneygraph/schema.py
src/moneygraph/temporal.py
src/moneygraph/trace.py
src/moneygraph/webexport.py
starter/README.md
starter/requirements.txt
starter/starter.py
task_guidelines.md
tests/conftest.py
tests/test_outputs.py
web/app.js
web/index.html
web/serve.py
web/styles.css

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

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

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