AAYMA
RU
Войти

MoneyGraph Investigator

Локальный инструмент AML-аналитика: объяснимые роли и приоритеты для всех 2248 узлов сети переводов, кластеры Louvain, топ-50, анализ паттернов…

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

О проекте

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

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

Суть

Локальный инструмент AML-аналитика: объяснимые роли и приоритеты для всех 2248 узлов сети переводов, кластеры Louvain, топ-50, анализ паттернов, устойчивость сети и AI-критик гипотез с инструментами.

Как устроено

starter.py читает parquet и детерминированно считает признаки, роли по документированным формулам, priority_score и сообщества Louvain, затем пишет три CSV. Отдельно считаются наблюдаемость (обрыв обхода на depth=4), временные паттерны (лаг +1/+2 дня), маршруты и циклы длиной 3–4, пиковые аномалии, симуляция удаления TOP-N и доминаторные зависимости. Веб-интерфейс на vanilla JS показывает карточку узла с пятью вкладками и методику с численными вкладами. AI-аналитик на OpenAI вызывает инструменты по графу, проверяет альтернативные гипотезы и ищет общий downstream для 1–5 GID; без ключа доступен офлайн-архив реальных ответов.

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

  • Louvain
  • rule-based role scoring
  • dominator dependency
  • temporal pattern matching
  • cycle detection
  • top-N removal resilience
  • function calling
  • observability/next-query suggestion

Метрики

2248 узлов, 91 сообщество, топ-50; core 0.497 с, patterns 0.252 с; 99 Python-тестов + 25 JS-проверок

Особенности

Важность узла отделена от достаточности данных: нулевой выход на 4-м шаге не превращается в «терминал», вместо этого предлагается следующий запрос данных. AI выступает критиком детерминированных результатов, а не считает граф сам. Воспроизводимость подтверждена SHA-256 хэшами CSV по фазам работы.

Стек

Python · JavaScript · HTML/CSS · parquet

Модели

OpenAI


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

README

README.md

MoneyGraph Investigator — UNRU AI

Локальный анализ обезличенной сети переводов: кого проверить первым, какие наблюдаемые признаки это объясняют и где заканчиваются данные. Роли — проверяемые структурные гипотезы, а не утверждения о виновности. role_score — нормализованная сила признаков, не вероятность: размеченных ролей нет.

Финальное состояние (функции заморожены): локальный pipeline, три CSV, наблюдаемость и очереди, UI с поиском любого GID, объяснимые гипотезы, AI-критик с поиском общего downstream для 1–5 источников, offline-архив реальных AI-ответов, Stability Lite, TOP-N removal и Pattern Analysis (время, маршруты, peer-аномалии). Основной продукт и архив работают без API-ключа и интернета. Ключ нужен только для нового ответа LLM. Формулы ролей/приоритета и три CSV побайтно совпадают с PHASE 1 (09d2491).

Быстрый запуск

Из корня репозитория, Python 3.12:

python3.12 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python starter.py

Откройте http://127.0.0.1:8010: «Граф» → поиск GID → карточка узла. Для проверки подойдёт 100000003115284100; доступны также «Методика расчёта» и «Архив ИИ». Данные включены в репозиторий; ключ не нужен для локальных функций и архива. Новый ответ AI требует своего ключа OpenAI.

Соответствие ТЗ · Подробная установка · Сценарий 5-минутной демонстрации · Подготовка сдачи.

Статус проверок

Последний коммит кода/UI — 0f4316e: 99 Python-тестов + 25 JS-проверок прошли. Последующие коммиты уточняют только README. Проверен синтаксис модуля пояснений ролей; последние изменения интерфейса проверены в браузере. API-ключ в репозиторий не включён.

Последняя проверка установки с нуля — на версии 224c430: свежий clone, чистый venv, установка только по README, запуск без .env, поиск трёх случайных GID, интерфейс и offline AI archive. Все три CSV совпали по SHA-256. Отчёт чистого запуска. После этой проверки менялись интерфейс и документация. Каталог moneygraph/, requirements.txt и starter.py не изменились относительно 224c430 (проверено по Git). Полный чистый clone финального коммита повторно не проверялся.

Предыдущие проверки воспроизводимости

PHASE 8 проверен в чистой копии 6e5da22: 92 Python tests + 7 JS checks; core 0.497 с, patterns 0.252 с. Новая проверка. Зависимости прежние.

Предыдущая чистая проверка 336b6e3 (до PHASE 8): 85 Python-тестов + 3 JS renderer checks; три CSV совпадают по SHA-256. Первый расчёт после установки — 5.10 с, повторный — 0.47 с. UI, три случайных GID и offline-архив проверены без ключа. Отчёт, установленные версии.

Соответствие требованиям ТЗ

Сопоставление с разделами 7–8 ТЗ кейса: Обязательные требования: 5/5 реализованы. Опциональные возможности: 8/8 реализованы. Это описание реализованной функциональности, а не заявление о полученных баллах. Окончательное засчитывание требований и оценка остаются за жюри. Пути интерфейса ниже доступны после запуска python starter.py по адресу http://127.0.0.1:8010.

Обязательные требования — 5/5 реализованы

№Требование ТЗЧто реализованоГде проверить
1Воспроизводимый пайплайнОдин запуск читает исходные parquet, рассчитывает признаки и создаёт все три CSV; ручная подготовка и API-ключ не нужны. Пересчёт укладывается в лимит 5 минут на предоставленных данных.python starter.py (с UI) или python starter.py --no-ui; starter.py, pipeline.py; отчёт чистого запуска.
2Роль, скор и обоснование у каждого узлаВсе 2248 узлов, включая изолированные, получают роль из словаря, role_score, cluster_id, priority_score и непустой evidence. Сила признаков не трактуется как вероятность виновности.nodes_roles.csv; «Граф» → поиск ID → «Обзор»; roles.py.
3Объяснимые, задокументированные критерии ролейДля шести ролей приведены условия, пороги и формулы. Для выбранного GID показываются реальные признаки и численные вклады в оценку; формулы извлекаются из действующего кода.Карточка → «Показать расчёт этой роли»; /methodology → «Определение ролей»; roles.py, methodology.py.
4Кластеризация сети91 структурное сообщество Louvain; у каждого узла есть кластер, для каждого кластера — размер, число исходных узлов, внутренний оборот, приоритетные узлы и гипотеза.clusters.csv; под графом → «Структурное сообщество выбранного узла», кнопка «Кластер»; communities.py.
5Топ-лист ≥20 узлов и визуализацияТоп-50 с обоснованиями; очереди проверки, поиск произвольного GID, направленная локальная окрестность, цвета ролей, подсветка кластера и точные суммы рёбер.top_nodes.csv; «Граф» → поиск ID, очередь, «Связи выбранного узла»; server.py, app.js.

Опциональные возможности — 8/8 реализованы

№Возможность ТЗЧто реализованоГде проверить
1Учёт обрыва на четвёртом коленеНулевой выход на depth=4 не создаёт гипотезу конечного получателя. Исходящие признаки отмечены как обрезанные выборкой; предлагается запрос продолжения обхода.ID 100000003037476100 → «Данные»; /methodology#observability; roles.py, observability.py.
2Временные паттерныСовместимость входа и выхода с лагом +1/+2 дня без повторного использования суммы; сбор от нескольких отправителей за день и последующее распределение; всплески активности. Учтены границы месяца и неизвестный порядок внутри дня. Это совместимость потоков, не доказательство движения тех же денег.Карточка → «Паттерны» → «Временные»; /methodology#temporal; patterns.py.
3Повторяющиеся маршруты и возвратные потокиПовторные цепочки A→B→C, взаимные переводы и направленные циклы длиной 3–4. Показаны конкретные GID и подсветка рёбер, видимых в текущей окрестности. Цикл сам по себе не доказывает возврат тех же средств.Карточка → «Паттерны» → «Маршруты»; /methodology#routes; patterns.py.
4Детекция аномалийПравила отклонений относительно узлов того же колена: число связей, операции, оборот, дневные вееры; распределение одинаковых сумм минимум трём получателям. Операции ниже 5000 ₸ не наблюдаются, поэтому скрытое дробление не заявляется как обнаруженное.Карточка → «Паттерны» → «Отклонения»; /methodology#anomalies; patterns.py.
5Устойчивость сети при изъятии топ-NУдаление TOP-1/3/5/10 в копии графа; сравнение компонент, крупнейшей компоненты и достижимости, отдельно — оставшиеся узлы, потерявшие путь. Это контрфактический анализ наблюдаемого графа.«Граф» → блок «Устойчивость сети» → TOP-N и «Подробные показатели и ограничения»; resilience.py.
6AI-ассистент аналитикаВопросы на естественном языке, вызовы инструментов по графу, объяснение с источниками, проверка альтернатив и общий downstream-поиск для 1–5 GID. Новый ответ требует ключа OpenAI; без ключа доступны локальная аналитика и архив шести реальных предыдущих ответов, явно обозначенный как архив.«Граф» → «ИИ-помощник аналитика»; /ai-archive; analyst.py, downstream.py, archive.py.
7Автогенерация карточки узлаКарточка любого найденного GID: роль и альтернатива, потоки, связи, основания приоритета, структурные признаки и следующий шаг. Пять вкладок разделяют обзор и подробности.«Граф» → поиск ID или нажатие на узел → «Обзор», «Структура», «Паттерны», «Устойчивость», «Данные»; server.py, app.js.
8Оценка полноты и следующий запросДостаточность наблюдений, статусы доступности признаков, ограничения охвата, недостающие данные и конкретный следующий запрос. Это пригодность выгрузки для интерпретации, а не процент полной банковской истории.Карточка → «Данные», «Следующий полезный запрос»; /methodology#observability и /methodology#queues; observability.py, evidence.py.

Проверки реализации: 99 Python-тестов и 25 JS-проверок; отчёт UI и воспроизводимости. Дополнительные механизмы — зависимость путей, альтернативные гипотезы, разнообразие входящих ветвей и Stability Lite — не используются для искусственного увеличения числа пунктов ТЗ.

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

  • Важность узла отделена от достаточности данных: высокий приоритет не скрывает пробелы наблюдений.
  • Гипотеза роли сопровождается альтернативой, если её поддерживают правила, и ограничениями данных.
  • Неизвестные связи не достраиваются; вместо этого предлагается следующий запрос данных.
  • AI объясняет результаты детерминированных инструментов и проверяет альтернативы, а не рассчитывает граф самостоятельно.

Обязательные условия реализации

ТребованиеКак выполнено
ExplainabilityУ каждого узла и каждой позиции топ-листа есть текстовое обоснование. Карточка показывает наблюдаемые признаки, альтернативу и ограничения; «Методика расчёта» раскрывает пороги и численные вклады конкретного GID.
ПриватностьРабота ведётся с обезличенными GID и транзакционными признаками. ФИО, ИИН и другие идентифицирующие сведения не требуются и не восстанавливаются. Локальная аналитика не отправляет данные наружу; при явном запросе live AI выбранные GID и результаты инструментов передаются OpenAI, как описано в разделе AI.
Осторожность формулировокРоли — гипотезы для проверки, не утверждения о виновности. Сила признаков не является вероятностью; отсутствие наблюдаемых переводов не доказывает отсутствие реальных операций.
ПроизводительностьПолный локальный пересчёт на предоставленных данных укладывается в 5 минут; измерения и проверки воспроизводимости приведены выше и в audit/. GPU не требуется.
ИнфраструктураПосле установки зависимостей pipeline, UI, методика и архив работают локально без интернета. Интернет нужен для установки пакетов и, при использовании, нового запроса к внешнему LLM API.
СтекPython 3.12, pandas, NetworkX, FastAPI; статические HTML/CSS/JavaScript. Сборка frontend и Node.js для запуска не нужны. Зависимости зафиксированы в requirements.txt.
МасштабируемостьВ разделе «Архитектура» описан переход к ~1 млн узлов: потоковые агрегации, компактный графовый движок, оптимизация достижимости и ограниченные окрестности в UI. Это план развития, не заявление о проведённом нагрузочном тесте.

Установка и одна команда запуска

Python 3.12, обычный CPU, ориентир 2 GB свободной RAM. Интернет нужен только один раз для установки пакетов. Проверено на macOS arm64 / Python 3.12.14 в отдельном окружении, созданном с нуля; финальная проверка выполнена стандартным pip по командам ниже. Выполнять из корня этого репозитория:

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

Полный пересчёт от исходных parquet до всех CSV одной командой:

.venv/bin/python -m moneygraph.pipeline

Исходные файлы организатора уже включены в audit/source/data/data/. Никакого аккаунта, API-ключа, GPU или ручной подготовки результатов не требуется. Другие входы/каталог результатов:

.venv/bin/python -m moneygraph.pipeline --data /path/to/data --out /path/to/results --top 50

Выход в outputs/:

  • nodes_roles.csv: все узлы, обязательные поля gid,role,role_score,cluster_id,priority_score,evidence и дополнительные рассчитанные признаки/силы альтернативных ролей;
  • clusters.csv: cluster_id,n_nodes,n_seed,sum_kzt_internal,top_gids,hypothesis;
  • top_nodes.csv: rank,gid,role,priority_score,why; по умолчанию 50 узлов, минимум 20 (для входа меньше 20 — все узлы);
  • run_report.json: измеренное время, версии, SHA-256 входов, контрольные суммы и статистика;
  • node_context.json: отдельный слой интерпретации всех 2248 узлов: наблюдаемость, очередь, ограничения, следующий запрос данных и альтернативные роли. gid здесь строка: исходные int64 больше безопасного диапазона JavaScript Number.

evidence содержит не более 200 символов. Все CSV в UTF-8, десятичная точка, разделитель запятая. Валидация выполняется до записи. Ошибка входных данных завершает команду с ненулевым кодом; ранее рассчитанные файлы не считаются новым успешным результатом.

Проверка:

.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/python -m pytest -q

Тесты проверяют реальные схемы и полноту, устойчивость к перестановке строк, малые контрольные графы, границу depth=4, seed, изолированные узлы, изменение результата при изменении входа, точность денег и отказ при повреждении агрегатов.

Локальный интерфейс

После установки requirements.txt запустите из корня репозитория:

.venv/bin/python starter.py

Откройте http://127.0.0.1:8010. Одна команда пересчитывает parquet → CSV/JSON, Stability и Pattern Analysis, затем запускает FastAPI и статические HTML/CSS/JS. Node.js, npm, сборка frontend, интернет и API-ключ не нужны. Остановка: Ctrl+C. При занятом порте: --port 8011. Другие исходники/выход: --data /path/to/data --out /path/to/results.

Главный сценарий:

  1. Выберите очередь: «Проверить», «Данные» или «Наблюдать». Узлы отсортированы по прежнему приоритету, показаны страницы по 25 записей.
  2. Введите полный gid в поиск и нажмите «Найти»/Enter. Если введено начало gid — получите до 20 подходящих вариантов с общим числом совпадений. Неизвестный gid показывает понятное пустое состояние.
  3. Слева очередь, в центре направленные переводы, справа карточка. Карточка раскрывает основную/альтернативную гипотезы, силу признаков, приоритет, наблюдаемость, факты, ограничения и следующий запрос данных.
  4. Цвет узла — гипотеза роли. Цвет стрелки — роль отправителя; её направление соответствует наблюдаемому переводу. Значок ⓘ в легенде раскрывает пояснение роли по наведению или фокусу; нажатие закрепляет подсказку, Escape закрывает её. Кнопка «Кластер N» выделяет участников кластера выбранного узла, остальные приглушаются. Обводка seed и подсказки помогают ориентироваться; полная информация о кластере расположена под графом.
  5. Нажмите соседний узел, чтобы открыть его карточку и окружение. Наведение на ребро показывает полные gid, сумму и число переводов. Раздел «Точные суммы и направления» показывает их таблицей. Кнопки +/−/«По размеру» меняют масштаб; увеличенный граф можно прокручивать.
  6. Скачайте три CSV по ссылкам внизу. Просмотр, поиск, переключение очередей и скачивание ничего не изменяют.

GID всегда string в JSON и JavaScript. CSV по ТЗ сохраняют точный int64. Подписи соседей в графе сокращены только визуально; полные значения доступны в подсказке, таблице и карточке. На больших окрестностях подписи соседей скрыты для читаемости, выбранный gid всегда подписан. Ни Number(gid), ни parseInt(gid) не используются.

Ego graph — выбранный узел и его непосредственные входящие/исходящие соседи (1 hop). Показываются только рёбра, инцидентные выбранному узлу; связи соседей между собой не рисуются. Слева отправители и взаимные связи, справа остальные получатели. Взаимные направления нарисованы раздельно. По умолчанию максимум 160 узлов; при усечении показано предупреждение и число неотображённых соседей, отбор по исходному приоритету. На предоставленных данных все одноколенные окрестности укладываются в лимит. Нет 2 hops и полного overview: основной сценарий не требует загрузки 2248 точек одновременно.

Три демо-случая (это данные для проверки, не зашитая логика UI):

  • 100000003115284100 → INVESTIGATE_NOW, consolidator, HIGH.
  • 100000003684369100 → REQUEST_MORE_DATA, coordinator + alternative distributor, неполный upstream seed.
  • 100000003037476100 → REQUEST_MORE_DATA, LOW, depth=4: запрос продолжения обхода.

Полный сценарий защиты на 5 минут: пересчёт и файлы (30 с) → различие очередей (45 с) → три карточки выше (2 мин) → произвольный gid жюри и его связи (1 мин) → ограничения и README (45 с).

Технические подробности: API, формулы, архитектура, ограничения и история этапов

Read-only API

GET endpointРезультат
/api/summaryЧисла узлов/рёбер/транзакций/кластеров, очереди, наблюдаемость, оборот, время последнего расчёта
/api/queues/{queue}?offset=0&limit=25Страница очереди и total; limit≤200
/api/nodes/{gid}Карточка, рассчитанные метрики, альтернативы, ограничения, следующий запрос, кластер
/api/nodes/{gid}/ego?max_nodes=160Направленная окрестность, string src/dst, суммы, n_tx; max_nodes≤250; отметка усечения
/api/clusters/{cluster_id}Размер, seed, внутренний оборот, гипотеза и приоритетные участники
/api/search?q=...&limit=20Точное совпадение в приоритете, иначе prefix; limit≤100
/api/download/{filename}Только nodes_roles.csv, clusters.csv, top_nodes.csv

API-документация: http://127.0.0.1:8010/docs. Неизвестные gid/кластер/очередь/файл → 404, некорректные лимиты → 422. Сервер по умолчанию доступен только через loopback. Это локальное конкурсное приложение без многопользовательской авторизации и production-развёртывания.

PHASE 4A: дополнительный evidence layer

outputs/structural_evidence.json добавляет данные ко всем карточкам /api/nodes/{gid}. Три CSV и node_context.json PHASE 2 не меняются. Ни dominator impact, ни новые статусы не пересчитывают primary role, role_score, priority_score, Louvain, очереди или сводную наблюдаемость.

Зависимость наблюдаемых путей

К копии направленного графа добавляется искусственный источник с ребром к каждому seed. NetworkX immediate_dominators строит дерево доминаторов для достижимых узлов. Для узла X:

  • dominated_nodes — число других вершин в его поддереве; все наблюдаемые пути от seed к ним проходят через X;
  • dominated_clusters — число разных прежних cluster_id среди этих вершин; кластер самого X добавляется только если в нём есть зависимый потомок;
  • sample_dominated_gids — до 12 примеров (строки); если это не полный список, sample_is_truncated=true;
  • недостижимый от seed узел получает NOT_EVALUABLE и null, не ложный ноль.

Счётчик = размер поддерева с X минус 1 = число строгих потомков. В предоставленном исследовании формула с «descendants − 1» имела ошибку на единицу; псевдокод с «subtree_size − 1» был верным. Это проверяют отдельные тесты на цепочке и листе.

В текущих данных 539 узлов имеют ненулевой impact. Максимум: gid 100000002527114100, 269 узлов в 1 кластере. Другой пример: 100000003016635100, 102 узла в 10 кластерах. Для пяти крупнейших случаев выполнена независимая проверка удаления узла в тестовой копии графа: число оставшихся узлов, утративших достижимость от seed, точно совпало. Это тест алгоритма, не новая продуктовая функция симуляции.

Интерпретация ограничена наблюдаемой сетью. Сбор через BFS и единственный seed в компоненте сами могут создавать доминаторов. Невидимые обходные маршруты могут отменить реальную зависимость; 0 на depth=4 описывает только обрезанную сеть. Метрика не означает контроль денег, распоряжение клиентами или подтверждение coordinator. Денежный объём «под контролем» не рассчитывается. Семантика без весов: речь о существовании направленных путей, а не о суммах.

Доступность разных видов evidence

API хранит evidence_availability (статусы) и evidence_details (status, value, reason). В карточке показана таблица:

ПризнакПравило
fan_inAVAILABLE для наблюдаемого не-seed входа; PARTIAL для seed или отсутствующего входа; число наблюдаемых отправителей сохраняется
fan_outdepth=4 → CENSORED/null; иначе при нулевом выходе PARTIAL/0, при наблюдаемом выходе AVAILABLE
transitdepth=4 → CENSORED/null; seed/изолят → NOT_EVALUABLE/null; остальные PARTIAL, сила правила только если видны обе стороны
terminaldepth=4, seed, изолят → NOT_EVALUABLE/null; остальные PARTIAL/сила прежнего правила: удержание не доказано
seed_reachAVAILABLE — прежняя направленная достижимость, включая себя для seed; это не независимость ветвей
communityAVAILABLE — прежний ID структурного кластера, а не установленной группы
dominatorAVAILABLE для достижимых от seed; иначе NOT_EVALUABLE/null

AVAILABLE означает вычислимость в наблюдаемом графе, а не полноту клиентской истории. Для depth=4 вход, reach и community остаются доступны, а outgoing/transit/terminal показаны как N/A. В старой таблице фактов при этом может оставаться «наблюдаемый выход = 0»: это число записей, а не оценка исходящего поведения. Никакой подмены исходных нулей и прежних scores нет.

Из идей исследования не реализованы disjoint paths и изменение сортировки запросов. Effective last-hop branches, AI Hypothesis Critic, Stability Lite и temporal compatibility добавлены в последующих фазах и описаны ниже. Предложенные новые формулы ролей и нормализации не перенесены в принятое ядро.

Как защищать роли

Сначала проверяется условие допуска роли, затем вычисляется сила её признаков. Выбирается максимальная сила среди допущенных ролей. При точном равенстве порядок: coordinator → distributor → consolidator → transit → terminal. Если ни одно условие не выполнено: peripheral, role_score=0. Это отсутствие достаточных признаков по данным правилам, а не свидетельство низкого риска. Все альтернативные силы сохраняются в strength_*.

Обозначения: I/O — число разных отправителей/получателей; Ti/To — число входящих/исходящих переводов; Di — число дней входящих переводов; S — число seed, из которых узел достижим направленным путём (для seed учитывается он сам); R — наблюдаемая исходящая сумма / входящая при положительном входе. cap(x)=min(x,1). Pi/Po/Ppr — процентиль положительного входящего/исходящего оборота/PageRank. Mi/Mo — доля крупнейшего отправителя/получателя в соответствующей сумме.

РольУсловие допускаСила признаков (0–1)
consolidatorI≥3; O≤max(2,I/2)0.50·cap(I/8) + 0.30·(1−Mi) + 0.20·Pi
distributorO≥50.55·cap(O/20) + 0.25·(1−Mo) + 0.20·Po
transitне seed; положительные вход и выход; 0.8≤R≤1.20.50·max(0,1−abs(R−1)/0.2) + 0.25·cap(min(Ti,To)/5) + 0.25·cap(min(I,O)/3)
terminalне seed; depth<4; O=0; 1≤I≤2; Ti≥3; Di≥20.40·cap(Ti/10) + 0.35·cap(Di/5) + 0.25·Pi
coordinatorI≥3; O≥3; S≥20.40·cap(min(I,O)/8) + 0.35·cap(S/5) + 0.25·Ppr
peripheralни одна из ролей выше не допущена0

Пороги — прозрачные эвристики стартовой версии; они не обучены и не валидированы на ground truth. Coordinator означает структурно связующий узел, а не установленного организатора. Consolidator отражает схождение связей, не доказанное накопление средств. Terminal — повторный наблюдаемый приём на внутреннем колене без видимого продолжения; удержание средств не доказано. Одного нулевого выхода недостаточно. У seed отношение потоков не участвует в присвоении transit; входы вне выборки неизвестны. У остальных отношение также описывает только видимые потоки. Точное время и происхождение конкретных денег не восстанавливаются.

Приоритет и кластеры

priority_score = 0.30·Pvolume + 0.25·Pneighbors + 0.20·cap(S/5) + 0.15·role_score + 0.10·Ppr.

Pvolume — процентиль суммы вход+выход; Pneighbors — процентиль числа разных соседей независимо от направления. Процентили считаются среди положительных значений, одинаковым значениям присваивается средний ранг, нулевым — 0. Оборот вход+выход может учитывать один проход денег дважды: это активность узла, не новые деньги. PageRank вычисляется по направлению переводов с весом суммы (alpha=0.85). Изолированным узлам назначается приоритет 0: нет транзакционных фактов для ранжирования; их seed-статус и отсутствие данных сохраняются. Это не рекомендация игнорировать такие seed.

Приоритет не умножается на полноту наблюдений; важный узел на границе не теряется автоматически. Очереди и пригодность наблюдений находятся в отдельном JSON и не меняют этот рейтинг. S — достижимость, не число независимых ветвей и не происхождение средств. Топ сортируется по убыванию приоритета, при равенстве — по возрастанию gid. why приводит числа, участвующие в ранжировании.

Louvain: неориентированная проекция, встречные суммы складываются; resolution=1, seed=42. В граф изначально добавлены все gid, включая изоляты. ID кластеров упорядочены по минимальному gid, одиночные узлы сохраняются. Внутренний оборот — сумма оригинальных направленных рёбер внутри кластера, каждое ребро один раз. top_gids — до 5 участников по приоритету; гипотеза описывает только структуру. Фиксированный seed даёт воспроизводимость, не доказательство устойчивости сообществ.

PHASE 2: четыре независимых понятия

ПонятиеЧто означаетЧего не означает
STRUCTURAL PRIORITYСуществующий priority_score: относительный приоритет проверки по структуре, обороту и силе ролиВероятность нарушения; умножение на полноту
ROLE EVIDENCEprimary_strength = прежний role_score; наблюдаемая поддержка выбранного правилаКалиброванная уверенность или ground truth
OBSERVABILITYПригодность текущей ограниченной выгрузки для интерпретации этой гипотезыПроцент известных операций банка/клиента или вероятность
INVESTIGATION QUEUEКакое действие предложено при заданных порогах приоритета, силы роли и наблюдаемостиВердикт о клиенте или команда на блокировку

Детерминированная наблюдаемость

Стартовый индекс 0.90; для каждого обнаруженного ограничения применяется потолок, итог — минимум всех применимых потолков (не сумма штрафов). Все причины сохраняются независимо от того, какая дала минимум. Это открытая эвристика без статистической калибровки. HIGH ≥0.75; MEDIUM ≥0.45 и <0.75; LOW <0.45. Даже HIGH относится лишь к данной гипотезе в данной выборке, не означает полную историю.

Известное ограничениеМаксимальный индекс
depth=4: граница исходящего обхода0.15
Изолированный узел0.05
Seed: неполный upstream0.55
Нет наблюдаемых входящих0.35
Нет исходящих: terminal-гипотеза / остальные роли0.55 / 0.35
depth=3: следующее колено граничное0.65
Входящих+исходящих транзакций меньше 30.35
Меньше 2 дней на соответствующей роли стороне0.55
У не-seed out−in>0.01 KZT: источник разницы не установлен0.55
Transit: месячный коэффициент ещё не доказывает последовательность потоков0.65

Для consolidator/terminal учитываются дни входа, для distributor — выхода; для coordinator/transit — минимум дней входа и выхода; для peripheral — максимум. Сумма дней не используется: это могло бы дважды посчитать один день. Нулевой выход для terminal — часть гипотезы, но удержание без полной истории/остатков остаётся неподтверждённым. Превышение выхода над входом не объявляется ошибкой или нарушением: возможны начальный остаток и входящие вне обхода.

Общие ограничения (один банк, июль 2026, порог 5000 KZT, исходящий обход) записаны у каждого узла. Они не скрываются за HIGH, но и не превращают индекс в одинаково низкий для всех.

Правила очередей

  1. priority_score < 0.75 → MONITOR.
  2. Приоритет ≥0.75, role_score ≥0.50 и observability_score ≥0.60 → INVESTIGATE_NOW.
  3. Остальные с приоритетом ≥0.75 → REQUEST_MORE_DATA.

Порядок проверок фиксирован, equality включается в верхнюю категорию. MEDIUM с индексом 0.65 допускает проверку существующих фактов, с 0.55 — отправляет важный узел в запрос данных. Пороги операционные и пока не валидированы на результатах расследований. Запрос данных не исключает параллельного изучения имеющихся фактов. Низкий индекс не уменьшает исходный приоритет. Изолированные seed имеют priority=0 по неизменённому PHASE 1 и поэтому MONITOR, но LOW, причины и запрос данных явно сохранены; эту очередь нельзя трактовать как разрешение игнорировать seed.

Для всех узлов сохранены observability_reasons, data_gaps (код + описание), next_data_request. Ничего не отправляется автоматически. Первый запрос выбирается по фиксированному порядку известных ограничений: граница → изолят → seed → отсутствие входа/выхода → близость к границе → мало переводов/дней → разница потоков → недостаток временной детализации transit. Остальные варианты — possible_data_requests. Порядок прозрачен, но не является доказанной оптимизацией ценности информации. Если специальных ограничений нет, предлагается соседний период для проверки повторяемости; никаких внешних атрибутов клиента не придумывается.

Альтернативные роли

primary_role/primary_strength копируют текущую роль и силу. alternative_role/alternative_strength — максимальная положительная сила другого уже допущенного правила; равенства разрешаются прежним ROLE_ORDER. Если других допущенных правил нет: null / 0, а не выдуманная альтернатива. Все положительные альтернативы хранятся в role_alternatives. Это конкурирующие структурные интерпретации, не взаимоисключающие вероятности; сумма сил не обязана равняться 1.

Измеренный результат PHASE 2

  • HIGH / MEDIUM / LOW: 117 / 469 / 1662.
  • INVESTIGATE_NOW / REQUEST_MORE_DATA / MONITOR: 49 / 203 / 1996.
  • Один граничный узел имеет priority≥0.75: сохранён в REQUEST_MORE_DATA.
  • Положительная альтернативная роль есть у 71 узла.

В tests/phase1_csv_sha256.json зафиксированы хеши CSV из коммита PHASE 1; тест не допускает скрытого изменения ролей, приоритетов, кластеров и топа при добавлении этого слоя. Подробнее с примерами — audit/PHASE_2.md.

Проверенные данные и расхождения с описанием

2248 узлов, 3119 рёбер, 4840 транзакций, 81 seed; сумма 365 890 012.01 KZT. Суммы и количества по каждой паре согласованы между transactions и edges. Деньги сверяются в целых тиынах. 97 совпадающих строк транзакций сохранены: идентификатора транзакции нет, удаление могло бы потерять реальные платежи.

16 edge-connected components; 35 components when isolated nodes from nodes.parquet are retained. Здесь edge-connected означает слабосвязные компоненты графа из рёбер. Разница — 19 изолированных seed.

out > inБез допускаРазница > 0.01 KZT
Все узлы377377
Seed4242
Не seed335335
Только положительный вход354354
Нулевой вход2323

Число 354 из ТЗ воспроизводится при условии in>0; 23 остальных — seed с нулевым наблюдаемым входом и положительным выходом. Это объяснение на основе данных; организатор не подтверждал, каким способом получал число. Обе агрегации (edges и transactions) дают одинаковый результат. Отчёт генерирует эти сравнения автоматически.

Все 444 узла depth=4 имеют нулевой выход, ни один не получает terminal. Ограничения выгрузки: исходящий обход до четырёх колен, один банк, июль 2026, платежи от 5000 KZT. Наблюдаемые потоки не являются полным балансом. Даты не содержат внутридневного времени. Отсутствующие сведения не достраиваются. Выводы и показатели качества ролей не заявляются как accuracy.

Архитектура

flowchart LR
  A[3 исходных parquet] --> B[Валидация сумм и схем]
  B --> C[Направленный граф всех gid]
  C --> D[Метрики и достижимость seed]
  D --> E[Правила ролей и приоритет]
  C --> F[Louvain: сумма встречных рёбер]
  E --> G[Проверка контрактов]
  F --> G
  G --> H[3 CSV и отчёт]
  H --> I[Read-only FastAPI]
  D --> J[Наблюдаемость и очереди]
  J --> I
  I --> K[Поиск gid / ego graph / карточка]
  C --> L[Dominators и seed convergence]
  L --> I
  C --> M[Stability: отдельные сценарии]
  C --> N[TOP-N removal: копии графа]
  A --> S[Pattern Analysis: дни, маршруты, depth peers]
  S --> I
  M --> I
  N --> I
  I --> O[Read-only tools и downstream BFS]
  O --> P[OpenAI: явный запрос и ключ]
  P --> K
  Q[Реальные AI-ответы и SHA-256] --> R[Offline AI archive без ключа]

Код: moneygraph/io.py — схемы и денежная точность; features.py — граф/метрики; roles.py — правила/рейтинг; communities.py — кластеры; pipeline.py — запуск/экспорт; observability.py — отдельный слой ограничений и очередей. Никаких списков «правильных gid» в реализации нет.

При росте до ~1 млн узлов: parquet-агрегации перенести в DuckDB/Polars с потоковой обработкой; NetworkX заменить компактным графовым движком (igraph/graph-tool или аналогом); PageRank/Louvain выполнять батчами; для достижимости большого количества seed использовать битовые множества на конденсации компонент сильной связности или ограниченный обход с явной маркировкой приближения. UI должен запрашивать ограниченные окрестности, не загружать миллион узлов. Текущая достижимость O(S·(V+E)), память графа O(V+E); масштабирование пока не реализовано и не измерено.

Происхождение и ограничения использования

  • ТЗ Freedom / HackAlem, локальная копия docs/CASE_FINANCE.md.
  • Данные и исходный starter организатора: ссылки и хеши в audit/PHASE_0.md и audit/source_sha256.json. Данные разрешены только для хакатона, публичной лицензии на дальнейшее использование не заявлено.
  • Предоставленный starter.py сохранён неизменённым в audit/source/starter/starter/. Схемы выходов и набор базовых метрик следуют заданию/starter; новый pipeline реализован в соревновании. Отдельная лицензия starter не указана; использование в рамках выданного кейса.
  • pandas (BSD-3-Clause), NumPy (BSD-3-Clause; bundled components: 0BSD, MIT, Zlib, CC0-1.0), PyArrow / Apache Arrow (Apache-2.0), NetworkX (BSD-3-Clause), SciPy (BSD-3-Clause); FastAPI (MIT), Uvicorn (BSD-3-Clause); для тестов pytest (MIT), HTTPX (BSD-3-Clause). SVG-рендерер интерфейса написан в ходе конкурса; сторонние frontend-библиотеки и CDN не используются. Версии зафиксированы в requirements.
  • Разработка и тестирование с помощью OpenAI Codex. Runtime AI добавлен в PHASE 5 как необязательный read-only критик через OpenAI Responses API; условия передачи данных описаны ниже. Предварительные тренировочные проекты в решение не перенесены.

PHASE 4B: разнообразие последних входящих ветвей

outputs/seed_convergence.json — отдельный evidence layer; доступен в GET /api/nodes/{gid} как seed_convergence и в карточке узла. Роли, рейтинг, очереди, Louvain, observability, три CSV и PHASE 4A не меняются.

Для целевого узла v и каждого непосредственного отправителя p находим множество seed, достигающих p в графе без v. Нулевой путь seed→сам seed разрешён для отправителя, но сам целевой seed исключён. Это предотвращает ложное схождение через циклы v→…→p→v. Поэтому используем target-specific reverse traversal, а не глобальные seed-множества, загрязнённые такими возвратами. Для seed s, достигающего m(s) последних ветвей, каждая получает 1/m(s).

mass(p) = Σ[seed s reaches p without v] 1/m(s)
q(p) = mass(p) / Σ mass
effective_last_hop_branches = exp(-Σ q(p) * ln(q(p)))

Если поддержанных ветвей нет, результат 0: наблюдаемых внешних seed-входов нет. Это не утверждение о связях вне выгрузки. external_seed_count считает только внешние seed; исходный reachable_seed_count сохранён, включая self-reach для seed. branches содержит точный строковый predecessor_gid, число seed, массу и долю; сумма масс равна числу внешних seed. Сортировка и math.fsum обеспечивают детерминизм при перестановке входных строк. Худшая сложность O(E·(V+E)); перебор простых путей и max-flow не используются.

Это last-hop diversity, не независимые пути и не прослеживание денег. Один seed может разветвиться: эффективных ветвей может быть больше, чем seed. Ветви могут пересекаться upstream; масса не учитывает суммы переводов и время. При depth=4 входящая достижимость остаётся измеримой, выход остаётся цензурированным. Ограничения одного банка, месяца, порога и исходящего обхода сохраняются.

Проверка PHASE 4B: 48 тестов; pipeline 0.4416 с, pipeline + загрузка UI store 0.545 с на машине участника (не гарантия для другого оборудования). Из 2248 узлов: 50 имеют 0 ветвей, 1806 — ровно 1, 392 — больше 1; среднее 1.3031, медиана 1, максимум 20.0603. Подробнее: audit/PHASE_4B.md.

Уточнение для защиты: 91 — все вычисленные сообщества, включая одиночные; из них 8 содержат больше одного seed. Эти числа описывают разные срезы.

PHASE 5: AI Hypothesis Critic

AI помогает оспорить структурную гипотезу, сопоставить узлы и выбрать следующий запрос данных. Детерминированный движок назначает роли и приоритеты; AI только интерпретирует результаты, не меняя CSV, очереди, граф или рейтинг. Панель находится под рабочим пространством; доступны свободный вопрос, «Оспорить основную гипотезу», сравнение двух GID и отдельное поле 1–5 источников для поиска общего downstream.

Включение и запуск

В корне этого репозитория создайте .env по .env.example, задайте OPENAI_API_KEY и при необходимости OPENAI_MODEL (по умолчанию gpt-5.4-mini). Переменные процесса имеют приоритет. Ключ читается сервером и не отдаётся браузеру. Запуск прежний: python -m moneygraph.server. Дополнительных SDK/библиотек нет: HTTPS JSON-запросы через Python standard library, Responses API и function calling. Используются FastAPI/Pydantic для валидации. Панель не отправляет запросы автоматически.

Без ключа: «AI analyst unavailable», основная аналитика полностью доступна. При сетевой ошибке, нехватке кредита, недоступной модели, превышении лимита или malformed response показывается ошибка, без выдуманного AI-ответа. GET /api/analyst/status сообщает только наличие настройки, не доказывает валидность ключа. POST /api/analyst/ask принимает gid, question и необязательные compare_gid или source_gids (1–5 строк); возвращает NDJSON activity/result/error. Сервер локальный, по умолчанию 127.0.0.1; публичного доступа/авторизации нет.

Инструменты и протокол

  • get_node_profile(gid) — роли/силы, сильнейшая альтернатива, метрики, очередь, наблюдаемость, seed reach, last-hop diversity, вклады приоритета.
  • get_structural_evidence(gid) — dominator и пригодность каждого признака.
  • compare_nodes(gid_a, gid_b) — оба профиля, структурные признаки, data gaps, вклады рейтинга и их разности, преимущества второго узла по вкладам.
  • get_neighbors(gid) — до 30 направленных инцидентных связей по сумме, суммы/count и явное указание обрезки; это не полная сеть.
  • get_data_gaps(gid) — причины неполноты, CENSORED/N/A, следующий запрос.
  • get_cluster_context(gid) — размер/seed/оборот кластера, top nodes и гипотеза.
  • find_common_downstream(gids, max_hops) — детерминированный BFS от 1–5 источников, до четырёх переходов. До 20 кандидатов с покрытием и одним кратчайшим путём от каждого достигшего источника. Входные узлы исключаются из кандидатов; для нескольких источников требуется покрытие минимум двух. Сортировка: покрытие ↓, максимальная длина пути ↑, прежний priority ↓, точный GID ↑. Общее число кандидатов и усечение указаны отдельно; 4/5 не считается общим для всех. Пути агрегированного графа не доказывают движение одних и тех же денег или временной порядок.

Модель сама выбирает инструменты. Для индивидуального анализа/сравнения сервер требует профиль, structural evidence и gaps для каждого запрошенного узла до выдачи ответа; compare покрывает все три для обоих и обязателен при двух GID. Вывод содержит заключение, 2–4 основания, альтернативу, возражения, ограничение, следующий шаг и ссылки T1… на инструменты. Структура JSON и ссылки проверяются. Сервер также требует упоминания существующей сильнейшей альтернативы и явной неопределённости terminal на depth=4; при несогласованности модель получает запрос исправления в пределах того же лимита шагов. Activity log отражает выполненные tools, не скрытые рассуждения. В групповом режиме обязательный инструмент — find_common_downstream для всех указанных источников; проверки альтернативных ролей отдельных входных узлов здесь не применяются. Семантика ответа группы не проверяется формально: сравнивайте покрытие и GID с результатами инструмента. До пяти GID для downstream; индивидуальный анализ/сравнение — один/два GID. Лимиты: 16 tool calls, 5 запросов к модели и один AI-запрос одновременно. Каждый HTTP-вызов ограничен 45 секундами; новые шаги после 120 секунд не запускаются. UI ждёт до 150 секунд.

Передаваемые данные

По нажатию пользователем отправляются вопрос, выбранные GID, инструкции и схемы инструментов, затем только результаты реально вызванных tools: рассчитанные метрики/роли/суммы, ограничения, локальные связи, агрегаты кластера или до 20 downstream-кандидатов с путями. Полные parquet/CSV/JSON и вся сеть не отправляются. GID — точные строки. GID из соседей/кластера не дают модели право запрашивать новые узлы: допустимы только GID выбранного узла, явно указанные в вопросе, поле сравнения или списке источников. Каждый вопрос обрабатывается отдельно, истории диалога и локальной памяти нет. store=false; это параметр хранения Responses, не обещание отсутствия всех видов обработки/логирования у провайдера. Ответы демо вручную сохраняются только скриптом проверки; сервер не пишет тела запросов или ключи в логи.

Ограничения и воспроизводимость

Роль — гипотеза; strength не вероятность. Depth=4 цензурирует выход, seed имеет неполную входящую историю. Dominator не означает контроль денег. Поле переименовано в effective_last_hop_branches без изменения расчёта: это разнообразие последних входов, а не число независимых маршрутов. Новые личности, внешние данные, виновность и новые роли не выводятся. Инструкции и обязательные tools уменьшают риск выдумок, но семантическая истинность каждого предложения LLM не гарантируется: аналитик должен сверять ссылки и числа с карточкой. Формальная проверка всех числовых утверждений не реализована. При отсутствии преимущества второго узла нельзя его выдумывать.

Все Python unit/API tests работают без сети: python -m pytest -q. Дополнительно при установленном Node: node tests/test_analyst_frontend.cjs проверяет malformed/error ответы и текстовый рендеринг без исполнения HTML. python scripts/check_analyst.py выводит локальный предварительный состав шести демо без внешнего запроса. python scripts/check_analyst.py --live передаёт данные в OpenAI и расходует API-кредиты; используется только при разрешённой передаче данных. Модель, качество ответов и задержка зависят от доступности API; offline-критерии не требуют личного аккаунта.

Официальная документация, сверенная при реализации: Responses function calling, GPT-5.4 mini.

Архив реальных AI-ответов без ключа

Откройте /ai-archive (ссылка под живой AI-панелью). Это локальный просмотр шести реальных ранее выполненных демо-вопросов PHASE 5: вопрос, GID, ответ, модель, исходная задержка и журнал tools. Нового вызова OpenAI нет, ключ/кредиты не нужны. Для других GID показывается отсутствие записи; архив не выдаётся за AI-обработку всех 2248 узлов или за живую генерацию.

Ответы показываются без переписывания из audit/phase5_live_verification.json. Манифест audit/ai_archive_manifest.json фиксирует исходный коммит, SHA-256 записи, входных данных и расчётных артефактов. При изменившихся данных страница предупреждает, что архив относится к прежней версии; при повреждении записи ответы не показывает. Хеш — проверка неизменности локального файла, не подпись провайдера. Точное время исходных вызовов и полные HTTP/tool payloads не сохранялись; дата демо и журнал вызванных инструментов сохранены.

PHASE 6: Stability Lite — отдельная диагностика

python -m moneygraph.stability читает те же parquet и пишет только outputs/stability.json. Сначала должны существовать обычные outputs. В репозитории диагностика уже сохранена; сервер читает её без пересчёта при открытии страницы. При отсутствии или несовпадении хешей показывает N/A и продолжает работать. После изменения данных: сначала обычный pipeline, затем эта команда. Доступна сводка /api/stability/summary и поле stability в карточке /api/nodes/{gid}. Stability не добавляется в tools AI; AI-ответы в архиве не содержат эту новую диагностику.

Проверки разделены на две семьи, результаты не смешиваются:

Параметрический сценарийИзменение
volume_plus10 / volume_minus10вес объёма priority ×1.1 / ×0.9
neighbors_plus10 / neighbors_minus10вес соседей priority ×1.1 / ×0.9
seed_reach_plus10вес seed reach priority ×1.1
without_pagerankвес PageRank priority =0
role_first_component_plus10 / minus10первый компонент evidence каждой роли ×1.1 / ×0.9
thresholds_stricter / looserперечисленные ниже входные пороги +1 / −1

После изменения веса priority или evidence нормируются к сумме 1 в своей формуле. В threshold-сценариях меняются coordinator in/out degree ≥3 и seed ≥2, distributor out degree ≥5, consolidator in degree ≥3, terminal tx ≥3 и days ≥2. Для transit допустимое out/in окно — 0.85–1.15 / 0.75–1.25; формула близости к 1 сохраняет исходный знаменатель 0.2, при отрицательном значении обрезается нулём. Остальные пороги/нормировки, запрет terminal для seed/depth=4 и порядок разрешения равенств сохраняются. No-PageRank — намеренно более сильная ablation, а не «небольшое ±10% изменение». Сценарии общие, без настройки под GID.

Edge dropout: три фиксированных random seeds 42, 43, 44. Из сортированного списка 3119 aggregated edges без возвращения удаляются ceil(5%)=156 рёбер; соответствующие исходные транзакции также исключаются. Все 2248 узлов и первоначальные seed/depth сохраняются. Метрики, seed reach, PageRank, роли и приоритет пересчитываются только в диагностической копии. Кластеры/очереди не пересчитываются и их устойчивость этим тестом не оценивается.

Для каждой семьи: доля совпадений роли с baseline, доля вхождений в TOP-20, другие наблюдавшиеся роли; диапазон ранга показывается для baseline TOP-100 и любого узла, попавшего в TOP-20 хотя бы в одном сценарии. Равенства ранга разрешаются точным числовым GID как в baseline. Для исходных 19 изолятов — N/A. STABLE означает совпадение роли минимум в 90% сценариев данной семьи: 9/10 допустимо, при 3 dropout runs требуется 3/3. Это условность отображения. Оценка ранга видна отдельно: STABLE у роли не гарантирует стабильного места.

Диагностическая доля — не accuracy, вероятность, confidence interval или статистическая значимость. Равномерное удаление рёбер — stress test, не модель реальной неполноты банковской выгрузки. Все сценарии ограничены выбранными настройками; устойчивость не доказывает правильность роли. Большое число peripheral влияет на общую долю STABLE. Сценарии не независимые наблюдения.

Измерения: диагностика 0.4975 с до сериализации; обычный pipeline не изменён. 72 Python tests и 3 JS renderer checks проходят. Подробности и примеры — audit/PHASE_6.md.

PHASE 7: Network Resilience / TOP-N removal

Панель на главной странице: удаление TOP-1/3/5/10 и «Сбросить симуляцию». GET /api/resilience?n=0|1|3|5|10 возвращает готовый сценарий. Все пять вариантов вычисляются при создании приложения (около 0.05 с суммарно), переключение не запускает новый pipeline. N=0 — исходный граф.

Берётся исходный directed graph со всеми 2248 узлами, включая 19 изолятов. TOP-N выбран по существующему priority_score с разрешением равенств по GID. Удаляются узлы и инцидентные рёбра только в копии графа. Роли, очереди, Louvain, наблюдаемость, CSV, AI, Stability и основной ego-граф не меняются. Удалённые seed перечисляются явно. Карточки и граф над симуляцией остаются baseline.

Метрики до/после: weak components, размер крупнейшей компоненты, изоляты, оставшиеся seed, направленная достижимость от любого оставшегося seed (включая сам seed), компоненты с несколькими seed. Weak components — не Louvain-кластеры: в baseline 35 компонент и 2 multi-seed компоненты, тогда как Louvain даёт 91 кластер и 8 multi-seed кластеров.

Общая потеря = (reachable_before - reachable_after) / reachable_before. Она включает удалённые узлы. Отдельно показаны удалённые ранее достижимые и оставшиеся, потерявшие путь. Доля для оставшихся использует знаменатель reachable_before minus removed, чтобы не выдавать само удаление за разрыв связности. Если знаменатель нулевой, отображается N/A, а не фиктивные 0%.

new_fragments — сумма max(число частей исходной компоненты после удаления − 1, 0). Полностью удалённые исходные компоненты считаются отдельно, поэтому чистое изменение числа компонент может быть отрицательным и не тождественно новым фрагментам. Directed reachability loss также не равна распаду weak components: направленный путь может исчезнуть без разрыва ненаправленной связности.

Это structural counterfactual наблюдаемого среза, не прогноз реакции реальных участников, не денежный ущерб и не доказательство разрушения преступной группы. Перестроение связей, альтернативные банки/маршруты и новые операции неизвестны. Baseline reachability=2248 связано с seed-originated выборкой и включением самого seed; это не доказательство полноты реальной сети.

Проверено 79 Python tests + 3 JS renderer checks, все сценарии и Reset в браузере. Измерения, таблица и определения: audit/PHASE_7.md. Temporal добавлен отдельным слоем PHASE 8, см. следующий раздел.

PHASE 8: Pattern Analysis — время, маршруты, peer-аномалии

В каждой карточке раскрываемый блок «Паттерны». Это отдельный расчёт:

.venv/bin/python -m moneygraph.patterns

В репозитории уже есть outputs/patterns.json. Команда воспроизводит его из трёх parquet, не читая и не меняя роли, priority, очереди, CSV или Stability. После изменения входных данных сначала обычный pipeline, затем patterns. Параметры: --data, --out, --period-start, --period-end; по умолчанию период из ТЗ 2026-07-01…2026-07-31, не min/max присутствующих транзакций. Выход за объявленный период — ошибка. Сервер проверяет SHA-256 исходников, схему и полноту GID. При отсутствии/устаревании/повреждении — N/A, основное приложение работает. Сводка: /api/patterns/summary; подробности — patterns в обычной карточке /api/nodes/{gid}. AI-tools и сохранённый AI-архив этот новый слой не получают; новые внешние API-запросы для PHASE 8 не выполнялись.

Временная совместимость

Даты сведены к календарным дням, деньги — integer tiyn. Для каждого узла строится маленькая потоковая сеть: source → входной день → выходной день → sink. Между днями есть ребро только при задержке +1 или +2 дня. Ёмкости входных и выходных bucket равны наблюдаемой сумме; max-flow не использует одну сумму дважды. Входы допускаются только при in_date + 2 days <= period_end: 30–31 июля исключены из знаменателя и отдельно показаны как right-censored. Выходы последних дней могут покрывать более ранние допустимые входы.

compatibility_fraction = max_flow / eligible_incoming; при нулевом знаменателе N/A. Это максимальный совместимый объём, не оценка реально прошедших средств. Вневыборочные входы и начальный остаток неизвестны. Same-day overlap = сумма min(in_day, out_day), отдельно ORDER UNKNOWN; не суммировать с max-flow. Для depth=4 исходящий temporal, same-day overlap и gather→scatter — CENSORED/N/A, не 0%.

Gather→scatter: ≥3 различных отправителя за день и ≥3 различных получателя в объединении следующих двух дней, с полным +2 follow-up. Окна могут перекрываться; суммы эпизодов нельзя складывать как независимые потоки. Всплеск: ≥5 входящих+исходящих операций за день и ≥50% наблюдаемых операций узла за весь период. Это описательный порог, не статистическая значимость.

Повторяющиеся маршруты и возвраты

  • A→B→C, три различных узла. На каждой паре рёбер сопоставляются даты через +1/+2 дня: входные даты по возрастанию, ближайшая ещё не использованная выходная дата. Требуется ≥2 пары. Каждый day-bucket каждого ребра используется максимум один раз в данной цепочке. Календарная дата может быть выходом одного эпизода и входом следующего на другом ребре. Разные цепочки могут разделять транзакции. Суммы не трассируются; эпизоды — пары дней, не число доказанных денежных маршрутов.
  • Reciprocal: обе направленные связи A→B и B→A присутствуют в месячном графе; показаны суммы каждого направления. Это не утверждение о возврате тех же денег.
  • Простые направленные циклы длины 3–4. Минимальный GID выбирается началом, вращения не дублируются; обратное направление — другой цикл. Ограниченный DFS, без all_simple_paths: максимум 10 000 циклов или 200 000 просмотренных расширений. При достижении лимита явно показана неполнота; числа становятся нижними границами. Время/суммы по циклу не согласовываются.

В карточке считаются маршруты с участием узла в любой позиции; до 10 примеров каждого типа. Кнопки GID открывают карточки. Подсветка выделяет только рёбра, присутствующие в текущем ego graph, и сообщает число отображённых рёбер полного пути. Не дорисовываются отсутствующие в окрестности связи.

Объяснимые отклонения от depth-peer группы

Для каждого depth отдельно (включая нулевые значения и изоляты): число отправителей/получателей, входящий объём, вход+выход, число транзакций, максимум различных отправителей/получателей за день. Процентиль = средний ранг равных значений / размер группы. Сигнал: процентиль ≥0.99 и значение >0, группа ≥20; иначе SMALL_COHORT/N/A. Не применяется к внешнему населению. На depth=4 исходящие, общий объём, общее число транзакций и daily fan-out CENSORED/N/A. Входящие остаются наблюдаемыми, но не полной историей. У seed вход тоже неполон, даже в сравнении с другими seed.

Отдельное правило равных сумм: один отправитель в один день переводит одинаковую сумму (до тиына) минимум трём различным получателям. Дубли строк сохраняются, но один получатель не считается тремя. Это наблюдаемое распределение равных сумм, не доказательство дробления; операции <5000 KZT в данных отсутствуют. Branch diversity не включена в новые процентили: её исходная метрика остаётся отдельной. Число flags — число сработавших правил, не вероятность/accuracy.

На предоставленных данных: 86 повторяющихся цепочек, 177 reciprocal pairs, 210 циклов (полный поиск длин 3–4), 8 узлов с gather→scatter, 98 узлов с peer flags, 6 узлов с распределением равных сумм. Время нового расчёта ~0.26 с до записи JSON. 92 Python tests + 3 AI renderer checks + 4 pattern renderer checks. Дополнительная JS-проверка: node tests/test_patterns_frontend.cjs.

Базовый structural evidence сохранён: сила transit по-прежнему рассчитана без temporal. При наличии Pattern Analysis UI уточняет это в подсказке, не изменяя исторические JSON или AI-архив.

Единая точка запуска и русский интерфейс

Проверено: 93 Python-теста + 13 JS-проверок. Запуск starter.py --no-ui проверен из другого рабочего каталога; запуск сервера — в окружении, установленном по README, без ключа.

После установки requirements.txt готовый проект запускается из корня:

.venv/bin/python starter.py

Команда последовательно пересчитывает core → Stability → Pattern Analysis, затем запускает интерфейс http://127.0.0.1:8010. Ключ не требуется. Остановка Ctrl+C. Другой порт: --port 8011. Только расчёт без сервера:

.venv/bin/python starter.py --no-ui

Поддерживаются --data /path/to/data и --out /path/to/results. Диагностика паттернов в общем стартере рассчитана на период официального кейса, июль 2026; для другого периода используйте отдельную команду patterns с датами. Исходная команда python -m moneygraph.pipeline сохранена для минимального обязательного сценария. Наш starter.py — точка запуска готового решения; организаторская заготовка с пустыми ролями сохранена в audit/source/starter/ для происхождения кода. Её README не требует использовать заготовку как единственный способ запуска финального продукта.

Русификация выполнена в web/i18n.js: словарь отображаемых терминов, текстовые узлы, подписи и подсказки. Значения input/textarea, URL, data-атрибуты, имена API-полей, role enum, очереди, CSV, Python и расчётные JSON не переводятся. PageRank доступен в «Подробнее о расчёте». Суммы показаны в ₸. В архиве есть исходный ответ без словарных замен; исходный JSON и его хеши прежние.

Намеренно сохранены: бренд MoneyGraph Investigator, HackAlem / UNRU AI, провайдер OpenAI, ID узла, API/JSON/SHA-256 в техническом контексте, название модели, PageRank в раскрываемом расчёте. Имена функций, полей и английские термины в раскрываемой исходной записи/метаданных — оригинал, а не непереведённая навигация. README сохраняет технические идентификаторы.

Проверки локализации: node tests/test_localization.cjs.

Методика расчёта в интерфейсе

/methodology — локальная страница с 17 раскрывающимися разделами: данные, граф, признаки, роли, приоритет, кластеры, достаточность данных, очереди, зависимость путей, схождение, устойчивость ролей, удаление узлов, временные признаки, маршруты, отклонения, ИИ и ограничения. Ссылка «Показать расчёт этой роли» в карточке открывает выбранный GID, условия допуска и реальные численные вклады в силу роли.

Формулы и предикаты извлекаются из AST установленного roles.py, без второго набора весов. Пороги очередей импортируются из observability.py; потолки наблюдаемости и параметры Louvain извлекаются из кода. Числа диагностик читаются из проверенных результатов Store. Все пять ролей для всех 2248 узлов сверяются с ядром тестом; вывод округлён до шести знаков. tests/methodology_source_hashes.json требует пересмотреть поясняющий текст при изменении описываемых алгоритмов. После осмысленной сверки методики обновите соответствующий хеш; не обновляйте манифест вслепую. Страница не вызывает LLM и не меняет Store, расчёты или CSV.

Компоновка вкладки «Граф»

На desktop рабочая область высотой 720–750 px: очередь, локальный граф, инспектор с вкладками «Обзор», «Структура», «Паттерны», «Устойчивость», «Данные». Длинное содержимое прокручивается внутри колонок; у границы прокрутка продолжается на странице. Детали кластера и удаления TOP-N раскрываются отдельно. На tablet инспектор расположен под графом, на mobile блоки идут последовательно. Методика и архив сохраняют оформление. Проверены 1440×900, 1536×960, 1728×1117, 1024×768 и 390×844. Дополнительные проверки вкладок: node tests/test_inspector_frontend.cjs. Сценарий 5-минутной демонстрации: docs/DEMO_5_MIN.md.

Наведение или клавиатурный фокус на стрелке показывает краткую справку: направление, полные ID, гипотезы ролей, сумму и число переводов. Клик/тап или Enter закрепляет детали под графом; оттуда можно открыть участника. Escape и кнопка закрытия убирают детали; смена узла очищает выбор связи. Точные даты ребра не заявляются: существующий ego API передаёт агрегаты. Проверки представления: node tests/test_edge_inspector.cjs.

В легенде увеличенные цветные кружки соответствуют цветам ролей узлов. Стрелки окрашены по роли отправителя, а не получателя; подсветка маршрута остаётся оранжевой. Значки ⓘ объясняют все шесть ролей по наведению или фокусу. Клик/тап закрепляет пояснение; повторный клик, Escape или клик снаружи закрывает его. Роли остаются гипотезами: близость входа и выхода у транзитного узла сама по себе не доказывает передачу именно тех же денег или порядок операций.

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

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

Dantezavr 25

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

  • 2026-09-23 17:35Expand technical README details by defaultDantezavr
  • 2026-09-23 17:33Streamline README entry path and clarify verified code versionDantezavr
  • 2026-09-23 17:30Document all mandatory implementation constraintsDantezavr
  • 2026-09-23 17:26Clarify final verification status and graph UI in READMEDantezavr
  • 2026-09-23 17:25Finalize graph readability, role explanations and scroll chainingDantezavr
  • 2026-09-23 16:48Add accessible edge tooltips and pinned connection detailsDantezavr
  • 2026-09-23 16:39Separate reciprocal graph edges and prevent amount label overlapDantezavr
  • 2026-09-23 16:24Document coverage of all mandatory and optional case requirementsDantezavr
Все коммиты (25)
  • 2026-09-23 16:23Record clean-clone acceptance of frozen UI releaseDantezavr
  • 2026-09-23 16:21Polish graph workspace with tabbed inspector and readable layoutDantezavr
  • 2026-09-23 16:09Add local methodology with source-derived formulas and node breakdownsDantezavr
  • 2026-09-23 16:00Localize presentation in Russian and add complete project starterDantezavr
  • 2026-09-23 15:49Verify clean pattern reproduction and clarify baseline transit evidenceDantezavr
  • 2026-09-23 15:48Add isolated temporal, route and depth-peer pattern analysisDantezavr
  • 2026-09-23 15:37Record clean installation audit and five-minute defense planDantezavr
  • 2026-09-23 15:33Add bounded five-source AI lookup and freeze features for deliveryDantezavr
  • 2026-09-23 15:19Add baseline top-N removal resilience simulation and resetDantezavr
  • 2026-09-23 15:12Add fixed-scenario stability diagnostics without changing core resultsDantezavr
  • 2026-09-23 15:06Add honest offline archive of recorded real AI demo resultsDantezavr
  • 2026-09-23 15:00Add read-only AI hypothesis critic with bounded tools and grounded checksDantezavr
  • 2026-09-23 14:45Add cycle-safe last-hop seed convergence evidence and UIDantezavr
  • 2026-09-23 14:36Add observed dominator dependency and evidence availability without changing core scoresDantezavr
  • 2026-09-23 14:29Add local investigator dashboard with exact GID search and read-only graph APIDantezavr
  • 2026-09-23 14:02Add separate observability and investigation queues without changing Phase 1 scoresDantezavr
  • 2026-09-23 13:53Implement Phase 1 graph engine with validated CSV exports and rule testsDantezavr
Структура репозитория (114 файлов)
.env.example
.gitignore
README.md
audit/DOWNSTREAM_FREEZE.md
audit/PHASE_0.md
audit/PHASE_1.md
audit/PHASE_10.md
audit/PHASE_11_UI.md
audit/PHASE_2.md
audit/PHASE_3.md
audit/PHASE_4A.md
audit/PHASE_4B.md
audit/PHASE_5.md
audit/PHASE_6.md
audit/PHASE_7.md
audit/PHASE_8.md
audit/PHASE_9.md
audit/ai_archive_manifest.json
audit/data_profile.json
audit/downstream_live_verification.json
audit/final_environment.txt
audit/final_reproduction.json
audit/phase1_verification.json
audit/phase2_verification.json
audit/phase3_verification.json
audit/phase4a_verification.json
audit/phase4b_verification.json
audit/phase5_live_verification.json
audit/phase5_verification.json
audit/phase6_verification.json
audit/phase7_verification.json
audit/phase8_clean_check.json
audit/phase8_verification.json
audit/source/README.md
audit/source/data/data/edges.parquet
audit/source/data/data/nodes.parquet
audit/source/data/data/transactions.parquet
audit/source/starter/starter/README.md
audit/source/starter/starter/requirements.txt
audit/source/starter/starter/starter.py
audit/source_sha256.json
audit/ui-polish/acceptance.json
audit/ui-polish/edge-tooltip.png
audit/ui-polish/graph-1440.png
audit/ui-polish/graph-1728.png
audit/ui-polish/layout-checks.json
audit/ui-polish/reciprocal-edges.png
docs/CASE_FINANCE.md
docs/DEMO_5_MIN.md
docs/SUBMISSION.md
moneygraph/__init__.py
moneygraph/analyst.py
moneygraph/archive.py
moneygraph/communities.py
moneygraph/convergence.py
moneygraph/downstream.py
moneygraph/evidence.py
moneygraph/features.py
moneygraph/io.py
moneygraph/methodology.py
moneygraph/observability.py
moneygraph/patterns.py
moneygraph/pipeline.py
moneygraph/resilience.py
moneygraph/roles.py
moneygraph/server.py
moneygraph/stability.py
outputs/clusters.csv
outputs/node_context.json
outputs/nodes_roles.csv
outputs/patterns.json
outputs/run_report.json
outputs/seed_convergence.json
outputs/stability.json
outputs/structural_evidence.json
outputs/top_nodes.csv
requirements-dev.txt
requirements.txt
scripts/check_analyst.py
starter.py
tests/methodology_source_hashes.json
tests/phase1_csv_sha256.json
tests/phase4b_convergence_sha256.json
tests/phase4b_frozen_sha256.json
tests/test_analyst.py
tests/test_analyst_frontend.cjs
tests/test_archive.py
tests/test_convergence.py
tests/test_downstream.py
tests/test_edge_geometry.cjs
tests/test_edge_inspector.cjs
tests/test_evidence.py
tests/test_inspector_frontend.cjs
tests/test_localization.cjs
tests/test_methodology.py
tests/test_observability.py
tests/test_patterns.py
tests/test_patterns_frontend.cjs
tests/test_pipeline.py
tests/test_resilience.py
tests/test_server.py
tests/test_stability.py
tests/test_starter.py
web/ai-archive.html
web/ai-archive.js
web/app.js
web/edge-inspector.js
web/i18n.js
web/index.html
web/methodology.css
web/methodology.js
web/role-help.js
web/style.css
web/workspace.css

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

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

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