Перейти к содержанию

Эта · Бизнес — умный кредитный кабинет

Прототип кредитного конвейера для малого и микробизнеса, сделанный на хакатоне Альфа-Банка (кейс — misc/Альфа_Хак_Умный_кредитный_кабинет.pdf). Клиент — ИП или ООО, уже обслуживается в банке: он вводит ИНН, даёт согласия, отвечает на 3–6 вопросов о потребности и через несколько секунд получает набор предложений, отказ с объяснением или запрос выписки. Затем выбирает предложение, подписывает документы кодом и получает деньги (мок). Сотрудник банка видит досье заявки и решает то, что конвейер не решает сам.

Решение принимает детерминированный движок по данным из источников, а не по словам клиента: восемь мок-источников (ЕГРЮЛ/ЕГРИП, ФНС, БКИ, ФССП, арбитраж, 115-ФЗ, счета и карточка клиента), уровень качества данных по счёту, стоп-факторы и флаги, скоринговая карта, обученная на синтетическом мире, лимит по денежному потоку, ставка по составляющим риска и матрица полномочий. Языковая модель — необязательная надстройка.

Документация

Документ Что в нём
Онлайн-документация Адрес публичной версии справочника после настройки DNS и деплоя
docs/PROCESS.md Модель процесса: роли, этапы, схема статусов, почему этап есть, упрощён или отсутствует
docs/DATA_MODEL.md Таблицы, файлы, воспроизводимость решения
docs/SLA.md Сроки и критичность этапов, как они обеспечиваются
docs/SIMPLIFICATIONS.md Реестр упрощений и моков
ARCHITECTURE.md Модули, поток решения, ИИ, безопасность
docs/v2/SPEC.md Кредитная логика и её обоснование
docs/v2/CONTRACTS.md Форматы данных между модулями
apps/world/PERSONAS.md Демо-персоны и их ожидаемые исходы
VPS.md Развёртывание на сервере

Подробности по компонентам — в README и *.md рядом с кодом: apps/world/, apps/sources/, apps/api/features/, apps/ml/, apps/api/*.md, apps/web/.

Запуск в Docker

Нужен только Docker с Compose v2 (Docker Desktop на Windows и macOS). Из корня репозитория:

cp .env.example .env
docker compose up --build

На Windows в PowerShell первая команда — Copy-Item .env.example .env. После старта откройте http://127.0.0.1:8000/ (рабочее место сотрудника — /staff, OpenAPI — /docs). Перед показом за пределами своей машины замените в .env значения STAFF_PASSWORD (от 12 символов, это пароль входа сотрудника) и POSTGRES_PASSWORD (буквы, цифры, дефисы); scripts/init_env.py создаёт .env со случайными паролями сам.

Compose поднимает PostgreSQL, API и отдельный worker, который принимает решения по заявкам, проверяет условия выдачи и раз в сутки считает предодобрения. Порт привязан к 127.0.0.1 и наружу не публикуется. Оба сервиса приложения читают .env целиком; DATABASE_URL, APP_ORIGIN, WORKER_ENABLED и DATA_DIR для контейнеров задаёт compose.yaml.

  • Синтетический мир (5000 компаний, зерно 42) записывается в apps/world/out при сборке образа, поэтому первая заявка не ждёт его генерации. Демо-персоны подобраны из этого мира (apps/world/PERSONAS.md), поэтому WORLD_SEED, WORLD_N и WORLD_AS_OF в Docker не действуют: мир читается из образа. Другой мир строится, только если WORLD_DIR указывает на пустую папку; демо-персоны для него придётся подобрать заново (python -m apps.world.personas).
  • Моки источников работают с задержками по умолчанию: решение по заявке занимает несколько секунд. MOCK_SOURCES_LATENCY_SCALE=0 убирает задержки. В .env.example для персоны source_down (ИНН 2309002984) задан отказ БКИ (MOCK_SOURCES_SCENARIOS, # MOCK:); пустое значение отключает сценарий.
  • Журнал — docker compose logs -f api worker. Уровень задаёт LOG_LEVEL (по умолчанию INFO): в журнале видны, например, оценки основной и теневой скоринговых карт.
  • Данные PostgreSQL лежат в томе postgres-data; docker compose down -v удаляет их вместе с заявками.

Сборка и сквозной сценарий из docs/api/requests.http проверены в Docker Desktop (Compose v5) с чистым .env из .env.example; в простое стек занимает около 1,5 ГБ ОЗУ. Вход внутри реального Telegram подготовлен в конфигурации, но без токена бота не проверялся.

Запуск без Docker

Нужны Python 3.12+, Node.js 22+ и npm. Без DATABASE_URL используется SQLite в data/alfa.db; worker работает внутри процесса API. Синтетический мир строится при первом обращении к нему и сохраняется в apps/world/out; можно построить заранее: python -m apps.world.

Windows (PowerShell):

.\setup.ps1
.\start.ps1

setup.ps1 ставит зависимости, собирает клиент и создаёт .env со случайным STAFF_PASSWORD; start.ps1 запускает сервер. Если политика PowerShell запрещает скрипты, выполните команды из setup.ps1 вручную.

macOS и Linux:

python3 -m venv .venv
.venv/bin/pip install -r requirements-lock.txt
npm ci
npm run build
.venv/bin/python scripts/init_env.py
.venv/bin/python run.py

run.py читает .env, выбирает свободный порт в диапазоне 8000–8020 и печатает ссылки; фиксированный порт задаёт PORT. Для разработки клиента: npm run dev вместе с run.py, Vite проксирует /api на порт 8000.

Демо-учётки

  • Клиент. Экран входа открывается сам: введите любой e-mail, пароль и код не нужны. Один и тот же e-mail открывает тот же профиль с историей заявок. ИНН компании клиент вводит сам, как в настоящем кабинете; подходят ИНН демо-персон из apps/world/PERSONAS.md, другой ИНН кабинет считает не привязанным к профилю.
  • Сотрудник. «Для сотрудников» (или /staff), пароль — STAFF_PASSWORD из .env, при входе выбирается должность: андеррайтер или риск-менеджер. Общего фиксированного пароля нет.

Клиент и сотрудник — разные cookie-сессии одного сайта. Открывайте рабочее место в отдельном профиле браузера или приватном окне, иначе вход сотрудника заменит сессию клиента (кабинет клиента тогда снова попросит e-mail). Для комитета второй сотрудник входит ещё в одном окне с другой должностью.

Сценарий проверки

Все компании и их ожидаемые исходы — в apps/world/PERSONAS.md. Решение по каждой персоне проверяют тесты (tests/test_golden_personas.py), поэтому исход ниже совпадает с тем, что увидит проверяющий.

1. Автоматическое одобрение и выдача

  1. Войдите клиентом по любому e-mail, нажмите «Подать заявку».
  2. Введите ИНН 6163001918 (ООО, персона auto_ooo), отметьте три согласия и нажмите «Данные верны, продолжить».
  3. Потребность: «Закупить товар или сырьё», 3 450 000 ₽, 12 месяцев.
  4. Экран анализа показывает опрос каждого источника, затем решение: предложение на запрошенные условия, автоматическое решение.
  5. Выберите предложение, прочитайте документы, запросите код (он показывается на экране вместо SMS) и подпишите.
  6. Через пару секунд отлагательные условия выполнены, статус — «Финансирование выдано».

Так же автоматически решаются auto_ip (ИП, ИНН 165500071419, 950 000 ₽ на 12 мес.) и equipment (ИНН 7715000717: «Купить оборудование», 1 610 000 ₽ на 36 мес., оборудование за 2 300 000 ₽, взнос 690 000 ₽, ИНН поставщика 7715999986; выдача — оплатой поставщику). У auto_ooo на главной виден предодобренный овердрафт до 2 820 000 ₽. Его считает фоновый пакет по всем клиентам: первый раз сразу после запуска worker, затем раз в сутки; пока пакет не закончился, баннера нет. Сотрудник может запустить пакет заново: POST /api/v2/staff/preapproval/run.

2. Набор предложений вместо запрошенного

ИНН 631700005555 (персона counter_offer), 3 000 000 ₽ на 12 месяцев: денежного потока хватает только на 670 000 ₽. Клиент видит, какое ограничение определило лимит, и контрфактические подсказки.

3. Андеррайтер

  1. Клиентом подайте ИНН 770300000904 (персона underwriter): «Купить оборудование», 560 000 ₽ на 36 месяцев, оборудование за 800 000 ₽, взнос 240 000 ₽, ИНН поставщика 7703999981. Заявка уходит на рассмотрение.
  2. В отдельном окне войдите андеррайтером. В очереди откройте досье: источники, факты «заявлено / подтверждено», качество данных, флаги, вклад признаков в балл, расчёт лимита и ставки, почему заявка пришла сюда, журнал.
  3. Одобрите предложение системы с кодом причины и комментарием. У клиента появится предложение — дальше как в сценарии 1.

4. Нехватка данных и выписка

ИНН 6670019632 (персона thin_statement, недавно открытый счёт), 1 150 000 ₽ на 12 месяцев: уровень данных THIN, конвейер просит выписку из другого банка. Загрузите файл apps/world/demo/statements/thin_statement.json — заявка пересчитывается и уходит андеррайтеру с предложением 450 000 ₽. Персона partial_statement (ИНН 2312030413, 3 000 000 ₽) — основной счёт в другом банке, выписку запрашивает андеррайтер («Запросить документы»).

5. Отказы и сбои

Персона ИНН, заявка Что видно
stop_factor 772700014292, 650 000 ₽ на 12 мес. Отказ до скоринга: просрочка 90+ дней
economic_decline 5074000029, 1 750 000 ₽ на 12 мес. Отказ: лимит по денежному потоку ниже минимума продукта, дата повторного обращения
shell 5074000903, 550 000 ₽ на 12 мес. Андеррайтер: флаги 115-ФЗ, снятие наличных, смена директора
source_down 2309002984, 600 000 ₽ на 12 мес. БКИ не отвечает после трёх попыток — заявка у андеррайтера с причиной «источник недоступен»

6. Риск-менеджер

Войдите риск-менеджером: страница валидации модели (Gini с доверительным интервалом, калибровка по грейдам, PSI) и статистика переопределений сотрудниками.

Что нельзя показать через интерфейс

  • Завышенная выручка (inflater, ИНН 616400324807): флаг расхождения с ФНС срабатывает, только если клиент заявил выручку, а экран потребности её не спрашивает. Через интерфейс заявка одобряется автоматически по данным ФНС; флаг видно, если подать заявку через API с declared_annual_revenue (docs/api/requests.http).
  • Две подписи комитета: персона committee (ИНН 3664000050) попадает на комитет без предложений, поэтому подписывать нечего. Экран подписей готов, но для демонстрации нужна заявка с предложением на маршруте комитета.
  • Неизвестный ИНН (not_found): демо-профиль может подать заявку только за компании демо-персон, поэтому этот исход проверяется тестами через API.

Языковая модель

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

Подходит любой сервер с OpenAI-совместимым API (vLLM, Ollama, llama.cpp, LM Studio) и модель с открытыми весами. Адаптер (apps/api/assistant.py) читает только три переменные .env:

LLM_BASE_URL=http://host.docker.internal:11434/v1
LLM_API_KEY=
LLM_MODEL=qwen2.5:7b
  • LLM_BASE_URL — базовый адрес API, обычно оканчивается на /v1; адаптер сам добавляет /chat/completions. Пустое значение выключает модель.
  • LLM_API_KEY — необязателен; если задан, передаётся как Authorization: Bearer ….
  • LLM_MODEL — имя модели на этом сервере.

Пример с локальной моделью: Qwen2.5-7B-Instruct (открытые веса, Apache 2.0, около 5 ГБ ОЗУ в 4-битной квантизации, работает на CPU). Установите Ollama, выполните ollama pull qwen2.5:7b и задайте значения выше. Из контейнера сервер на хост-машине доступен как host.docker.internal (в compose.yaml это имя настроено и для Linux); при запуске без Docker используйте http://127.0.0.1:11434/v1. Для vLLM: LLM_BASE_URL=http://<хост>:8000/v1, LLM_MODEL=Qwen/Qwen2.5-7B-Instruct.

Модель вызывается без инструментов и не может менять заявки; тайм-аут 30 секунд, ответ до 500 токенов. К модели передаются вопрос пользователя и минимальный справочный контекст, без файлов, реквизитов и полного финансового профиля. Типичные цифровые идентификаторы и email в сообщении маскируются. Ответ с числами, которых нет в контексте, отклоняется. Все авторитетные условия остаются в карточках, рассчитанных движком.

Других моделей в прототипе нет: скоринговая карта — собственные веса в репозитории (apps/ml/scorecard_v1.json, обучение — python -m apps.ml), OCR не используется (выписки загружаются уже разобранными).

Упрощения: тайм-аут и лимит ответа — константы в коде (# SIMPLIFICATION:); живой вызов модели в тестах не выполняется, проверяются формат запроса и откат к шаблону. На странице заявки помощник объясняет её статус, клиентские причины решения v2 и рассчитанные предложения; внутренние оценки и тексты для сотрудников в контекст не передаются.

Telegram

В браузере /mini/ работает как компактная версия того же клиента. Внутри Telegram подключается официальный Web App SDK; сервер проверяет HMAC и возраст данных авторизации (до 5 минут). Для запуска из бота нужны TELEGRAM_BOT_TOKEN, доступный HTTPS-адрес, корректный APP_ORIGIN, COOKIE_SECURE=true и настройка меню бота на <адрес>/mini/.

API

  • Схема OpenAPI: http://127.0.0.1:8000/docs (интерактивно) и docs/api/openapi.json (файл в репозитории). После изменения маршрутов файл обновляет python scripts/export_openapi.py; тест tests/test_deploy.py падает, если файл устарел.
  • Коллекция запросов: docs/api/requests.http — сквозной сценарий по шагам: демо-вход, карточка компании, заявка, предложения, принятие, код подписания, подпись, выдача; затем вход андеррайтера, досье, одобрение и страница риск-менеджера. В начале файла впишите STAFF_PASSWORD из .env. Коллекция написана для VS Code REST Client (расширение humao.rest-client): ссылки на ответы предыдущих запросов (# @name, {{имя.response.body…}}) JetBrains HTTP Client не читает.
  • Сторонние компоненты и их лицензии: THIRD_PARTY_NOTICES.md, создаётся python scripts/third_party_notices.py.

Проверки

.venv/bin/python -m pytest -q
npm run build

На Windows — .\.venv\Scripts\python.exe -m pytest -q и npm.cmd run build. Тесты покрывают генератор мира, моки и оркестратор источников, признаки и качество данных, обучение и валидацию карты, стоп-факторы, скоринг, лимит и ставку, предложения, маршрут, статусы и журнал, действия сотрудников, выписки, подписание и выдачу, предодобрение, золотые исходы каждой персоны через API и worker и сквозной сценарий от заявки до выдачи.

Структура

apps/world/          синтетический мир: компании, обороты, кредитные истории, исходы, демо-персоны
apps/sources/        мок-источники и оркестратор обогащения
apps/api/features/   признаки и качество данных — общие для обучения и движка
apps/ml/             обучение и валидация скоринговой карты
apps/api/            API, статусы и журнал, скоринг, лимит, ставка, предложения, маршрут,
                     действия сотрудников, выписки, подписание и выдача, предодобрение, ИИ-адаптер
apps/web/src/        React/TypeScript: кабинет клиента, mini app и рабочее место сотрудника
config/              кредитная политика с версиями (значения — у экономического блока)
docs/                аналитика, спецификация v2, OpenAPI и коллекция запросов
tests/               тесты и примеры контрактов (tests/fixtures/)
scripts/             служебные скрипты: .env, экспорт OpenAPI, сторонние лицензии
deploy/, compose.vps.yaml, deploy.sh   развёртывание на VPS: Caddy или nginx (VPS.md)
misc/                материалы кейса и рабочие заметки

Границы готовности

Это хакатонный прототип на синтетических данных. Все внешние системы — моки внутри проекта: реестры, ФНС, БКИ, ФССП, 115-ФЗ, SMS, реестр залогов, Корпорация МСП, проводки. Скоринговая карта обучена на синтетическом мире, поэтому её качество доказывает работу конвейера обучения, а не реальный риск. Пороги и ставки в config/ — синтетическая политика, их подтверждает экономический блок; они не описывают кредитную политику банка. Полный список упрощений — docs/SIMPLIFICATIONS.md. Вне скоупа: сопровождение кредита после выдачи, регистрация бизнеса, физлица без бизнеса, реальные проводки.

Панель организатора

Откройте /organizers и войдите с STAFF_PASSWORD. Вверху панели кнопка «Получить ИНН» выдаёт случайную компанию синтетического мира: ИНН, который можно скопировать, и карточку с данными реестра и ФНС. Скрытые параметры мира (качество, архетип, исход) не показываются. Для всех восьми мок-источников доступны режимы «По умолчанию», «Работает», «Ошибка», «Тайм-аут» и задержка 0–30 000 мс. Пустая задержка сохраняет параметры окружения; при тайм-ауте применяется MOCK_SOURCES_HANG_SECONDS, а не задержка. Внутренним источникам можно принудительно назначить сбой для проверки поведения конвейера.

Кнопка «Сохранить» применяет изменения ко всему стенду. API и отдельный worker читают настройки из общей БД перед каждым запросом к источнику, без перезапуска. Уже начатый запрос и сохранённое решение не меняются. Изменения могут затронуть следующие попытки обработки текущей заявки. Сброс к исходным значениям также нужно сохранить: он возвращает сценарии из .env, включая сбои отдельных демо-компаний. Ночной пакет предодобрения использует базовые настройки окружения, без интерактивных переопределений и задержек.

API панели: GET /api/organizers/sources, PUT /api/organizers/sources (полный набор восьми источников и текущая revision), GET /api/organizers/random-company. Доступ только сотрудникам и только при DEMO_MODE=true; конфликт версий возвращает 409. Настройки сохраняются между перезапусками.