AAYMA
RU
Войти

Qadam

Streamlit-приложение для AML-аналитика: по транзакционной сети назначает роли всем 2 248 клиентам, выделяет сообщества и строит объяснимую очередь…

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

О проекте

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

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

Суть

Streamlit-приложение для AML-аналитика: по транзакционной сети назначает роли всем 2 248 клиентам, выделяет сообщества и строит объяснимую очередь проверки (top-50) с интерактивным графом и карточкой узла.

Как устроено

Пайплайн (run.py / src.pipeline) читает три Parquet-файла, строит направленный граф в NetworkX, считает признаки (в т.ч. PageRank через SciPy), по конфигу scoring.yaml назначает роли и приоритет и выделяет сообщества. На выходе три обязательных CSV и аналитический отчёт. Интерфейс на Streamlit со встроенным JS-фронтендом связывает рейтинг, граф, карточку узла и вклады факторов. Опциональная кнопка «Объяснить узел» вызывает LLM только по уже рассчитанным данным, на роли и баллы она не влияет. Отдельная CLI-проверка оценивает, насколько устойчив топ-20 при смене весов и параметров кластеризации.

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

  • NetworkX
  • PageRank
  • community detection
  • rule-based role scoring
  • top-20 ranking stability audit
  • Supabase Auth
  • Google OIDC

Особенности

Отдельная CLI-проверка устойчивости топ-20 к смене весов и кластеризации. ИИ только поясняет готовый результат и не определяет роль. Роли прямо названы гипотезами, а балл не выдаётся за вероятность преступления. Есть локальный офлайн-режим и Supabase-авторизация для облачного деплоя.

Стек

Python · Streamlit · NetworkX · SciPy · pandas · SQLite · Supabase · Docker · JavaScript

Модели

GPT-4o-mini


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

README

README.md

Qadam — аналитика финансовых связей

Пошаговый анализ транзакционной сети и приоритизация счетов для проверки.

Открыть онлайн-демо · Основной репозиторий · Зеркало для сайта · Сценарий защиты

Junior Syndicate · HackAlem AI · кейс Freedom «Граф денег»

AML-аналитик видит нижний уровень цепочки переводов и вручную выясняет, кто выше собирает, передаёт и распределяет деньги. Qadam превращает предоставленную транзакционную сеть в объяснимую очередь проверок: кого посмотреть первым, какую роль предположить и на каких наблюдаемых фактах она основана.

Из трёх Parquet-файлов приложение рассчитывает признаки, назначает роли всем клиентам, выделяет сообщества и формирует три обязательных CSV. Интерфейс связывает рейтинг с графом, карточкой узла и ограничениями данных. Дополнительное ИИ-пояснение помогает прочитать готовый результат, но не определяет роль или приоритет.

Роль — гипотеза для проверки. Балл — не вероятность преступления. Qadam не устанавливает виновность и не принимает решения о блокировке счетов.

Навигация

Что получает аналитик

ЗадачаВозможность Qadam
С чего начать проверку?Ранжированный список: по умолчанию top-50, с числовым обоснованием приоритета
Что происходит с конкретным клиентом?Поиск полного gid, входящие и исходящие связи, суммы, контрагенты и карточка роли
Почему назначены роль и балл?«Полное обоснование»: исходные метрики, вклады факторов и ограничения наблюдения
Как связаны участники?Направленная карта, цвет по роли или кластеру, окрестность узла, сообщество и полный граф
Какие группы стоит изучить?Размеры кластеров, количество seed, внутренний оборот и проверяемая гипотеза
Можно ли выгрузить результат?nodes_roles.csv, clusters.csv, top_nodes.csv и аналитический отчёт
Насколько результат зависит от настроек?Отдельная CLI-проверка топ-20 при изменении весов и кластеризации
Как быстро прочитать сложную карточку?Опциональная кнопка «Объяснить узел», работающая по рассчитанным данным

Начальный обзор показывает 120 наиболее приоритетных узлов для читаемости; это не весь граф. В разделе «Анализ сети» доступны все 2 248 узлов. Колесо над картой меняет масштаб; для прокрутки страницы используйте область вне карты.

Онлайн-демо и его зеркало развёртывает Алишер. Основной командный репозиторий указан отдельно: автоматическая синхронизация зеркала не предполагается, версии могут различаться. Настройки входа и ИИ зависят от конкретного развёртывания; для воспроизводимой проверки используйте локальный запуск ниже.

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

Проверенная среда: Python 3.12, CPU. Для основного анализа не нужны GPU, облачная база или API-ключ. Интернет требуется для первоначальной установки зависимостей; сам расчёт работает локально.

Откройте терминал в корне проекта — каталоге с run.py и requirements-lock.txt. Если используете зеркало с вложенной папкой Qadam, сначала перейдите в неё.

Поместите данные организаторов в data/:

data/
├── nodes.parquet
├── edges.parquet
└── transactions.parquet

Исходные Parquet и каталог output/ исключены из Git: при клонировании основного репозитория их нужно получить отдельно у команды или организаторов. Не публикуйте данные без разрешения.

Windows PowerShell

py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-lock.txt
$env:MONEY_GRAPH_AUTH='local'
$env:QADAM_ALLOW_EXTERNAL_AI='false'
.\.venv\Scripts\python.exe run.py
.\.venv\Scripts\python.exe -m streamlit run app/app.py

Если команда py недоступна, используйте путь к установленному Python 3.12. Активация окружения и изменение execution policy для этих команд не требуются.

Linux / macOS

python3.12 -m venv .venv
.venv/bin/python -m pip install -r requirements-lock.txt
export MONEY_GRAPH_AUTH=local
export QADAM_ALLOW_EXTERNAL_AI=false
.venv/bin/python run.py
.venv/bin/python -m streamlit run app/app.py

Откройте http://127.0.0.1:8501, создайте локальный аккаунт и сохраните резервный код. В этом режиме вход работает без почтового сервиса; локальный сервер слушает только loopback. Аккаунты хранятся отдельно от аналитических данных, в SQLite.

Команды выше явно выбирают автономный режим и выключают внешнее ИИ-пояснение. Supabase Auth с подтверждением email и списком разрешённых пользователей, а также опциональный Google OIDC настраиваются отдельно: авторизация. База аккаунтов не используется для расчёта графа; переносить Parquet в SQL для запуска не нужно.

requirements-lock.txt фиксирует проверенные версии, включая SciPy для PageRank. requirements.txt задаёт минимальные зависимости для самостоятельного обновления; результаты между версиями NetworkX могут отличаться.

Одна команда для расчёта

В активированном окружении выполните:

python run.py

Результат — три обязательных CSV и дополнительные отчёты в output/. Без активации используйте путь к Python из .venv, как в примерах выше. Настройка путей: python -m src.pipeline --data data --out output --config config/scoring.yaml; список параметров: python run.py --help.

Отсутствующие или некорректные входы приводят к понятной ошибке и ненулевому exit code. Пути по умолчанию привязаны к корню проекта. Актуальная точка входа интерфейса — app/app.py, не одноимённый файл в корне.

Деплой в Streamlit Community Cloud

Сайт команды: Qadam в Streamlit. Для него команда использует зеркало Алишера. Ниже — инструкция воспроизведения развёртывания; текущие настройки чужого кабинета хостинга она не подтверждает. Docker для Community Cloud не требуется.

1. Подготовить deployment-репозиторий

Нужны исходники приложения, зависимости и разрешённый доступ к данным. Выберите соответствующие пути:

НастройкаПроект в корне репозиторияПроект во вложенной папке Qadam/
Branchmainmain
Main file pathapp/app.pyQadam/app/app.py
Исходные данныеdata/*.parquetQadam/data/*.parquet
Результаты расчётаoutput/Qadam/output/

Для зеркала выберите второй вариант, если файлы действительно находятся в Qadam/; перед деплоем проверьте дерево выбранной ветки. Не выбирайте старый app.py из корня проекта.

Community Cloud ищет requirements.txt в корне Git-репозитория или рядом с файлом запуска. Одного Qadam/requirements.txt при запуске Qadam/app/app.py недостаточно. Для фиксации проверенных версий в deployment-копии поместите в корневой requirements.txt одну строку:

-r Qadam/requirements-lock.txt

Если проект лежит непосредственно в корне, строка должна быть -r requirements-lock.txt. Сам lock-файл также должен присутствовать. Не оставляйте рядом с точкой входа другой файл зависимостей, который перехватит установку. Пользовательский конфиг Cloud читает из .streamlit/config.toml в корне Git-репозитория. Локальную привязку server.address = "127.0.0.1" в облачной копии уберите; адрес и порт оставьте хостингу. Правила структуры, установка зависимостей.

Приложению нужны nodes.parquet, edges.parquet, transactions.parquet в указанном каталоге данных. Основной репозиторий их не содержит. Для воспроизводимого развёртывания используйте разрешённую организаторами закрытую deployment-копию с этими файлами и ограничьте аудиторию сайта; для публичного репозитория требуется отдельное разрешение на публикацию данных. Автоматического загрузчика из Supabase Storage в текущем коде нет. Готовый output/ можно не переносить: после входа выполните расчёт кнопкой «Запустить анализ».

Не переносите .venv, .env, реальные secrets.toml, SQLite-базы аккаунтов и резервные коды в GitHub. Локальный диск Cloud не используйте как единственное хранилище аккаунтов: для облачного входа предусмотрена Supabase Auth.

2. Создать приложение

  1. Войдите в Streamlit Community Cloud и подключите GitHub-аккаунт с доступом к deployment-репозиторию.
  2. Нажмите Create app → Yup, I have an app.
  3. Выберите репозиторий, ветку main и Main file path из таблицы выше. Для зеркала репозиторий — AlisherAt/junior-syndicate.
  4. В Advanced settings выберите Python 3.12 и заполните Secrets по следующему разделу.
  5. Сохраните настройки и запустите развёртывание. Дождитесь установки зависимостей; при ошибке откройте логи приложения.

Порядок действий: официальная инструкция Streamlit.

3. Настроить Secrets

В поле Secrets вставьте TOML, заменив шаблоны своими значениями. Это настройки хостинга, а не файл для коммита:

MONEY_GRAPH_AUTH = "supabase"
QADAM_ALLOW_EXTERNAL_AI = "false"
OPENAI_API_KEY = ""
OPENAI_MODEL = "gpt-4o-mini"

[supabase]
url = "https://YOUR_PROJECT.supabase.co"
publishable_key = "[скрыто]"
allowed_emails = "[email protected],[email protected]"

Корневые параметры должны стоять до [supabase]: иначе TOML включит их в этот раздел, а приложение не получит ожидаемые переменные окружения. Укажите реальные разрешённые email, настройте подтверждение почты и адрес сайта в Supabase. Нужен publishable/anon key, не service_role, secret key или пароль PostgreSQL. Локальные SQLite-аккаунты автоматически не переносятся. Подробности: docs/AUTH.md.

ИИ необязателен: при пустом ключе и флаге "false" основной анализ работает. Для разрешённых к внешней передаче данных задайте ключ и QADAM_ALLOW_EXTERNAL_AI = "true". Облачные Secrets настраиваются отдельно от локального .env. Управление секретами в Streamlit.

4. Первый запуск и обновление

  1. Откройте выданную ссылку *.streamlit.app, войдите разрешённым аккаунтом с подтверждённым email.
  2. Если результатов ещё нет, нажмите «Запустить анализ». При наличии трёх Parquet приложение создаст output/ и покажет обзор.
  3. На исходной выборке проверьте 2 248 клиентов, 3 119 связей и 4 840 транзакций; найдите произвольный GID и скачайте три обязательных CSV. Отдельно убедитесь, что посторонний аккаунт не получает доступ.
  4. Для обновления отправляйте изменения именно в репозиторий и ветку, подключённые к Cloud. Пуш в основной командный репозиторий не обновляет зеркало автоматически. Если после обновления остаётся ошибка старого импорта, выполните Reboot app в управлении Cloud; при старых стилях обновите страницу с очисткой кэша.
СимптомЧто проверить
ModuleNotFoundErrorРасположение requirements.txt, версию Python и журнал установки
Нет исходных данных / расчёт не запускаетсяВсе три Parquet в data/ выбранного проекта; .gitignore исключает их из обычного пуша
Нет результатов анализаВойти и выполнить «Запустить анализ»; Streamlit сам не запускает run.py до старта приложения
Отказ во входеURL/key Supabase, подтверждение email и allowed_emails
Кнопка ИИ отключенаКорневые Secrets, непустой ключ и строковый флаг "true"
Сайт показывает старую версиюПодключённый репозиторий, ветка, актуальность зеркала и перезапуск процесса

Результаты на данных организаторов

Это результаты предоставленной выборки и базовой конфигурации, а не константы алгоритма или размеченная истина. Источник чисел — сформированные output/run_summary.json, output/analysis_report.md и диагностические отчёты.

ПоказательРезультат
Клиенты / направленные связи / транзакции2 248 / 3 119 / 4 840
Исходные seed-клиенты81
Наблюдаемый оборот365 890 012,01 KZT
Кластеры / кластеры с несколькими seed67 / 9
Изолированные узлы, сохранённые в результате19
Граничные узлы без выхода / из них названо terminal444 / 0
Обязательные выгрузки2 248 строк ролей, 67 кластеров, top-50

В сохранённом локальном запуске базовый pipeline со стресс-тестом выполнился за 15,291 с; запуск с дополнительной проверкой устойчивости рейтинга — за 77,928 с. Оба результата укладываются в ограничение кейса 300 секунд. Это измерения на Windows с Python 3.12, не гарантия времени на любом устройстве; проверка устойчивости выполнялась параллельно с тестами. Для своего запуска сверяйте runtime_seconds в соответствующем run_summary.json.

Распределение рассчитанных ролей:

РольУзлов
consolidator65
transit117
distributor144
terminal296
coordinator26
peripheral1 600

Число кластеров не подгоняется под ориентир 8 сообществ из brief: используются логарифмические веса, учитываются маленькие компоненты и изоляты. В графе 16 компонент со связями и 35 с учётом изолированных узлов; крупнейшая содержит 1 877 клиентов, вторая — 270. Это разные способы подсчёта, а не потерянные клиенты.

Подробный автоматически обновляемый отчёт: output/analysis_report.md — top-20, объяснения top-5, примеры всех ролей и ограничения. Это результат правил, а не ground truth.

Архитектура

Стек: Python, pandas, NumPy, PyArrow, NetworkX, SciPy; Streamlit с HTML/CSS/JavaScript-интерфейсом. Расчёт выполняется на сервере, браузер отображает результаты. Платные API не участвуют в назначении ролей и формировании обязательных CSV.

flowchart LR
    A["Данные: 3 Parquet"] --> B["Валидация и направленный граф"]
    B --> C["Метрики, сообщества, FIFO 24/48 ч"]
    C --> D["Роли, приоритет и обоснование"]
    D --> E["Контроль качества и CSV"]
    E --> F["Интерфейс аналитика"]
    F --> G["ИИ-пояснение по запросу"]
    D --> H["Стресс-тест и устойчивость топ-20"]
ФайлОтветственность
src/load_data.pyТипы, обязательные поля, сверка edges с transactions
src/graph_builder.pyНаправленный граф, устойчивые ID сообществ
src/features.pyФинансовые, графовые, временные признаки
src/scoring.pyРоли, eligibility, confidence, приоритет и тексты
src/pipeline.pyЕдиный запуск, экспорты, отчёты, explain_node
src/workspace.pyЗапросы UI, фильтры, срезы графа, карточки и ИИ-пояснения
src/resilience.pyУдаление top-N со случайным baseline и проверка устойчивости рейтинга
src/auth.pyЛокальные аккаунты SQLite и опциональная Supabase Auth
app/app.pyStreamlit: сессии, события интерфейса и вызовы аналитики
app/frontend/HTML/CSS/JavaScript: экраны, карта и интерактивность
config/scoring.yamlВеса, пороги, seed, параметры кластеризации
starter/Неизменённый starter организаторов

Базовые степени, суммы и количества операций сверяются тестом с starter/basic_features. От него решение расширено сохранением изолятов, валидацией сумм/количеств, временными признаками и объяснимым скорингом.

Аналитические вычисления принадлежат src/; интерфейс использует те же результаты и explain_node, не дублируя формулы. Идентификаторы передаются в браузер строками: числовой тип JavaScript не сохраняет точность длинных int64. Параметры модели находятся в config/scoring.yaml.

Входы, валидация и EDA

Выборка организаторов: июль 2026 года, исходящие внутрибанковские переводы на четыре перехода от 81 seed; операции ниже 5 000 KZT исключены. Qadam не расширяет выборку внешними данными и не придумывает атрибуты клиентов. В памяти строится граф строго из предоставленных узлов и рёбер. Схема исходных файлов: data/README.md.

ФайлОбязательные поля
nodes.parquetgid (int64), depth (0–4), is_seed (bool)
edges.parquetsrc, dst (int64), sum_kzt, n_tx, depth
transactions.parquetsrc, dst (int64), date, sum_kzt

Некорректные ID, null, infinity, отрицательные/нулевые суммы, нулевые количества, неизвестные endpoints и неразбираемые даты приводят к ошибке. false не превращается в True. Пары, суммы (допуск 0,01 KZT) и количества транзакций сверяются с edges. Дублирующиеся рёбра агрегируются с предупреждением; исходные parquet не изменяются.

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

eda_report.json содержит схемы, типы, null, распределение depth/seed, размеры компонент и квантили признаков. На реальных данных:

ПризнакМедианаP95Максимум
Число плательщиков1324
Число получателей05116
Входящий поток, KZT50 000613 7794 500 000
Исходящий поток, KZT0747 120,9023 001 375
Сумма отдельного перевода, KZT30 000см. EDA3 000 000

Порог ≥3 контрагентов отделяет выраженную структуру от обычной пары. Масштаб насыщения 8 соответствует сильному fan-in и fan-out, но не фиксирует число назначенных ролей. Это экспертные начальные параметры, обоснованные EDA, без обучения на метках.

Признаки

Суммы, средние, медианы и максимумы считаются по отдельным транзакциям. Степени и уникальные контрагенты — по направленным связям без self-loop. Самопереводы сохраняются в денежных итогах, но не создают искусственных контрагентов.

observed_net = sum_in - sum_out — разность в выборке, не баланс счёта. При нулевом входе flow_through_ratio не определён (NaN), а не равен нулю.

PageRank взвешен по сумме. Betweenness использует направленные невзвешенные кратчайшие пути: денежная сумма не трактуется как длина пути. До 1 000 узлов расчёт точный, выше — 256 источников, seed=42. Точность оценки centrality ограничена выборкой.

seed_reach — число seed, из которых узел достижим по направлению денег; это не просто число соседей-seed. Рассчитываются минимальная дистанция, participation coefficient по сообществам соседей, articulation flag, two-hop reach, входящие и исходящие сообщества.

Нормализация P(x): нулю/неопределённому значению соответствует 0; положительные значения получают средний percentile rank среди положительных. Поэтому у изолятов нулевые признаки не становятся «средними». Percentile rank устойчив к выбросам; предварительный log1p не меняет порядок и здесь не нужен.

Формальные критерии ролей

Ниже — действующие правила базового конфига. consolidator — кандидат в сборщики, distributor — распределители, transit — транзитные узлы, terminal — наблюдаемое удержание внутри границы, coordinator — структурные связующие узлы. peripheral означает недостаток специализированных признаков, а не отсутствие риска.

Обозначения: V=P(sum_in+sum_out), I=P(sum_in), O=P(sum_out), PR=P(PageRank), B=P(betweenness).

FI=P(in_degree)×min(in_degree/8,1); FO=P(out_degree)×min(out_degree/8,1). R=clip(1−out/in,0,1); для seed его вклад умножается на 0,25. U=P(число входящих сообществ). bridge=0,7×participation + 0,3×articulation_flag. participation=1−Σ(доля соседей в сообществе)².

РольУсловия допускаСырой score
consolidator≥3 плательщика, положительный вход0,40 FI + 0,25 I + 0,15 PR + 0,10 R + 0,10 U
distributor≥3 получателя, положительный выход0,45 FO + 0,25 O + 0,15 P(n_out_tx) + 0,15 out_degree/total_degree
transitВход и выход; для non-seed out/in ∈ [0,5; 1,5]; для seed FIFO48 ≥0,80,35 similarity + 0,30 FIFO48 + 0,15 V + 0,10 middle + 0,10 B
terminalНе seed, depth<4, ≥2 входящих перевода, retention≥0,8(0,35 I + 0,40 R + 0,25 P(n_in_tx)) × (1−0,5 FI)
coordinator≥3 связей, betweenness>0 и его положительный percentile≥0,8; ≥2 соседних сообщества или articulation0,40 B + 0,25 bridge + 0,15 P(seed_reach) + 0,10 PR + 0,10 V
peripheralДоступна всегда1−max(пяти специализированных scores)

middle=1 при наличии входа и выхода. similarity=clip(1−max(|out/in−1|−0,2;0)/0,8;0;1); для seed вклад умножен на 0,25. Недопущенная роль имеет score=0. Побеждает argmax шести scores; равенства разрешаются фиксированным порядком ролей из кода. Скидка terminal при большом fan-in помогает разделить накопление от множества плательщиков и обычное наблюдаемое удержание.

Большая доля peripheral вызывает предупреждение, но не принудительное перераспределение ролей. У многих клиентов действительно наблюдаются только 1–2 связи.

role_score — эвристическая обоснованность, не калиброванная вероятность. Пусть top — лучший score, margin — разница с вторым:

role_score = (0,6×top + 0,4×min(margin/0,25;1)) × Q.

Качество наблюдения Q начинается с 1 и умножается на 0,75 для depth=4, на 0,8 для seed, на 0,8 при <3 наблюдаемых операциях. Понижение уверенности не стирает реальные признаки fan-in/fan-out на границе.

Приоритет и доказательность

priority_score = Q × (0,25 structural + 0,20 financial + 0,20 seed_relevance + 0,20 role_importance + 0,10 anomaly + 0,05 temporal).

  • structural = (P(betweenness) + P(PageRank) + participation)/3.
  • financial = P(sum_in + sum_out).
  • seed_relevance = 0,6 P(seed_reach) + 0,4/(1+minimum_distance); недостижимые узлы получают 0 по дистанции.
  • role_importance = вес роли × role_score. Веса: coordinator 1; consolidator 0,9; distributor 0,85; transit 0,7; terminal 0,5; peripheral 0,1.
  • anomaly = (P(in_degree)+P(out_degree))/2. Это вспомогательная структурная необычность, не ML-модель и не аномальный баланс.
  • temporal = FIFO48.

Для узлов без внешних связей priority=0. Это отсутствие структурных доказательств в выборке, не утверждение об отсутствии риска. Веса составляют 1, компоненты ограничены [0,1]. Приоритет не перенормируется после расчёта, чтобы сумма вкладов оставалась проверяемой.

Каждый вклад сохраняется в node_features.csv. top_nodes.why содержит три максимальных слагаемых и реальные показатели. nodes_roles.evidence содержит описание роли с числами, не более 200 символов. Ограничения seed/depth сохраняются даже при сокращении текста.

from src.pipeline import explain_node
card = explain_node(100000003115284100)

Карточка содержит metrics, role_factors, priority_factors, limitations, evidence, priority_why. GID здесь — демонстрационный пример из результатов, не правило классификации. Любой входной GID обрабатывается одинаково.

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

Louvain работает на неориентированной проекции: сначала суммируются встречные направления, затем используется log1p(сумма). Так крупная единичная связь не полностью подавляет структуру связей меньшего объёма. Направление сохраняется в основном графе, признаках и UI.

Random seed=42, resolution=1; ID кластеров упорядочены по убыванию размера, затем по минимальному GID. Изолированный узел получает отдельный кластер. Внутренний оборот считается по оригинальным направленным рёбрам ровно один раз, без удвоения от проекции.

Временной анализ

FIFO раздельно для окон 24 и 48 часов сопоставляет входящие суммы с более поздними исходящими. Вход может быть частично сопоставлен с несколькими выходами, но одна исходящая сумма не расходуется дважды. Просроченные входы исключаются.

В данных есть только даты, а не время. Все события одного дня имеют одинаковый timestamp и не сопоставляются между собой. Интервалы 24/48 часов — приближение на основе календарных дат, не утверждение о точной задержке. Включение timezone UTC техническое, исходный часовой пояс неизвестен.

В общем случае это наблюдаемый паттерн, не трассировка тех же банкнот: внешние входы/выходы отсутствуют. 97 одинаковых строк не удаляются без идентификатора транзакции.

Экспорты

Все файлы создаются в output/. Первые три — обязательный результат кейса; остальные позволяют проверить расчёт и восстановить его настройки.

ФайлСхема / назначение
nodes_roles.csvgid, role, role_score, cluster_id, priority_score, evidence; все 2 248 клиентов исходной выборки
clusters.csvcluster_id, n_nodes, n_seed, sum_kzt_internal, top_gids, hypothesis
top_nodes.csvrank, gid, role, priority_score, why; top-50
node_features.csvВсе метрики, роли, eligibility и вклады
graph_edges.csvИсходные агрегированные связи для карты
eda_report.jsonКвантили, схемы и распределения
run_summary.jsonИтоги, конфиг, SHA256 входов, предупреждения и время
analysis_report.mdИтоговый разбор результатов для команды
resilience.csvСценарное удаление 1/3/5/10/20 узлов

Проверка перед экспортом контролирует точный набор GID, уникальность, схемы, диапазоны, длину evidence, размеры/seed кластеров, последовательность и сортировку top-list. Для маленьких тестовых графов выводятся все доступные узлы вместо выдумывания 20 клиентов.

role_score и priority_score находятся в [0, 1]; evidence непустое и не превышает 200 символов. В базовой конфигурации выгружается top-50, что покрывает требование минимум 20 узлов. Длинные GID импортируйте в табличные редакторы как текст, чтобы не потерять разряды.

Стресс-тест сети

Удаление top-N по приоритету сравнивается с 30 случайными выборками с фиксированным seed. Это виртуальный расчёт на слабой связности; он не изменяет данные и не рекомендует блокировку счетов.

При N=20 крупнейшая компонента содержит 1 445 оставшихся узлов против в среднем 1 844,23 при случайном удалении: около 64,9% против 82,8% оставшихся узлов. Удаление top-20 увеличивает число компонент на 276. Структурный эффект не доказывает преступную роль. Приоритет не оптимизировался под эту метрику.

Устойчивость рейтинга к настройкам

На исходной выборке в каждом из 14 изменённых сценариев сохраняются 18–20 узлов базового топ-20. 15 из 20 остаются в топе во всех сценариях, первые два сохраняют места 1 и 2. По отдельным группам во всех сценариях остаются 16 узлов при изменении весов и 17 при изменении разрешения кластеризации. Это чувствительность к выбранным параметрам, а не оценка точности.

Это отдельная проверка, не удаление узлов из стресс-теста выше. Из корня проекта выполните:

.\.venv\Scripts\python.exe run.py --check-stability --out output/stability

Команда повторяет базовый pipeline в отдельном каталоге и добавляет проверку топ-20. Текущие файлы в output/, которые читает интерфейс, не затрагиваются. Обычный python run.py не запускает эту дополнительную проверку. Базовые роли, веса и три обязательных CSV не меняются ради устойчивого результата.

Проверяются 14 вариантов плюс базовый сценарий:

  • 12 вариантов весов: каждый из шести весов priority по очереди умножается на 0.9 и 1.1, затем все шесть весов делятся на их новую сумму. Это относительное изменение на ±10% до нормализации, не ±10 процентных пунктов. Сообщества и роли фиксированы.
  • 2 варианта кластеризации: community_resolution умножается на 0.8 и 1.2 (при текущем конфиге получаются 0.8 и 1.2). Заново рассчитываются сообщества, все признаки, роли и приоритет. Веса и random_seed остаются базовыми.

Группы оцениваются раздельно, чтобы двенадцать проверок весов не скрывали результат двух проверок кластеризации. Для графа меньше 20 узлов используется min(20, n_nodes). При равенстве score сортировка, как в основном pipeline, идёт по GID по возрастанию; равенство на границе топа отмечается отдельно.

Дополнительные файлы в output/stability/:

ФайлСодержимое
ranking_stability_report.mdЧитаемый отчёт: совпадение топа, изменения мест и ролей, конфигурация, SHA256 входов и ограничения
ranking_stability_scenarios.csvОдна строка на сценарий: scenario, kind, parameter, multiplier, фактические weight__* и community_resolution, размеры, overlap_count/fraction, entered_gids/left_gids, сдвиги мест базового топа, число смен ролей и равенства на границе
ranking_stability_nodes.csvПо строке на каждый GID в каждой из двух групп: базовое место/роль/score, in_baseline_top, scenario_count, top_n_hits/fraction, best_rank, worst_rank, max_abs_rank_shift, role_change_count

Базовый сценарий не входит в знаменатель частоты, диапазоны мест и число смен роли. best_rank/worst_rank — места среди всех узлов, а не только внутри топ-20. top_n_fraction — доля проверенных вариантов, не вероятность и не точность. Файлы содержат и узлы, вошедшие в топ только после изменения настроек. Числовые идентификаторы нужно читать как int64 или строки, в браузер передавать строками.

Расчёт находится в src/resilience.py::assess_ranking_stability; он принимает валидированные InputData, полный граф, исходные признаки из calculate_features и конфиг. Возвращает две таблицы без изменения аргументов. Экспорт и флаг командной строки находятся в src/pipeline.py. Новый экран и действия UI не добавлены. run_summary.json содержит раздел ranking_stability только для запуска с флагом; не используйте старый диагностический отчёт как результат нового запуска без сверки конфигурации и хешей.

Проверка не подбирает «лучшие» веса и не доказывает правильность ролей. Не исследуются совместные изменения нескольких параметров, пороги ролей, другие random seed и неполнота транзакционной выборки. Даже неизменный топ может быть устойчиво ошибочным; при нулевых или равных score стабильный порядок GID сам по себе ничего не доказывает.

Проверки и воспроизводимость

Из корня проекта, после установки зависимостей и расчёта:

$env:MONEY_GRAPH_AUTH='local'
$env:QADAM_ALLOW_EXTERNAL_AI='false'
.\.venv\Scripts\python.exe -m pytest -q -rs
.\.venv\Scripts\python.exe -m pip check

Для Linux/macOS используйте .venv/bin/python и export, как в быстром запуске. Успех — отсутствие упавших тестов, объяснённые пропуски и сообщение No broken requirements found у pip check.

Зафиксированный полный локальный прогон 23.09.2026 для версии fd5c37b: 75 passed, 1 skipped. Пропущен tests/test_browser.py, поскольку не задан MONEY_GRAPH_TEST_URL. Это результат конкретного прогона, а не непрерывная проверка всех последующих коммитов или облачного сайта.

ОбластьПроверки
Входы и метрикиtests/test_loading.py, tests/test_features.py: типы, суммы, изоляты, направления и временное FIFO
Роли и приоритетtests/test_scoring.py: синтетические структуры, depth=4, seed bias, сумма вкладов и детерминизм
Полный расчётtests/test_pipeline.py: Parquet → CSV, точность int64, схемы, сверка со starter и неизменность базовых экспортов при диагностике
Устойчивостьtests/test_resilience.py: воспроизводимость, независимый пересчёт весов, пересчёт кластеров и ролей, равенства и смена мест
Интерфейс и доступtests/test_workspace.py, tests/test_app.py, tests/test_auth.py, tests/test_supabase_auth.py: запросы, сессии, аккаунты и контролируемые ошибки

Тест реальных данных пропускается, если Parquet организаторов отсутствуют. Живой браузерный сценарий требует отдельного тестового сервера, MONEY_GRAPH_TEST_URL, Playwright и Chrome. Он создаёт тестовый аккаунт и запускает расчёт: не направляйте его на публичное демо или рабочую базу аккаунтов. Тесты внешних API с подменёнными ответами не подтверждают работу настоящих ключей, Supabase или OAuth; сборка Docker и облачный вход также требуют отдельной проверки.

При смене Python/NetworkX сравните распределения и top-list; lock-файл фиксирует проверенную среду, входные хеши сохраняются в отчёте. Автообновление Streamlit не всегда сбрасывает импортированные модули: после изменения Python-модулей остановите сервер и запустите заново.

Ограничения и масштабирование

  • Глубина 4 скрывает исходящие операции: все 444 потенциальных ложных стока рассматриваются с ограничением, не как доказанные terminal.
  • Входящие извне не видны; особенно ненадёжен баланс seed. Соотношение выхода/входа >1 само по себе не считается подозрением.
  • Внутрибанковские операции и порог 5 000 KZT оставляют слепые зоны; мелкие и межбанковские переводы не исследуются.
  • Отсутствуют клиентские атрибуты, внешнее обогащение, ground truth. Формулы требуют проверки аналитиком; нельзя заявлять precision/recall или «точность 95%».
  • Кластеризация и приближённая betweenness зависят от конфигурации. Boundary/seed поправки — экспертные, не обученные вероятности.
  • Изоляты сохраняются. Низкий score не заменяет проверку по другим источникам, которых здесь нет.

При миллионе узлов NetworkX и интерактивный полный layout становятся узкими местами. Перенести агрегации в Polars/DuckDB, граф в igraph/graph-tool или sparse CSR, использовать Leiden и выборочную centrality; считать локальные признаки по партициям, не выполнять all-pairs/reach для каждого seed. UI отдавать подграфы/агрегированные сообщества, а не миллион узлов. Миллионная версия здесь не реализована и её производительность не заявляется.

Автоматический поиск повторяющихся цепочек и циклов, рабочие дела аналитика с заметками и статусами, отдельная временная шкала и black-box модель аномалий не входят в текущую версию. Наличие этих идей в обсуждении команды не означает, что они реализованы. Интервалы FIFO и проверка устойчивости уже рассчитываются, но отдельного экрана устойчивости рейтинга нет.

ИИ-пояснение

Кнопка «Объяснить узел» добавлена в карточку клиента и окно «Полное обоснование» существующего HTML/CSS/JS-интерфейса. Браузер передаёт серверу только событие {id, action: "explain", gid}. После проверки сессии сервер сам выбирает узел из рассчитанных результатов. Присланные браузером роль, score или текст не используются.

Сервер отправляет OpenAI компактную сводку: строковый GID, роль, scores, кластер, число отправителей/получателей, суммы, PageRank, центральность, вклады факторов и ограничения. Связи ограничены пятью крупнейшими входящими и пятью исходящими агрегированными связями с их GID; число пропущенных указано. Сырые транзакции и весь граф в API не передаются, но сводка всё равно содержит финансовые данные и требует разрешения на внешнюю передачу.

Ответ имеет поля summary, reasons, limitations по Structured Outputs. Он служит только пояснением: роли, баллы, CSV и очередь проверок не меняются. Текст модели показывается как проверяемая гипотеза и требует сверки с исходными метриками.

Подключение

Скопируйте .env.example в локальный .env рядом с run.py и заполните его на своём компьютере:

OPENAI_API_KEY=ваш_ключ
OPENAI_MODEL=gpt-4o-mini
QADAM_ALLOW_EXTERNAL_AI=false

gpt-4o-mini — значение по умолчанию в коде; модель задаётся через OPENAI_MODEL. Включайте QADAM_ALLOW_EXTERNAL_AI=true только для синтетических или разрешённых к передаче данных. .env исключён из Git, шаблон без ключа — .env.example. Сервер читает файл через python-dotenv; переменные окружения имеют приоритет. Если в быстром запуске установили флаг false в терминале, измените его там же перед перезапуском сервера. Ключ не попадает в HTML, JavaScript и аргументы компонента.

Используйте то же окружение и команду запуска интерфейса из раздела «Быстрый запуск»; повторная установка зависимостей не нужна. Настройка ИИ на локальном компьютере не настраивает автоматически облачный сайт.

После входа выберите GID на графе или через поиск и нажмите «Объяснить узел». Без настройки API кнопка отключена. При ошибке остаются исходная карточка и полное обоснование; повтор можно запустить вручную. Запрос не повторяется при перерисовке страницы. Ответ другого GID не отображается в текущей карточке; новый расчёт и выход очищают пояснение.

В текущем коде тайм-аут API — 25 секунд, автоматических повторов нет, максимум 1200 выходных токенов. Запрос содержит store=false; это не обещание отсутствия журналирования у провайдера. Реализация и проверка структуры ответа находятся в src/workspace.py.

Docker Compose передаёт эти три настройки в контейнер через окружение; .env не включается в образ. По умолчанию внешнее ИИ-пояснение отключено. Настройка размещения: docs/DEPLOY.md.

Команда и документация

Junior Syndicate

УчастникНаправление работы
Тұрар НұрбаұлыАналитическое ядро, интеграция и проверка устойчивости рейтинга
Алмас ӘлханИИ-пояснения и аналитические проверки
Алишер АхметИнтерфейс, дизайн, авторизация и развёртывание сайта

Секреты, локальные аккаунты и исходные данные не включайте в коммиты. Перед обновлением зеркала сверяйте его версию с основным репозиторием: публикация README в одном репозитории сама по себе не обновляет другой или запущенный сайт.

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

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

turarnurbauli 9AlmasAlkhan 3Alisher 3AlisherAt 1

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

  • 2026-09-23 17:58fix: validate Google OAuth configurationAlmasAlkhan
  • 2026-09-23 17:58docs: add Streamlit Community Cloud deployment guideturarnurbauli
  • 2026-09-23 17:54docs: present Qadam demo, methodology and reproducible setupturarnurbauli
  • 2026-09-23 17:43fix: restore scrolling in embedded analyst workspaceturarnurbauli
  • 2026-09-23 17:22feat: audit top-20 ranking stability across parameter changesturarnurbauli
  • 2026-09-23 16:51Merge Qadam AI explanations with Supabase authenticationturarnurbauli
  • 2026-09-23 16:28feat: add Qadam AI node explanationsAlmasAlkhan
  • 2026-09-23 15:18Merge reviewed analyst workspaceturarnurbauli
Все коммиты (16)
  • 2026-09-23 15:18Merge reviewed analytics tests and auditturarnurbauli
  • 2026-09-23 15:07test: validate graph features and role scoringAlmasAlkhan
  • 2026-09-23 15:07Merge verified core baseline into mainturarnurbauli
  • 2026-09-23 15:00feat: add explainable money graph pipelineturarnurbauli
  • 2026-09-23 16:27feat: add Supabase authentication and Docker deployment setupAlisher
  • 2026-09-23 16:02feat: redesign analyst workspace with interactive graph and authenticationAlisher
  • 2026-09-23 15:02feat: add analyst workspace and demo flowAlisher
  • 2026-09-23 14:56Add files via uploadAlisherAt
Структура репозитория (50 файлов)
.dockerignore
.env.example
.gitignore
.streamlit/.gitignore
.streamlit/config.toml
.streamlit/secrets.example.toml
AGENTS.md
AUDIT.md
DEMO.md
Dockerfile
README.md
app.py
app/app.py
app/frontend/app.js
app/frontend/index.html
app/frontend/styles.css
compose.yaml
config.toml
config/scoring.yaml
data/README.md
docs/AUDIT.md
docs/AUTH.md
docs/DEMO.md
docs/DEPLOY.md
requirements-lock.txt
requirements.txt
run.py
src/__init__.py
src/auth.py
src/features.py
src/graph_builder.py
src/load_data.py
src/pipeline.py
src/resilience.py
src/scoring.py
src/workspace.py
starter/README.md
starter/requirements.txt
starter/starter.py
tests/conftest.py
tests/test_app.py
tests/test_auth.py
tests/test_browser.py
tests/test_features.py
tests/test_loading.py
tests/test_pipeline.py
tests/test_resilience.py
tests/test_scoring.py
tests/test_supabase_auth.py
tests/test_workspace.py
Ветки (6)

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

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

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