Как известно, при создании промышленного процесса, в котором регламентирован каждый шаг, все участвующие подразделения стараются максимально облегчить выполнение своей части работы. Поэтому часто применяются упрощения, которые не позволяют учесть все нюансы процесса, отслеживаемые в ручном режиме каждым аналитиком. По сути, перед автоматизаторами стоит задача охватить наибольшее число вариаций и при этом не усложнить процес�� так, чтобы с ним было невозможно работать. Под усложнениями понимаются различные блокирующие процесс проверки, многочисленные итерации согласований по той или иной задаче, формы дополнительного ручного ввода данных и т.п.
В итоге формируются упрощенные требования, которые не позволяют в полной мере реализовать контроль как над ручными ошибками пользователей, так и над ошибками, допущенными при разработке требований и алгоритмов автоматизируемого процесса.
Вас приветствуют Гевонд Асадян и Илья Мясников. В банке «Открытие» в управлении риск-технологий мы занимаемся внедрением моделей оценки кредитного риска. В этой статье на примере большого и сложного процесса выдачи экспресс-кредитов мы расскажем, как нам удалось реализовать полноценный дубль процесса на стороне одного проверочного скрипта и ускорить процесс выдачи экспресс-кредитов с двух рабочих дней до семи минут.
История болезни
Коротко о скоринге
При выдаче кредитов зачастую используется скоринговая модель, позволяющая отделить высоко рискованных клиентов от клиентов с приемлемым уровнем риска. Подобная модель позволяет проанализировать множество различных факторов за короткий промежуток времени и вынести вердикт об уровне риска в случае, если банк выдаст кредит.
Скоринговая модель — это настроенный алгоритм, присваивающий клиенту определенное количество баллов (скоров) на основе статистических методов. Общая балльная оценка (скор) используется для отнесения клиента к той или иной группе риска. В основу скоринга могут быть заложены различные статистические или экспертные модели.
Для анализа корпоративных клиентов была разработана многомодульная скоринговая модель. Каждый из модулей анализирует определенное направление деятельности клиента:
Каждый блок содержит в себе определенное количество факторов, по которым производится скоринг клиента. В результате по совокупной балльной оценке принимается решение, возможна ли выдача кредитного продукта клиенту или нет.
Расчет факторов — дело достаточно трудозатратное и ресурсоемкое: например, для оценки кредитной истории клиента необходимо направить платный запрос в бюро кредитных историй. Чтобы попусту не тратить финансовые ресурсы банка, входящий поток заявок ограничивается определенными критериями, они называются стоп-факторами. Причем стоп-факторы могут быть реализованы как до скоринга, так и в процессе скоринга, чтобы ограничить число этапов.
Внешние и внутренние данные
Итак, благодаря безграничной фантазии и бесценному опыту коллег из бизнес-подразделений, подразделение рисков создает перечень критериев, которые отсекают заявки и ограничивают выдачу кредитов. Под эти критерии заложены сложные алгоритмы. Любой алгоритм строится согласно оговоренной методике (правилам), которые фиксируются в определении стоп-фактора или фактора. Тут и начинается история болезни, о которой мы хотим рассказать.
Модель охватывает большое число направлений для анализа. Естественно, банк не может себе позволить ручное заполнение факторов по неск��льким тысячам входящих ежедневно заявок. Для этого используются алгоритмы, которые подтягивают накопленные внутри банка данные, а также данные, которые банк получает из внешних источников.
Внутренние данные аккумулируются у отдельных подразделений, каждое из которых хранит их в удобном для своего собственного использования виде. Как правило, внутренние данные хранятся разрозненно, для их систематизации создаются системы хранения данных, в которых решается задача реализации связей между первоисточниками для дальнейшей аналитики.
Внешние данные чаще всего банк закупает у сторонних организаций через специальные API-сервисы. Тут возможны два пути: получение данных напрямую через API либо выгрузка в единое хранилище данных банка через API и дальнейшее их использование через так называемые зеркала внешних баз.
При разработке алгоритмов, по которым внутренние или внешние данные из первоисточников наполняют системы хранения данных, очень важно предметно разбираться в том, какие это данные. Какую специфику они содержат, какие могут быть ограничения по этим данным, как работать с исключениями. Если разработчик алгоритма не знает всех нюансов или они не четко регламентированы в техническом задании, возникает высокий риск ошибок при загрузке и обработке этих данных. Помимо ошибок в алгоритме загрузки данных могут возникать сбои в самом сервисе API. И по тем или иным причинам данные могут не загрузиться вовсе.
Таким образом, возникает первая проблема, которую необходимо решать при расчете факторов для скоринговой модели: проблема корректности и полноты поступивших в расчет фактора данных.
Реализация расчета факторов
Предположим, что все данные получены и загружены в системы, полнота и корректность проверена. Теперь стоит задача рассчитать факторы для скоринговой модели. Перед аналитиком возникает следующая проблема, которую необходимо решить: реализация алгоритма расчета фактора, учитывающая максимальное число исключений из общих правил.
Самый простой пример, который покажет эту проблему на практике — реализация расчета фактора «Общий долг/EBITDA». Данный фактор является распространенной метрикой для оценки долговой нагрузки клиента на основе его финансовой отчетности и широко используется финансовыми аналитиками. Существуют разные способы оценки как общего долга клиента, так и его EBITDA.
Именно поэтому важно расписать методику, по которой необходимо произвести расчет фактора в скоринговой модели, чтобы учесть все составляющие числителя и знаменателя. Отдельно необходимо учесть такие исключения, как возможность равенства нулю знаменателя и описать для автоматизаторов, как поступать в таких случаях.
В скоринговой модели экспресс-кредитования помимо реализации таких простых факторов существует потребность расчета сложных факторов, включающих в себя большие объемы данных, например, транзакции клиентов банка за последние 2-3 года. При расчете таких факторов возникает еще одна проблема — проблема производительности при расчете факторов. Банк не может позволить себе многочасовой расчет факторов по одному клиенту. Поэтому при расчетах важно пользоваться оптимизированными алгоритмами на системах, позволяющих производить расчеты с большими объемами данных.
В процессе реализации расчета факторов разработчик может допустить ошибки, связанные с описанными выше проблемами. Поэтому очень важно:
Прописывать методику расчета факторов так, чтобы она трактовалась однозначно;
Продумать методику тестирования и валидации разработанных скриптов, чтобы покрыть наибольшее число кейсов.
Теперь — подробнее о том, как мы решали все эти проблемы.
Лечение болезни
Многомодульная модель — настолько сложный и многоэтапный процесс, что сбой может возникнуть на любом этапе. Поэтому наиболее эффективный способ контроля процесса выдач в онлайн режиме — разработка дублирующего проверочного скрипта, который будет независимо повторять все шаги за промышленной системой и сигнализировать в нужный момент о проблемах, останавливая процедуру выдачи кредитных продуктов по заявкам.
Архитектура промышленного процесса
Для того, чтобы повторить процесс выдачи экспресс-кредитов, прежде всего необходимо понимать архитектуру промышленного решения. В нашем случае мы разбирались с уже реализованным в кредитном конвейере процессом, однако в идеальной картине мира необходимо все эти процедуры продумывать заранее. На рисунке 1 приведена схематичная архитектура промышленного бизнес-процесса, которую мы попробовали повторить.
С целью проверки каждого этапа был разработан дублирующий проверочный скрипт, который запускался после этапа принятия решения, но до выдачи. Таким образом процесс, иллюстрированный на рисунке 1, был доработан следующим образом:
Технология реализации проверочного скрипта
Проверочный скрипт реализован на платформе Mlops в виде развернутого rest-сервиса, работающего по принципу API (запрос-ответ). Для этих целей развернут Docker с интеграцией между кредитным конвейером и внутренними базами данных. Подобный подход позволяет без дополнительных интеграций осуществлять обмен данными между системами (см. рисунок 3).

Рассмотрим каждый этап по отдельности, с выдержками кода, примерами и описаниями решений.
Формирование входных данных
Любой расчет начинается с данных, которые необходимо подать на вход. При этом для целей дублирующего проверочного скрипта важно сохранить данные в первозданном виде и настроить независимую обработку. Поскольку в процессе кредитования экспресс-продуктов используются большие массивы данных, наиболее удобным способом для передачи такого массива является JSON. В нем зашифрованы данные в том виде, в котором приходят от внешних сервисов.
Входящий JSON, состоящий из разных модулей данных, целесообразно формировать в следующей структуре:
RqUID — идентификатор файла для расчета;
DateTime — дата и время формирования файла для расчета;
app — информация по заявке:
Набор атрибутов для расчета по заявке;
spark_data — xml СПАРК;
pravo_data — список словарей с данными ПРАВО.ру;
bki_data — список словарей с данными бюро кредитных историй.
loan_member_express — список словарей с данными по клиенту и связанным лицам из кредитного конвейера для сравнения.
Также с целью упрощения передачи данных реализовано хранение пользовательских справочников в виде JSON, в которых содержится информация следующего рода:
Списки отраслей с экспертными оценками;
Перечень типов входных данных для проверки ошибок;
Справочник с кодами факторов и стоп-факторов.
Кредитный конвейер по маршруту заявки генерирует запросы во внешние системы и записывает полученную информацию в JSON-файл, затем инициирует запрос к REST-API, который, в свою очередь, ссылается на нужные справочники и производит расчет скоринга и лимитов.
Технология реализации проверочного скрипта
Проверочный скрипт реализован в классовой логике: это означает, что каждый модуль процесса выделен в отдельный класс, вызываемый на основе установленного пайплайна.
Класс DataLoader
В данном классе производится загрузка необходимых для расчета справочников. При инициализации в классе задаются пути к справочникам:
class DataLoader:
def __init__(self, industry_classifier_path, stop_industry_list_path, types_path,
activity_type_path,
gz_stops_path,
gz_ec_map_path
):
# Инициализация переменных класса, содержащих пути к файлам, содержащим таблицы и словари с параметрами модели
self.stop_industry_list_path = stop_industry_list_path #20220524
self.industry_classifier = industry_classifier_path
self.types_path = types_path
self.activity_type_path = activity_type_path
self.gz_stops_path = gz_stops_path
self.gz_ec_map_path = gz_ec_map_pathДалее в классе поочередно реализованы функции по загрузке каждого из необходимых справочников, например, функция для загрузки справочника стоп-отраслей:
def load_industry_classifier(self):
"""
Загрузка стоп-отраслей ОКВЭД
"""
df_bank_okved = pd.read_excel(self.industry_classifier, engine='openpyxl', sheet_name = 'ОКВЭД',
usecols = ['КОД', 'Отрасль', 'МБ'], skiprows=1, converters = {'КОД':str})
df_bank_okved['МБ'] = df_bank_okved['МБ'].where(df_bank_okved['МБ'].isnull() == False, '1')
df_bank_okved['МБ'] = df_bank_okved['МБ'].apply(lambda x: (x.strip()).capitalize())
df_bank_okved = df_bank_okved[df_bank_okved['МБ']!='1']
return df_bank_okvedКласс CheckInputData
Важным этапом при реализации дублирующего скрипта является проверка входных данных на полноту и корректность. Для этого реализован класс, позволяющий провести проверку входящих данных на целостность, полноту и корректность по форматам. При инициализации класса задается датафрейм, полученный нормализацией входного json и датафрейм-справочник типов входных данных.
class CheckInputData:
def __init__(self, df_root, df_types):
self.df_types = df_types
self.df_root = df_root
self.df = None
self.RqUID = None
self.DateTime = NoneДалее в классе реализована функция, которая позволяет производить логирование ошибок во входных данных. В случае, если приходят не такие данные, какие необходимы, скрипт выдает сообщение об ошибке и формирует технический отказ по заявке до момента исправления ошибки и повторного расчета.
def log_errors(self):
root_columns = list(self.df_root)
if 'RqUID' not in root_columns:
result_calculation = json.dumps({'RqUID': None, 'DateTime': str(datetime.utcnow() + timedelta(hours=3)),
'app': [{
'APP_ID': None,
'Code': "402",
'Status': "Error",
'Description': "Несоответствие перечня загруженных полей по модулю модели (отсутствует RqUID)"}]})
result_json = json.loads(result_calculation)
return result_json
self.RqUID = self.df_root['RqUID'][0]Пример ошибки, по которой проводится логирование: "Несоответствие перечня загруженных полей по модулю модели — отсутствует RqUID".
Класс ExtractData
После загрузки и проверки входных данных на корректность ставится задача по их парсингу. При инициализации задается датафрейм, полученный нормализацией входного json, а также датафрейм-справочник отраслей:
class ExtractData:
def __init__(self, df, industry_classifier):Далее создаются основополагающие датафреймы, с которыми потом работаем в скрипте:
apps_df — датафрейм с информацией по заявке;
df_lm — часть входного json, соответствующая участникам сделки.
Поскольку существует множество разных источников с различной методикой формирования входных данных, в классе реализована функция, которая приводит пустые значения во входящих данных к единообразному виду:
def check_missing(self, field):
if isinstance(field, str): #добавил проверку на строку, иначе падал при попытке привести float в нижний регистр
if field.lower() in ('nat', 'null', 'nan'):
field = np.nan
return fieldФункция заменяет "строковые пропущенные значения" вида ‘nat', 'null', 'nan' на np.nan.
Далее реализована функция def init_extractor(self, df, industry_classifier), которая осуществляет первоначальную загрузку данных для дальнейшего расчета факторов. В этой функции поочередно загружаются данные из входящего JSON для каждого модуля с предварительной обработкой пропущенных значений.
После загрузки и первичной обработки данных производится парсинг данных БКИ, СПАРК, ПРАВО.ру и др. Парсинг данных для каждого источника реализован как отдельная функция.
Парсинг данных БКИ включает в себя анализ платежной строки по полученным от сервиса CREA данным. Первоначально фильтруются нужные договоры клиента по условиям, заложенным в методологии на основе типа договора и срока действия, затем в каждой платежной строке производится поиск нужной информации, например, просроченной на 120+ дней задолженности, который обозначен символом «5».
Анализ осуществляется, если у клиента есть кредитная история. В противном случае формируются пустые датафреймы. Пример парсинга платежной строки приведен ниже:
# Удаляем символ подчеркивания в начале платежной строки
self.bki_df['PMTSTRING84M'] = self.bki_df['PMTSTRING84M'].\
apply(lambda x : str(x[1:]) if str(x) != 'nan' and '_' in x else x)
# 2.2) отбрасываются договора по которым неизвестна платежная строка (пустая)
self.bki_df = self.bki_df[(self.bki_df['PMTSTRING84M'].notnull())&\
(~self.bki_df['PMTSTRING84M'].str.lower().isin(self.missing_list))]Парсинг данных СПАРК производится в два этапа — сначала определяется список связанных лиц, а затем производится загрузка информации, содержавшейся в методах СПАРК. Для анализа используется информация, содержащаяся в следующих методах API-СПАРК:
GetCompanyExtendedReport;
GetCompanyStructure;
ManagementCompanyINN;
GetCompanyListByPersonINN.
Данные по связанным лицам сохраняются в виде списка списков следующего формата: [ИНН, наименование, ИНН связанного лица на уровень ближе к заемщику, доля владения, роль связанного лица, OKATO, наименование страны].
После того как сформирован список связанных лиц, рассчитываются стоп-факторы СПАРК, которые включают в себя такие критерии, как динамика выручки, наличие сообщений о ликвидациях и банкротствах, проверка статуса компании и срока деятельности, информация о руководителях и об их смене.
Пример реализации парсинга данных-XML для извлечения OKOPF приведен ниже (остальные данные извлекаются аналогичным способом на основе описания каждого метода, изложенного в спецификации API-СПАРК).
if 'GetCompanyExtendedReport' in self.all_spark[inn].keys():
xml_root = self.all_spark[inn]['GetCompanyExtendedReport']
if not (xml_root.find('Data/Report/OKOPF') is None):
okopf_dict = xml_root.find('Data/Report/OKOPF')
okopf = okopf_dict.get('CodeNew')Обработка данных ПРАВО.ру производится похожим методом с той лишь разницей, что необходимо обработать JSON вида {«КЛЮЧ»: «ЗНАЧЕНИЕ»: «»}:
def pravo_json_parse(self):
if not self.root: return self.pravo_df
for page in self.root:
try: total_json = page['page']
except KeyError: page_json = -1Независимая от промышленной системы реализация парсинга данных из первоисточников позволяет исключить эффект "замыливания глаз" разработчиков. А дальнейшее тестирование потока заявок в автоматическом режиме позволяет находить расхождения и в результате анализа дорабатывать скрипты, чтобы покрыть выявленный кейс.
Класс FeatureEngineering
В данном классе реализован расчет факторов и стоп-факторов для каждого модуля модели. По соображениям конфиденциальности мы не и��еем права раскрывать состав факторов модели, но можем поделиться технологией, по которой такой расчет производится.
В данном классе при инициализации загружаются датафреймы с распарсенными в классе FeatureExtract данными и производятся манипуляции, которые не выходят за рамки применения основных библиотек python для работы с датафреймами (pandas, datetime, numpy): def __init__(self, df_spark, df_apps, df_bki, df_pravo).
Разработка функций для расчета каждого фактора была осуществлена по следующей схеме:
Важным этапом при расчете факторов является тестирование результатов путем сверки полученных значений со значениями в выборке для разработки модели. Также важным является тестирование расчета фактора по сгенерированным искусственно кейсам, которые содержат экстремальные значения. Реализация в виде REST-API дает возможность прогонять такие кейсы пакетно, что недоступно в кредитном конвейере, а значит позволяет решать проблему покрытия максимального количества кейсов.
Класс StopsProcess
В данном модуле производится проверка наличия стоп-факторов и проставляется отказ по заявке в случае их выявления. При инициализации задаются датафреймы с построенными в FeatureEngineering атрибутами. Используются данные по заявке, СПАРК, ЧС, БКИ, ПРАВО, транзакции и др.
Отказы по каждому модулю формируются в отдельной функции, которая проверяет датафрейм с расчетом своих стоп-факторов и при превышении пороговых значений проставляет отказ по сделке. Например, реализация по модулю ПРАВО.ру выглядит следующим образом:
def stops_from_pravo(self):
self.stops_pravo = self.pravo
self.stops_pravo['PRAVO_CntOpCaseBankrupt36m'] = int(self.pravo['PRAVO_CntOpCaseBankrupt36m'].sum()>0)
comment = 'Наличие арбитражных дел, открытых на дату заявки, зарегистрированных на горизонте 36 мес. до даты заявки о банкротстве'
if len(self.df_apps['client_inn'][0]) == 12:
self.pravo_stops_comments = self.pravo_stops_comments + [comment] if self.pravo['PRAVO_CntOpCaseBankrupt36m'].sum()>0 else self.pravo_stops_comments
return self.stops_pravo.copy()На выходе получаем либо флаг отсутствия стоп-факторов, либо формируем выходной json с информацией об отказе по тому или иному фактору и переходим к проверке наличия расхождений с данными основного процесса.
Класс CalculateScore
В данном классе осуществляется расчет скорингового балла по клиенту. Рассчитываются флаги наличия данных в источниках.
Общая схема расчета скорингового балла выглядит одинаково:
Выгружаются значения нескольких факторов;
Задаются коэффициенты линейной модели и значения для трансформации в WOE;
Факторы бинаризируются (строковые в соответствии со списком, числовые атрибуты — по промежуткам) и проставляется значение WOE;
Скор по модулю получается линейной комбинацией значений WOE и 1.
Класс CalculateLimit
В данном классе производится расчет лимита. Аналогично классу FeatureEngineering осуществляется расчет факторов для расчета лимита: в каждую отдельную функцию выделен отдельный фактор. Затем каждая функция вызывается в итоговой функции расчета лимита.
Результаты и отчет о расхождениях
По итогам прохождения пайплайна формируются две сущности, с которыми в дальнейшем работает аналитик: отчет с результатом расчета и отчет с расхождениями по сравнению с основным процессом.
Для этих целей выделено три класса:
MakeReport — класс для построения отчета по итогам расчета;
MakeDiffList — класс формирования отчета о расхождениях;
ModelESDSWrapper — класс формирования выходного json, содержащего в себе два отчета, указанных выше.
При инициализации класса MakeRport задаются датафреймы с данными по заявке, все данные из внешних источников и трансакциям, а также скорам, стопам, и лимитам. Итогом работы класса является JSON со структурой: {«report»: [{«app_id»: НОМЕР; «client_name»: НАИМЕНОВАНИЕ; «Score»: СКОР; и т.д.}], «conn_participants»: [……]} Отчет содержит результаты по каждому модулю модели.
При инициализации класса MakeDiffList задаются первоначальный входной датафрейм, все данные из внешних источников и транзакциям, а также скорам, стопам и лимитам. Передается словарь с кодами причин отказа для стопов. Затем формируется датафрейм, содержащий код фактора, первоначальное значение из кредитного конвейера и значение, рассчитанное в дублирующем скрипте. По каждой строке этого датафрейма сравниваются значения факторов по основному процессу и по дублирующему. В случае наличия расхождений формируется JSON, содержащий все пары расходящихся значений с кодами факторов.
Удобство для клиентов и пользователей
На текущий момент система экспресс-кредитования банка, благодаря существованию оптимальных алгоритмов и проверочных скриптов, позволяет обеспечить принятие решения по выдаче кредита клиента за семь минут с момента заведения заявки. Клиенту д��статочно зайти на сайт банка, заполнить короткую анкету и получить решение. При этом клиентам нужно предоставить минимальное количество документов и согласий.
С точки зрения пользователей процесса внутри банка тоже удалось добиться значительного снижения трудозатрат при выдаче экспресс-кредитов. Больше нет необходимости проверять каждую заявку в ручном режиме и принимать решения индивидуально. Благодаря REST-API кредитный конвейер самостоятельно инициирует запрос расчета в дублирующем скрипте, получает отчет и, в случае отсутствия расхождений, пропускает заявку дальше. Таким образом, аналитикам приходится рассматривать только небольшой перечень заявок, по которым возникают расхождения.
В дальнейшем внедрение подобных технологий и их распространение на процессы классического кредитования также позволит сократить время до выдачи кредита, что будет способствовать устойчивому и быстрому развитию малого и среднего бизнеса в России.
