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

Библиотека признаков

Общий код расчёта признаков скоринговой карты (SPEC §8) для обучения (T10) и для движка на живых заявках. Обучение и движок обязаны считать признаки одним и тем же кодом: иначе модель учится на одних числах, а решения принимает по другим. Поэтому здесь же — правила по просрочкам (SPEC §5) и проверенная выручка, на которых стоят стоп-факторы и флаги (T14), и качество данных по счёту (T09, SPEC §5.1).

Как вызывать

from apps.api.features import (SourcePayloads, data_quality, data_quality_from_responses, extract_features,
                               features_from_responses)

# движок: ответы источников из обогащения (T07), на дату их получения
features = features_from_responses(enrichment.responses)

# обучение: копия ответов из синтетического мира, на дату среза мира
features = extract_features(SourcePayloads.from_company(company), world.meta.as_of)

# качество данных по счёту: ответы источников и заявленная клиентом выручка
quality = data_quality_from_responses(enrichment.responses, request.declared_annual_revenue)
quality = data_quality(SourcePayloads.from_company(company), company.declared_annual_revenue)

На вход идут только ответы источников в формате контрактов: ЕГРЮЛ / ЕГРИП, ФНС, счета и БКИ. Скрытая правда мира (q, архетип, исход) в библиотеку не попадает: from_company берёт у компании только эти четыре ответа. Если источник не ответил, его ответ — None, и все признаки, которые от него зависят, — null (интервал «нет данных» скоринговой карты, SPEC §5.1).

as_of — дата, на которую считаются возраст бизнеса, давность просрочек и окно запросов КИ. У движка это день, когда ответили источники (snapshot_date), у обучения — дата среза мира.

Признаки

Значения округляются: доли и отношения — до трёх знаков, дни остатка — до одного. Округление — часть определения признака: обучение видит те же числа, что и движок.

Признак Как считается null, если
inflow_trend_12m Поступления от покупателей за последние 6 месяцев / за предыдущие 6 − 1 история по счёту короче 12 месяцев или в первом полугодии поступлений не было
inflow_cv_12m Коэффициент вариации месячных поступлений от покупателей (стандартное отклонение по генеральной совокупности / среднее) история короче 6 месяцев или поступлений не было
months_without_inflows_12m Месяцев без поступлений от покупателей, в пересчёте на 12 месяцев история короче 6 месяцев
customers_count_12m, top_customer_share Из сводки по контрагентам за 12 месяцев история короче 6 месяцев; доля — ещё и если покупателей нет
cash_withdrawal_share Снятие наличных / все списания история короче 6 месяцев или списаний не было
balance_days_of_expenses Средний остаток / (среднемесячные списания / 30) то же
tax_payment_months_12m, payroll_payment_months_12m Месяцев с налоговыми / зарплатными платежами, в пересчёте на 12 месяцев. У ИП без сотрудников зарплаты нет, это 0, а не «нет данных» история короче 6 месяцев
worst_delinquency_days_24m_company Худшая просрочка по отчёту компании после правил по просрочкам (ниже) ИП; у компании нет отчёта или в нём нет ни одного продукта
worst_delinquency_days_24m_owner То же по отчёту собственника (у ИП — предпринимателя) в отчёте нет ни одного продукта: нет кредитной истории — не то же, что чистая
bki_inquiries_3m Запросы КИ за 3 месяца до as_of, по всем отчётам БКИ не ответило
debt_to_revenue Остаток долга бизнеса (отчёт компании у ООО, отчёт предпринимателя у ИП; без выданных гарантий) / годовая выручка ФНС, пересчитанная на полный год нет отчётности в ФНС или выручка нулевая
limit_utilization Использованная часть действующих возобновляемых лимитов (овердрафты, кредитные линии, карты) по всем отчётам возобновляемых продуктов нет
business_age_months Полных месяцев от регистрации до as_of ЕГРЮЛ не ответил
okved_risk_group Группа риска основного ОКВЭД из config/features.json то же
legal_form Из ЕГРЮЛ / ЕГРИП то же
owner_or_director_changed_6m Директор или учредитель (у ИП — только предприниматель) появился после регистрации и меньше 6 месяцев назад то же
margin Чистая прибыль / выручка из ГИР БО ИП (в декларации только доход); нет отчётности; выручка нулевая
negative_equity Капитал в ГИР БО меньше нуля ИП; нет отчётности

Все признаки по счёту смотрят на последние 12 полных месяцев или на всю историю, если она короче, но не меньше 6 месяцев: с меньшей историей данные — THIN (SPEC §5.1). Счётчики месяцев пересчитываются на 12, чтобы 8 месяцев с налоговыми платежами из 8 читались как 12. «Операционные поступления» — поступления от покупателей: переводы со своих счетов, полученные кредиты и взносы учредителя — не выручка.

Правила по просрочкам

credit.py, пороги — в config/features.json. Просрочка учитывается, только если она:

  • длиннее 5 дней (до 5 дней — техническая задержка);
  • не меньше 1 000 ₽ и не меньше 1% платежа по графику.

Давность эпизода считается от пропущенного платежа (started_at). Эпизоды старше 24 месяцев идут с половинным весом, старше 36 — не учитываются. Текущая просрочка проходит те же правила.

Признак worst_delinquency_days_* — худшая просрочка в днях с учётом веса: эпизод 25–36-месячной давности на 100 дней даёт 50. Поэтому «24m» в имени признака означает «полный вес за 24 месяца», а в признак попадают и эпизоды до 36 месяцев с половинным весом.

Для стоп-факторов (T14) subject_history отдаёт по каждому субъекту очищенные эпизоды, worst_days_24m (худшая просрочка с полным весом, для «просрочка 90+ за 24 месяца») и current_overdue_days (для «текущая просрочка больше 30 дней»). inquiries_since считает запросы КИ с нужной даты. Наша заявка не оставляет запроса в отчёте (T06), поэтому все посчитанные запросы — чужие.

Выручка

revenue.py, fns_revenue(fns, egrul, accounts). ФНС даёт выручку за последний сданный календарный год, а клиент заявляет выручку за последние 12 месяцев. Поэтому считаются три числа:

  • filed — как в ФНС;
  • annualized — пересчёт на полный год, если компания зарегистрирована в отчётном году: filed / доля года, которую она существовала;
  • estimate_12m — annualized, перенесённая на последние 12 месяцев по динамике поступлений от покупателей: поступления за 12 месяцев / среднемесячные поступления отчётного года × 12. Нужны 12 месяцев истории и хотя бы 6 месяцев отчётного года на счёте, иначе остаётся annualized.

Кто что использует: debt_to_revenue делит на annualized (цифра отчётности); флаг расхождения заявленной выручки (T14) должен сравнивать с estimate_12m; покрытие выручки в качестве данных (T09) — делить на annualized.

На мире (зерно 7, 3000 компаний) заявленная выручка честных клиентов отличается больше чем на 30%:

от выручки ФНС как есть от estimate_12m
Отчётность за полный год 21% 7%
Компания зарегистрирована в отчётном году 82% 15%

«Завышателей» оценка по-прежнему ловит примерно в 93% случаев. Часть оставшихся честных клиентов и правда ошибается в заявке больше чем на 30%: так заявляют 4% честных клиентов (T04).

Качество данных по счёту

data_quality.py, data_quality(payloads, declared_revenue) → контракт DataQuality. Четыре показателя SPEC §5.1, у каждого свой уровень; итоговый уровень — худший из четырёх.

Показатель Как считается
history_months Месяцев в выписке по нашему счёту
revenue_coverage Поступления от покупателей за последние 12 месяцев / годовая выручка. Выручка — fns_revenue(...).annualized (выручка ФНС, пересчитанная на полный год, раздел «Выручка»); если в ФНС её нет или ФНС не ответила — заявленная клиентом. Что взято, видно в revenue_basis (fns / declared). При истории короче 12 месяцев поступления пересчитываются на 12: короткая история и так снижает первый показатель, и покрытие не должно наказывать за неё второй раз
operating_inflow_share Поступления от покупателей / все поступления за последние 12 месяцев. Переводы со своих счетов, полученные кредиты и взносы учредителя — не выручка
regularity_months Месяцев с поступлениями от покупателей из последних 12, без пересчёта

Показатель, который посчитать нельзя (нет выписки по счёту; нет выручки ни в ФНС, ни в заявке), получает null и уровень THIN: данных для решения нет. Без заявки — например, при пакетном предодобрении (T22) — declared_revenue не передаётся.

client_message — фраза для клиента по уровню и худшему показателю (первому из худших в порядке таблицы), например «Мы видим не все обороты вашего бизнеса: похоже, часть выручки проходит через другие банки. Выписка из другого банка поможет увеличить лимит.»

Пороги — config/data_quality.json, значения принадлежат экономическому блоку; сейчас там пороги SPEC §5.1. Версия файла попадает в DataQuality.policy_version. Признаки скоринговой карты от этих порогов не зависят, поэтому их правка не требует переобучения карты.

На мире (зерно 7, 3000 компаний) у обычных компаний 74% FULL, 19% PARTIAL и 7% THIN; у всего мира — 65%, 22% и 13%. Клиенты с недавно открытым счётом и с раздутыми своими переводами (T04) никогда не получают FULL, клиенты с основным счётом в другом банке — в 2% случаев. Около 4% обычных компаний — PARTIAL только из-за доли операционных поступлений: за последние 12 месяцев они получили кредит или взнос учредителя.

Политика: config/features.json

Пороги правил по просрочкам, окно запросов КИ (3 месяца), окно смены директора или собственника (6 месяцев, им же пользуются факты T07) и группы риска ОКВЭД — кредитная политика, значения принадлежат экономическому блоку. Группы риска ОКВЭД сейчас повторяют отраслевые поправки к дефолтности в config/world_outcomes.json: поправка ≤ 0 — low, 0,1–0,2 — medium, от 0,3 — high. Код из списка или его префикс по границе точки (41 покрывает 41.20) выбирается по самому длинному совпадению; ОКВЭД, которого нет в списках, получает okved_default_risk_group.

Версия файла — это и версия набора признаков (Features.feature_set_version). Её поднимают при любой правке определений здесь или значений в файле, после чего скоринговую карту надо переобучить.

Упрощения

Упрощение Как было бы в реальности
# SIMPLIFICATION: выручка переносится на последние 12 месяцев по динамике нашего счёта, даже если часть бизнеса идёт через другие банки Квартальные декларации по НДС и УСН, выписки других банков
Группы риска ОКВЭД заданы для дюжины отраслей мира, остальные коды получают группу по умолчанию Таблица по всему дереву ОКВЭД, откалиброванная по дефолтам портфеля банка
Долг к выручке — только долг бизнеса; личные кредиты собственника ООО не учитываются Совокупная долговая нагрузка группы: компания, собственник, связанные компании
# SIMPLIFICATION: пороги качества данных взяты из SPEC §5.1 экспертно Пороги, откалиброванные по портфелю банка: при каком покрытии и истории лимит по потоку ещё надёжен
# SIMPLIFICATION: история по счёту — все месяцы выписки: в мире нет месяцев без операций Считать только месяцы, в которых по счёту были операции

Тесты

tests/test_features.py:

  • из примеров источников в tests/fixtures/sources/ получается ровно tests/fixtures/features.json;
  • каждое правило по просрочкам проверяется на границе;
  • на сгенерированном мире у каждого признака есть значения, null — там, где ожидается (ИП, короткая история);
  • признаки несут исход (волатильные поступления — больше дефолтов);
  • оценка выручки снижает долю честных клиентов с расхождением больше 30%.

tests/test_data_quality.py:

  • из примеров источников получаются показатели и уровень tests/fixtures/data_quality_full.json;
  • каждый порог из config/data_quality.json проверяется на границе;
  • короткая история, свои переводы, низкое покрытие, выручка за неполный год, заявленная выручка вместо ФНС, нет выписки;
  • доли уровней на мире совпадают с ожидаемыми (apps/world/README.md).