Эта · Бизнес — умный кредитный кабинет¶
Прототип кредитного конвейера для малого и микробизнеса, сделанный на хакатоне Альфа-Банка (кейс — 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. Автоматическое одобрение и выдача¶
- Войдите клиентом по любому e-mail, нажмите «Подать заявку».
- Введите ИНН
6163001918(ООО, персонаauto_ooo), отметьте три согласия и нажмите «Данные верны, продолжить». - Потребность: «Закупить товар или сырьё», 3 450 000 ₽, 12 месяцев.
- Экран анализа показывает опрос каждого источника, затем решение: предложение на запрошенные условия, автоматическое решение.
- Выберите предложение, прочитайте документы, запросите код (он показывается на экране вместо SMS) и подпишите.
- Через пару секунд отлагательные условия выполнены, статус — «Финансирование выдано».
Так же автоматически решаются 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. Андеррайтер¶
- Клиентом подайте ИНН
770300000904(персонаunderwriter): «Купить оборудование», 560 000 ₽ на 36 месяцев, оборудование за 800 000 ₽, взнос 240 000 ₽, ИНН поставщика7703999981. Заявка уходит на рассмотрение. - В отдельном окне войдите андеррайтером. В очереди откройте досье: источники, факты «заявлено / подтверждено», качество данных, флаги, вклад признаков в балл, расчёт лимита и ставки, почему заявка пришла сюда, журнал.
- Одобрите предложение системы с кодом причины и комментарием. У клиента появится предложение — дальше как в сценарии 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. Настройки сохраняются между перезапусками.