О проекте
Команда: 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, роли или приоритеты.
Формальные правила ролей
Правила применяются сверху вниз, первое совпадение побеждает. Сначала слепая зона и отсутствие исходящих, затем координация, распределение, консолидация, транзит и остаток.
| Порядок | Роль | Условие |
|---|---|---|
| 1 | terminal, не подтверждён | depth == 4 && out_deg == 0; поддержка правила 0.35 |
| 2 | terminal | out_deg == 0 && in_deg >= 1; исходящие не наблюдаются |
| 3 | coordinator | есть вход и выход, betweenness >= p98 и pagerank >= p90 |
| 4 | distributor | out_deg > 0 && out_deg >= in_deg и (out_deg >= p95 или out_deg >= 5 && out_kzt >= p90) |
| 5 | consolidator | in_deg >= 3 && in_deg >= out_deg |
| 6 | transit | не seed, есть вход/выход, 0.7 <= pass_through <= 1.3 |
| 7 | transit | оставшийся не-seed с входом/выходом и pass_through >= 0.5 |
| 8 | peripheral | всё остальное |
Перцентили вычисляются на активных узлах; p95 исходящей степени — на узлах с out_deg > 0.
Используется линейная интерполяция NumPy без округления вниз и без дополнительного пола:
на этом наборе p95 = 16.35. Структурные константы 3 и 5 и границы pass-through
заданы явно и не выдаются за перцентили. Для пустого распределения порог равен 0;
условия положительной степени исключают ложные роли у изолятов.
Результат исправленного расчёта на приложенных данных:
| Роль | Количество |
|---|---|
| coordinator | 12 |
| distributor | 99 |
| consolidator | 85 |
| transit | 310 |
| terminal | 1535 |
| peripheral | 207 |
Из терминалов 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
| Тип | Колонки |
|---|---|
| int64 | gid, cluster_id, depth, in_deg, out_deg, n_seed_in, in_tx, out_tx |
| float64 | role_score, priority_score, in_kzt, out_kzt, pagerank, pass_through, hub, authority, betweenness, net_kzt, turnover_days |
| bool, CSV True/False | is_seed, truncated_by_depth, in_cycle |
| text | role, 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-вызовы в тестах подменяются.
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.