
Введение
Сегодня мы покажем инструмент, который помог избавиться от хардкода в одной из legacy систем. Он здорово облегчил жизнь разработчикам и команде сопровождения. Возможно, кто-то найдет в нашем тексте решение и своей похожей проблемы.
Инструмент постараемся описать простым языком, без использования математических формул и заумных формулировок, ведь мы хотим продемонстрировать его практическое применение. Речь пойдет о аналитической подсистеме в CRM (далее по тексту просто «система»), которая отвечает за создание стратегии взаимодействия с потенциальным клиентом.
Немного предыстории
Опишем ситуацию, в которую мы попали около 5 лет назад. Возможно, кто-то узнает в ней себя, потому что история типовая.
На сопровождение и развитие нам передали CRM-систему. Если с частью системы, которая отвечает за работу операторов – звонки, сбор и накопление данных – все было понятно, то аналитическая часть, которая выстраивала стратегию взаимодействия с потенциальными клиентами, оказалась черным ящиком, да еще и со своими «скелетами в шкафу».
Проблемы, с которыми мы столкнулись:
- Практически любое изменение бизнес-правил приводило к корректировке кода. Условно говоря, добавлялся новый "IF". С ростом бизнеса скорость и объем вывода новых изменений постоянно увеличивались. Изменения нужно было применять, что называется, на лету, однако текущий подход не позволял этого делать. Система превратилась в «кладбище с привидениями». 
- В случае возникновения вопросов отсутствовала возможность без привлечения разработчиков понять, почему был сделан тот или иной выбор. Разбор кейсов занимал длительное время. 
Рефакторинг. Новые требования к системе
Всем стало ясно, что для дальнейшего развития системы нужны кардинальные изменения.
После анализа мы сформулировали ряд требований для рефакторинга. Вот они:
- Вынести все бизнес-правила из кода приложения в настройки. 
- Заложить потенциал к подключению UI для визуализации бизнес правил. 
- Добавить возможность повторить расчет с учетом параметров субъекта на момент принятия решения. 
- Заложить возможность перенастройки основного объема бизнес-правил «на горячую» - без простоя системы. Сложность состояла в том, что надо было все запланированные требования выполнить без изменения бизнес-логики существующих процессов. А еще переход надо было совершить плавно, чтобы конечные пользователи не заметили никаких изменений. 
Наше решение
В заголовке статьи мы намекнули, что ставку сделали на «дерево принятия решения» (далее просто дерево) и так называемые аналитические признаки.
Сначала раскроем суть некоторых терминов.
Дерево — структура данных, состоящая из узлов, которые соединены направленными дугами. В каждый узел ведет только одна дуга (кроме корневого, у которого вообще нет входящих дуг – см. рис.1).
Корневой узел — это начальный узел, не имеющий предков
Лист — это конечный узел, из которого не выходит ни одной дуги.

Структуру можно считать деревом, если выполняются следующие условия:
- Есть только один корневой узел. Из него стартует обход узлов дерева. 
- Каждый некорневой узел имеет только одного предка. Аналитический признак предлагаем рассматривать как некий параметр, значение которого анализируется при принятии решения. Это может быть: 
- Простой параметр – статический, например, продукт из интернет-заявки, возраст субъекта, пол и т.п. 
- Более сложный – динамический, вычисляемый в моменте и зависящий от ряда фактов. 
 Факты могут быть самыми изощренными из разряда «наличие интернет-заявок за последние 30 дней, поданных субъектом с определенным продуктом после дождичка в четверг, когда семь планет выстроились в ряд» и т.п.
Таким образом, аналитические признаки дают нам механизм, который позволяет упаковать экзотические требования заказчика и далее, жонглируя ими, настраивать бизнес-правила в деревьях.
Структуры в базе данных
Упростим структуры данных до минимума, который позволит продемонстрировать рассматриваемое решение. Даже замахнемся на святое - нормализацию ????.
Справочник аналитических признаков T_ANALYSIS_UNITS
| Наименование | Тип | Комментарий | 
| ANALYSIS_NAME | Строка | Уникальное наименование | 
| ANALYSIS_DESCR | Строка | Описание | 
| DATA_TYPE | Строка | Тип аналитического признака (строка, число, дата) | 
Таблица дерева T_TREE
| Наименование | Тип | Комментарий | 
| ID | Число | Ид. Первичный ключ | 
| ID_PARENT | Число | Ид записи родителя | 
| ANALYSIS_NAME | Строка | Аналитический признак | 
| PRIORITY | Число | Приоритет обхода дерева на уровне | 
| ANALYSIS_COMPARE_VALUE | Строка | Значение аналитического признака для сравнения | 
| OPERATOR_TYPE | Строка | Оператор сравнения (=, >, <, >=, <=, !=, in) | 
| TARGET_VALUE | Строка | Целевое значение | 
Графическую перечисленные структуры мы отобразили на рис 2.

Алгоритм принятия решения
Целью работы алгоритма является определение целевого значения. Этим значением для разных типов деревьев может быть: дата вывода на обзвон, дата отправки информационного сообщения, выбор финансового консультанта, выбор причины исключения из обзвона и т.п.
В демонстрационном примере будем выбирать абстрактную стратегию взаимодействия с клиентом.
Ниже представлены записи в таблицах T_ANALYSIS_UNITS и T_TREE, которые задают структуру дерева на рис 2.
| T_ANALYSIS_UNITS | ||
| ANALYSIS_NAME | ANALYSIS_DESCR | DATA_TYPE | 
| А1 | Тип события | Строка | 
| А2 | Город проживания | Строка | 
| А3 | Продукт | Число | 
| А4 | Сумма кредита | Строка | 
| А5 | Время поступления интернет-заявки. После 21:00 принимает значение ‘Y’, иначе ‘N’. | Строка | 
| T_TREE | ||||||
| ID | ID_PARENT | ID_ANALYSIS_UNIT | PRIORITY | ANALYSIS_COMPARE_VALUE | OPERATOR_TYPE | TARGET_VALUE | 
| 1 | А1 | 100 | EventInternetApp | = | ||
| 2 | 1 | А2 | 100 | Казань | = | |
| 3 | 1 | А2 | 100 | Саратов | = | |
| 4 | 2 | А3 | 100 | Продукт_01 | = | Стратегия_01 | 
| 5 | 2 | А3 | 100 | Продукт_02 | = | Стратегия_02 | 
| 6 | 3 | А5 | 120 | Y | != | Стратегия_03 | 
| 7 | 3 | А3 | 100 | Продукт_03 | = | Стратегия_04 | 
| 8 | 3 | А3 | 100 | Продукт_04 | = | |
| 9 | 3 | А3 | 100 | Продукт_05 | = | Стратегия_05 | 
| 10 | 8 | А4 | 100 | 100000 | <= | Стратегия_06 | 
| 11 | 8 | А4 | 100 | 100000 | > | Стратегия_07 | 
Как все это работает?
Предварительно рассчитываем значения всех аналитических признаков. Получаем их в виде коллекции. Начинаем пробегать дерево. Обход стартует с корня. Движемся сверху вниз.
Порядок обхода узлов дерева, находящихся на одном уровне, определяется их приоритетом. Приоритет нужен в случае, когда на одном уровне встречаются узлы с разными аналитическими признаками.
Чтобы провалиться в узел, мы должны выполнить его условия. Для этого:
- Из узла берем аналитический признак. 
- Подтягиваем заранее посчитанное для него значение. 
- Сравниваем с эталонным значением, применяя оператор сравнения, указанный в узле. 
- Если результат операции сравнения true, то проваливаемся в узел. 
- Если false, то путь закрыт – переходим к следующему узлу. 
Обход завершается при наступлении одного из событий:
- мы достигли любого листа дерева (получили целевое значение), 
- обошли все дерево, но так и не удалось добраться до листа (ничего не выбрали). 
Примеры
На вход дерева подадим коллекцию значений аналитических признаков.
Пример №1:
А1 = EventInternetApp, А2= Казань, А3= Продукт_02, А4= 80000, А5= Y

Результат выбора: Стратегия_0
Пример №2:
А1 = EventInternetApp, А2= Саратов, А3= Продукт_03, А4= 80000, А5= N

Результат выбора: Стратегия_03
История расчета
Все посчитанные значения аналитических признаков сохраняются в базу данных. Это позволяет при возникновении вопросов или спорных ситуаций повторить расчет по дереву и провести анализ. Объем данных большой, поэтому период хранения ограничен: в нашем случае 6 месяцев.
Подведем итоги
На этом, пожалуй, все. Материал пытались изложить максимально кратко, не перегружая подробностями реализации. Цель – поделиться опытом и продемонстрировать удачное решение в действии. Как бывает в жизни: есть алгоритмы или математические модели, но как их применить на практике – не всегда очевидно.
Классическое решение в виде «дерева выбора» обладает двумя ключевыми свойствами:
- Гибкость настройки (добавил/переместил узел дерева – получил желаемую логику). 
- Простота визуализации (для отображения прикрутить UI не составит особого труда). 
Последнее свойство дарит еще такую плюшку как «самодокументирование».
Функционал дерева можно постоянно расширять под свои задачи. Например, на начальных этапах у нас были простые операторы сравнения в узлах, позже мы добавили операторы работы со множествами. Область применения ограничивается только вашей фантазией ???? Спасибо за внимание! Надеемся, что материал был для вас полезен.
 
          