AAYMA
RU
Войти

ВЕРШИНА · Vertex

Офлайн-инструмент исследования транзакционной сети: роли узлов по упорядоченным правилам, PageRank, HITS, betweenness, сообщества Louvain, приоритет…

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

О проекте

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

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

Суть

Офлайн-инструмент исследования транзакционной сети: роли узлов по упорядоченным правилам, PageRank, HITS, betweenness, сообщества Louvain, приоритет проверки, 3 CSV и автономный HTML-дашборд с force-graph и опциональным OpenAI-ассистентом.

Как устроено

solution.py строит DiGraph по всем 2248 узлам, считает PageRank и HITS с весом sum_kzt, betweenness с расстоянием 1/sum_kzt, простые циклы длиной ≤5 и turnover_days. Роли назначаются правилами сверху вниз по перцентилям, узлы глубины 4 без исходящих помечаются как «слепая зона». Louvain (seed=42) запускается на неориентированной взвешенной проекции. Приоритет — взвешенная сумма веса роли и перцентильных рангов с min-max нормировкой. build_viz.py встраивает данные и force-graph в один vertex.html. server.py по кнопке подключает OpenAI Responses API, который не меняет расчёты.

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

  • NetworkX
  • PageRank
  • HITS
  • betweenness
  • Louvain
  • simple cycles ≤5
  • rule-based роли
  • percentile rank
  • force-graph
  • OpenAI Responses API

Метрики

~5 с на 2248 узлах/3119 рёбрах/4840 переводах; роли: coordinator 12, distributor 99, consolidator 85, transit 310, terminal 1535 (444 слепая зона), peripheral 207

Особенности

Артефакт выборки, 444 узла на 4-м колене без исходящих, явно помечен «слепой зоной», чтобы такие узлы не принимали за конечных получателей. Контракт CSV с типами строгий, GID больше 2^53 передаются строками, чтобы не терять точность в JS. Дашборд — один HTML-файл, открывается офлайн двойным кликом.

Стек

Python 3.12 · pandas · NumPy · NetworkX · JavaScript · HTML · GitHub Actions · unittest

Модели

OpenAI Responses API


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

README

README.md

ВЕРШИНА · Vertex

Командная интеграция: аудит, работа вдвоём, архитектурная схема. Проверка целиком: python verify.py.

Локальный инструмент исследования наблюдаемой транзакционной сети. Роли, приоритеты и гипотезы — сигналы для проверки, а не установление виновности. Показываем только синтетический gid, математические признаки и связи из выгрузки.

Запуск

Проверенное окружение: Python 3.12. Версии библиотек зафиксированы в requirements.txt.

Windows PowerShell:

.\run.ps1
# С локальным сервером и возможностью включить OpenAI:
.\run.ps1 --serve

Linux/macOS:

bash run.sh
bash run.sh --serve

В существующем Python-окружении:

python -m pip install -r requirements.txt
python run.py
python run.py --serve --skip-build

Офлайн: открыть out/vertex.html двойным кликом. Сервер: http://127.0.0.1:8777. Сервер слушает только loopback; это локальный инструмент, не публичный многопользовательский сервис.

Ручная сборка:

python backend/solution.py --data data --out out
python backend/build_viz.py --out out --vendor viz/vendor
python -m unittest discover -s tests -v

Консоль показывает этапы расчёта. На предоставленных 2248 узлах, 3119 рёбрах и 4840 переводах исправленный расчёт занял около 5 с в проверенном Windows-окружении (установка зависимостей не включена). Лимит кейса — 5 минут. Время зависит от машины и структуры данных.

Компоненты и поток данных

Parquet → проверка входов → ориентированный граф всех узлов → признаки → роли → Louvain → приоритет → проверка схем → 3 CSV + graph.json + summary.json → vertex.html.

  • backend/solution.py: расчёты и выгрузки.
  • backend/build_viz.py: сверяет CSV/JSON, читает топ-30 и гипотезы кластеров из CSV, встраивает данные, CSS, JS и локальный force-graph в единый HTML.
  • viz/dashboard.html, viz/dashboard.js: интерфейс, офлайн-фильтры и ассистент.
  • backend/server.py: необязательный локальный сервер с OpenAI Responses API.
  • tests/test_pipeline.py: регрессионные, интеграционные и граничные проверки.

Основная аналитика не требует облака, API или GPU. OpenAI используется только после явного выбора облачного режима/кнопки в UI и не меняет CSV, роли или приоритеты.

Формальные правила ролей

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

ПорядокРольУсловие
1terminal, не подтверждёнdepth == 4 && out_deg == 0; поддержка правила 0.35
2terminalout_deg == 0 && in_deg >= 1; исходящие не наблюдаются
3coordinatorесть вход и выход, betweenness >= p98 и pagerank >= p90
4distributorout_deg > 0 && out_deg >= in_deg и (out_deg >= p95 или out_deg >= 5 && out_kzt >= p90)
5consolidatorin_deg >= 3 && in_deg >= out_deg
6transitне seed, есть вход/выход, 0.7 <= pass_through <= 1.3
7transitоставшийся не-seed с входом/выходом и pass_through >= 0.5
8peripheralвсё остальное

Перцентили вычисляются на активных узлах; p95 исходящей степени — на узлах с out_deg > 0. Используется линейная интерполяция NumPy без округления вниз и без дополнительного пола: на этом наборе p95 = 16.35. Структурные константы 3 и 5 и границы pass-through заданы явно и не выдаются за перцентили. Для пустого распределения порог равен 0; условия положительной степени исключают ложные роли у изолятов.

Результат исправленного расчёта на приложенных данных:

РольКоличество
coordinator12
distributor99
consolidator85
transit310
terminal1535
peripheral207

Из терминалов 444 — слепая зона, 1091 — без исходящих на глубине <4. Изменение относительно первой версии ожидаемо: исправлены порядок правил, seed и веса.

role_score — эвристическая поддержка правила, не вероятность и не оценка виновности. Она вычисляется из перцентильных рангов метрик, степени, циклов и близости pass-through к 1; точные формулы находятся в assign_roles. Нет ground truth для оценки классификационной точности.

Метрики и детерминизм

  • Все 2248 узлов включены в DiGraph, в том числе 19 изолированных.
  • PageRank: направление переводов и weight=sum_kzt.
  • HITS: денежный атрибут weight=sum_kzt, явно выбран power iteration, постоянный начальный вектор, tol=1e-12, максимум 10000 итераций. Ошибки сходимости не скрываются.
  • Betweenness: ориентированные кратчайшие пути с distance = 1 / sum_kzt. Большая сумма означает меньшую стоимость пути. Это модель доступности денежных связей, а не доказательство маршрута конкретной суммы; результаты зависят от выбранной модели расстояния.
  • Louvain: неориентированная взвешенная проекция; встречные суммы складываются. Направление намеренно теряется только при выделении сообществ.
  • seed=42 передаётся Louvain. Прочие расчёты не используют случайную выборку. Входы сортируются, одинаковые приоритеты разрешаются по gid, одинаковые размеры сообществ — по минимальному gid.
  • Зависимости закреплены. Битовая идентичность между разными BLAS/ОС не обещается; повторный запуск и перестановка входных строк проверены тестом.
  • Циклы: все простые направленные циклы длиной ≤5. Экспортируются сами циклы и принадлежность рёбер. Ошибки не превращаются в «циклов нет».
  • turnover_days: медианный лаг от отправки до ближайшего предшествующего получения, поиск через searchsorted. Дневные даты не доказывают внутридневную последовательность.

Приоритет:

raw = 0.30*вес_роли + 0.20*rank(pagerank) + 0.20*rank(betweenness)
    + 0.15*rank(in_kzt + out_kzt) + 0.08*min(n_seed_in,3)/3 + 0.07*in_cycle
priority = minmax(raw), округление до 4 знаков

rank — pandas percentile rank с усреднением совпадений. Веса: coordinator 1, consolidator .85, distributor .75, transit .55, terminal .40, peripheral .10. При одинаковых raw у всех узлов приоритет равен 0.

Артефакты выборки и ограничения

  • truncated_by_depth = depth == 4 && out_deg == 0 имеет высший приоритет: все 444 узла помечены, получают terminal/0.35, предупреждение в evidence и карточке. Нужен запрос 5-го колена; отсутствие исходящих нельзя трактовать как оседание денег.
  • Seed никогда не получают transit по отношению сумм. Для них pass_through=-1; их evidence показывает структуру и количество переводов, без денежных сумм. Карточка показывает наблюдаемые суммы с предупреждением о неполном входе.
  • Для не-seed evidence содержит входящую/исходящую сумму и количество переводов. Размер evidence <200 символов. Все формулировки — наблюдения или гипотезы.
  • Циклы структурные, из месячных агрегатов: они не доказывают возврат тех же денег или хронологическое прохождение полной цепочки.
  • Изоляция, симуляция и LLM не изменяют исходную выгрузку и не блокируют реальные счета.
  • Точные betweenness и перечисление коротких циклов не имеют универсальной гарантии времени на произвольно больших или плотных сетях. Лимит проверен на данных кейса.

Схема выходов

Ровно три CSV, UTF-8, без индекса. Дополнительно JSON и автономный HTML. Пустые значения и NaN в CSV запрещены валидатором. В CSV нет встроенных типов: потребитель должен использовать следующий контракт.

nodes_roles.csv, 2248 строк на данном наборе, колонки в порядке:

gid,role,role_score,cluster_id,priority_score,evidence,
in_deg,out_deg,in_kzt,out_kzt,pagerank,pass_through,depth,is_seed,truncated_by_depth,
hub,authority,betweenness,net_kzt,n_seed_in,in_cycle,turnover_days,in_tx,out_tx
ТипКолонки
int64gid, cluster_id, depth, in_deg, out_deg, n_seed_in, in_tx, out_tx
float64role_score, priority_score, in_kzt, out_kzt, pagerank, pass_through, hub, authority, betweenness, net_kzt, turnover_days
bool, CSV True/Falseis_seed, truncated_by_depth, in_cycle
textrole, evidence

pass_through=-1: не определён (нет входящей суммы) или неприменим к seed. turnover_days=-1: нет наблюдаемой пары «получение → отправка». Это маркеры отсутствия наблюдения, не реальные отрицательные величины. В JSON эти два случая представлены null, в UI — «не применяется»/«не наблюдается».

clusters.csv: cluster_id:int64, n_nodes:int64, n_seed:int64, sum_kzt_internal:float64, top_gids:text, hypothesis:text. top_gids — до 5 gid через |; суммы внутренних рёбер округляются до копеек. Гипотеза CSV — детерминированная, из состава ролей, и явно помечена как гипотеза.

top_nodes.csv: rank:int64, gid:int64, role:text, priority_score:float64, why:text. Топ-30 (на меньшей сети — все доступные узлы), сортировка priority убывает, gid возрастает.

GID больше 2^53: не преобразовывать в JavaScript Number. В graph.json идентификаторы узлов/рёбер/циклов — строки. summary.json содержит реальные пороги, количество ролей, seed и версии библиотек.

GUI: overview → filter → details

Четыре вкладки: граф, кейсы, устойчивость, ассистент. cases.json и resilience.json согласованы с graph.json при сборке. Кейсы имеют стабильные ID от состава сообщества; заметки хранятся по версии анализа. Экспорт/импорт JSON переносит их между компьютерами, сохраняя локальные конфликты по умолчанию. Git не синхронизирует браузерное хранилище. Идентичные строки переводов сохраняются как отдельные наблюдения с разными TX-ID.

Широкий экран: панель 30%, граф 70%; на узком экране зоны расположены вертикально.

  • Мгновенный поиск полного gid, сброс конфликтующих фильтров, центрирование.
  • Таблица Rank / GID / Role / Priority; клик выделяет узел и его соседей.
  • Выбор кластера фильтрует сеть и показывает гипотезу; OpenAI-кнопка доступна при настройке API.
  • Цвет = роль; красный — координирующий, оранжевый — транзит, синий — терминал; размер = приоритет; стрелка = направление; толщина = логарифм суммы.
  • Кольцо = seed; пунктирное жёлтое кольцо = слепая зона.
  • Изоляция оставляет coordinator/consolidator/transit и связи между ними.
  • Циклы подсвечиваются красным только если все узлы цикла видны и не удалены.
  • Карточка: наблюдаемые суммы, степени, количество переводов, поддержка правила, evidence, все входящие/исходящие контрагенты с переходами.
  • Симуляция удаления топ-0…30: заранее рассчитанные NetworkX показатели слабой связности по всей оставшейся сети, не по текущему фильтру. Показывает компоненты, крупнейшую компоненту, одиночные узлы и исходный уровень.
  • Выбор исключённого узла явно сбрасывает симуляцию.
  • Офлайн-ассистент: детерминированные запросы по роли, seed-плательщикам, циклам, слепым зонам и gid.

Необязательный OpenAI

Ключ остаётся только в окружении серверного процесса, не попадает в HTML, JavaScript, CSV, репозиторий или ответы API. .env исключён из Git. .env.example — шаблон; автоматической загрузки .env нет.

PowerShell, перед запуском сервера:

$env:OPENAI_API_KEY = Read-Host "OpenAI API key" -MaskInput
$env:OPENAI_MODEL = Read-Host "Model ID available to your API project"
python run.py --serve --skip-build

В старом Windows PowerShell без -MaskInput задайте ключ через настройки окружения или менеджер секретов; не сохраняйте его в истории команд. Выберите модель, поддерживающую Responses API и Structured Outputs.

Чат отправляет вопрос и ограниченные метаданные (число узлов, перечень допустимых ролей и метрик), получает структурированный фильтр, затем выбирает реальные gid и формирует доказуемый ответ локально. Неизвестные gid/метрики отвергаются. LLM не генерирует ФИО, возраст, «профили» или обвинительные объяснения.

Кнопка гипотезы передаёт агрегаты выбранного кластера; модель выбирает один из фиксированных нейтральных типов структуры, а числа и итоговый текст собираются из данных. Результат помечается LLM-гипотеза; CSV не перезаписывается.

Используется store=false; это не утверждение об отсутствии всей обработки/хранения на стороне провайдера. Запросы происходят только при действии аналитика в облачном режиме. При отсутствии ключа/модели UI сохраняет локальный поиск. При сбое API выводится ошибка; облачный ответ не имитируется.

Реализация: официальная документация Structured Outputs.

Проверки

python -m unittest discover -s tests -v проверяет: seed и слепые зоны, границы ролей и дробный p95, взвешенные HITS/betweenness, неориентированную проекцию, точные рёбра циклов, изоляты, временные лаги, симуляцию удаления, повторяемость при перестановке строк, CSV/JSON/HTML и валидацию запросов ассистента. Внешние API-вызовы в тестах подменяются.

Звёзды
⭐ 0
Форки
0
Язык
HTML
Последний push
в прошлом месяце
Коммиты
7
Авторы
4
Файлы
42
Ветки
2

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

Sayat1231 3adilaltynbek 2Sayat Dzhunusov 1Krutoq 1

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

  • 2026-09-23 17:43Merge verified Vertex product into mainKrutoq
  • 2026-09-23 17:41Document published integration and external Actions billing blockeradilaltynbek
  • 2026-09-23 17:39Integrate team analytics, safe case exchange and verified offline dashboardadilaltynbek
  • 2026-09-23 17:55Merge pull request #2 from BAITC-Hacks/design/dashboardSayat Dzhunusov
  • 2026-09-23 17:54Дизайн: переключатель тёмной/светлой темы (C · Clean Light)Sayat1231
  • 2026-09-23 16:55Прод-версия Вершина: слияние аналитики напарника + кейсы + минимализм-дашбордSayat1231
  • 2026-09-23 14:58Вершина (Vertex) — решение кейса «Граф денег» (AML)Sayat1231
Структура репозитория (42 файлов)
.env.example
.gitattributes
.github/workflows/verify.yml
.gitignore
README.md
backend/build_viz.py
backend/cases.py
backend/explore.py
backend/server.py
backend/solution.py
backend/starter.py
data/edges.parquet
data/nodes.parquet
data/transactions.parquet
docs/ARCHITECTURE.md
docs/PITCH.md
docs/TEAM_AUDIT.md
docs/TEAM_WORKFLOW.md
docs/architecture.svg
docs/data_readme.md
docs/starter_readme.md
docs/ОБЪЯСНЕНИЕ.md
out/cases.json
out/clusters.csv
out/graph.json
out/nodes_roles.csv
out/resilience.json
out/summary.json
out/top_nodes.csv
out/vertex.html
requirements.txt
run.ps1
run.py
run.sh
tests/case-state.test.cjs
tests/test_cases.py
tests/test_pipeline.py
verify.py
viz/case-state.js
viz/dashboard.html
viz/dashboard.js
viz/team-workspace.js
Ветки (2)

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

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

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