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

Выписки из других банков (T23)

statements.py принимает выписки, которые клиент загружает в ответ на запрос данных, и добавляет их к нашему счёту при расчёте качества данных и лимита. Зачем: у клиента уровня PARTIAL или THIN часть бизнеса идёт через другие банки (SPEC §5.1), и без его документов банк видит только часть оборотов.

Когда просим выписку

  • Данные THIN. После стоп-факторов и скоринга decide (workflow.py) возвращает статус needs_data, а не in_review. В requested_documents — «Выписка по расчётному счёту в другом банке за последние 12 месяцев». Балл и причины маршрута в решении есть, предложений нет: без выписки лимит по потоку не считается. Клиент видит DataQuality.client_message («…понадобятся выписки из других банков»).
  • Запрос сотрудника. Андеррайтер запрашивает данные (request_data, T20, STAFF_ACTIONS.md), например у клиента PARTIAL, у которого покрытие выручки ниже 70%. Код причины — statement_needed.

У клиента PARTIAL, который получил предложения автоматически (offered), загрузить выписку нельзя: в таблице статусов SPEC §13 нет перехода offered → enriching. Клиент может подать новую заявку.

Что делает клиент

Действие API Переход
Загрузить выписки POST /api/v2/applications/{id}/statements, multipart/form-data: files (до 5 файлов по 2 МБ) и revision needs_data → enriching
Продолжить без выписки POST /api/v2/applications/{id}/statements/skip {"revision"} needs_data → enriching

Файл, который не удалось распознать, выписка на другой ИНН, файл, который уже загружен, и больше 5 выписок на заявку — ответ 422 с текстом для клиента, статус не меняется. Без выписки заявка THIN пересчитывается как есть и уходит андеррайтеру (route_data_thin); повторно выписку система не просит.

Пересчёт

Заявка после ответа клиента — в enriching без аренды; worker берёт её через claim_resumed (WORKFLOW.md). Источники заново не опрашиваются, если в прошлый раз ответили все обязательные: берутся сохранённые ответы. Иначе каждая выписка означала бы новый запрос кредитной истории, а частые запросы БКИ снижают балл самому клиенту. Если обязательный источник был недоступен, источники опрашиваются заново.

Что меняют выписки:

Что Чем считается С выписками
Качество данных (SPEC §5.1) data_quality (T09) По нашему счёту вместе с выписками: обороты складываются по месяцам. Растут история (месяцы до открытия нашего счёта), покрытие выручки, регулярность
Лимит по денежному потоку и обороту (SPEC §9.1) cash_flow (T16) Тоже по сумме нашего счёта и выписок, с уровнем данных, пересчитанным с выписками
Стоп-факторы, флаги T14, признаки скоринга, балл evaluate_stop_factors, features_from_responses, score Не меняются: только наши данные и госисточники. Карта обучена на нашем счёте, а документ клиента менее надёжен (порядок надёжности источников, SPEC §5.1)
Маршрут route_application(..., statements=True) Всегда андеррайтер, причина route_statement_upload: документ клиента проверяет человек
Факты statement_facts operating_inflows_12m и statement_months с источником statement_upload и уровнем надёжности client_document, рядом с теми же фактами по нашему счёту

Событие журнала «Данные из источников собраны…» хранит, сколько выписок учтено (statements_used), уровень данных по одному нашему счёту (own_account_tier) и были ли переиспользованы ответы источников (sources_reused).

Проверки (check_statements)

Документы клиента проверяются на согласованность с нашими данными и госисточниками (SPEC §5.1):

Проверка Что происходит Флаг
Выписка заканчивается больше чем за 3 месяца до последнего полного месяца или позже него Выписка не учитывается statement_period
Поступления от покупателей за 12 месяцев с учётом выписок выше выручки по ФНС больше чем на 30% Выписка учитывается statement_inflows_above_fns
ИНН в выписке не совпадает с ИНН заявки Загрузка отклоняется (422) —

Флаги попадают в Decision.flags и маршрут (route_flag_<код>), клиент видит только «Мы проверим загруженные выписки вручную».

Формат файла

Пока файл — это JSON с уже распознанной выпиской:

{
  "bank_name": "Банк «Пример»",
  "inn": "5406000001",
  "months": [
    {"month": "2025-10", "inflows": {"customers": 850000, "own_transfers": 0, "loans_received": 0,
                                     "founder_contributions": 0, "other": 0},
     "outflows": {"suppliers": 600000, "payroll": 120000, "taxes": 30000, "rent": 40000, "loan_payments": 0,
                  "cash_withdrawals": 0, "own_transfers": 0, "other": 10000},
     "average_balance": 200000}
  ]
}

months — от 1 до 24 месяцев, от старого к новому, в формате MonthlyTurnover из контрактов (те же категории, что у нашего счёта). Модель Statement лежит в statements.py, а не в contracts.py: перенос в контракты — отдельный PR в общую папку (docs/v2/NOTES.md, Unassigned).

Упрощения

  • # MOCK: распознавания нет. Файл уже содержит результат: обороты по месяцам и категориям. В банке клиент загружал бы PDF или файл обмена 1С (1CClientBankExchange), его читала бы модель OCR / разбора с открытыми весами, а каждый платёж относился бы к категории (покупатели, поставщики, зарплата, свои переводы…) классификатором.
  • # SIMPLIFICATION: переводы между нашим счётом и счётом в другом банке остаются в обеих выписках (поступление в одной и списание в другой), поэтому доля операционных поступлений с выписками ниже настоящей. В банке такие переводы сопоставлялись бы и исключались.
  • # SIMPLIFICATION: пороги проверок (3 месяца давности, 30% сверх выручки ФНС) и ограничения загрузки (5 файлов по 2 МБ) — константы в statements.py, а не config/.
  • Подлинность выписки не проверяется: в банке её подтверждали бы через банк-отправитель или по электронной подписи. Поэтому заявку с выпиской всегда смотрит андеррайтер.
  • Файлы не хранятся: хранится распознанная выписка, имя файла, размер и SHA-256 (loan_applications.statements).