Как известно, при создании промышленного процесса, в котором регламентирован каждый шаг, все участвующие подразделения стараются максимально облегчить выполнение своей части работы. Поэтому часто применяются упрощения, которые не позволяют учесть все нюансы процесса, отслеживаемые в ручном режиме каждым аналитиком. По сути, перед автоматизаторами стоит задача охватить наибольшее число вариаций и при этом не усложнить процесс так, чтобы с ним было невозможно работать. Под усложнениями понимаются различные блокирующие процесс проверки, многочисленные итерации согласований по той или иной задаче, формы дополнительного ручного ввода данных и т.п.
В итоге формируются упрощенные требования, которые не позволяют в полной мере реализовать контроль как над ручными ошибками пользователей, так и над ошибками, допущенными при разработке требований и алгоритмов автоматизируемого процесса.
Вас приветствуют Гевонд Асадян и Илья Мясников. В банке «Открытие» в управлении риск-технологий мы занимаемся внедрением моделей оценки кредитного риска. В этой статье на примере большого и сложного процесса выдачи экспресс-кредитов мы расскажем, как нам удалось реализовать полноценный дубль процесса на стороне одного проверочного скрипта и ускорить процесс выдачи экспресс-кредитов с двух рабочих дней до семи минут.
История болезни
Коротко о скоринге
При выдаче кредитов зачастую используется скоринговая модель, позволяющая отделить высоко рискованных клиентов от клиентов с приемлемым уровнем риска. Подобная модель позволяет проанализировать множество различных факторов за короткий промежуток времени и вынести вердикт об уровне риска в случае, если банк выдаст кредит.
Скоринговая модель — это настроенный алгоритм, присваивающий клиенту определенное количество баллов (скоров) на основе статистических методов. Общая балльная оценка (скор) используется для отнесения клиента к той или иной группе риска. В основу скоринга могут быть заложены различные статистические или экспертные модели.
Для анализа корпоративных клиентов была разработана многомодульная скоринговая модель. Каждый из модулей анализирует определенное направление деятельности клиента:
Каждый блок содержит в себе определенное количество факторов, по которым производится скоринг клиента. В результате по совокупной балльной оценке принимается решение, возможна ли выдача кредитного продукта клиенту или нет.
Расчет факторов — дело достаточно трудозатратное и ресурсоемкое: например, для оценки кредитной истории клиента необходимо направить платный запрос в бюро кредитных историй. Чтобы попусту не тратить финансовые ресурсы банка, входящий поток заявок ограничивается определенными критериями, они называются стоп-факторами. Причем стоп-факторы могут быть реализованы как до скоринга, так и в процессе скоринга, чтобы ограничить число этапов.
Внешние и внутренние данные
Итак, благодаря безграничной фантазии и бесценному опыту коллег из бизнес-подразделений, подразделение рисков создает перечень критериев, которые отсекают заявки и ограничивают выдачу кредитов. Под эти критерии заложены сложные алгоритмы. Любой алгоритм строится согласно оговоренной методике (правилам), которые фиксируются в определении стоп-фактора или фактора. Тут и начинается история болезни, о которой мы хотим рассказать.
Модель охватывает большое число направлений для анализа. Естественно, банк не может себе позволить ручное заполнение факторов по нескольким тысячам входящих ежедневно заявок. Для этого используются алгоритмы, которые подтягивают накопленные внутри банка данные, а также данные, которые банк получает из внешних источников.
Внутренние данные аккумулируются у отдельных подразделений, каждое из которых хранит их в удобном для своего собственного использования виде. Как правило, внутренние данные хранятся разрозненно, для их систематизации создаются системы хранения данных, в которых решается задача реализации связей между первоисточниками для дальнейшей аналитики.
Внешние данные чаще всего банк закупает у сторонних организаций через специальные 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 кредитный конвейер самостоятельно инициирует запрос расчета в дублирующем скрипте, получает отчет и, в случае отсутствия расхождений, пропускает заявку дальше. Таким образом, аналитикам приходится рассматривать только небольшой перечень заявок, по которым возникают расхождения.
В дальнейшем внедрение подобных технологий и их распространение на процессы классического кредитования также позволит сократить время до выдачи кредита, что будет способствовать устойчивому и быстрому развитию малого и среднего бизнеса в России.
darya777888
Очень познавательно ????????????