March 14, 2021

Интеграция Айко. Как сделать быстрый отчет по группам клиентов.

Как разделить всю клиентскую базу на группы "0-90 дней", "90-180 дней", ">180 дней" по дате последнего заказа и выгрузить контакты клиентов для рассылки индивидуальных предложений.

Постановка задачи

Выделяем 3 группы клиентов:

  1. Группа "0-90". Клиенты, последний заказ у которых был в течение последних 90 дней. Наиболее активные.
  2. Группа "90-180". Клиенты, у которых последний заказ был от 3 до 6 месяцев назад. Отдел маркетинга предлагает для этой группы активно включать механизмы повышения повторных продаж.
  3. Группа ">180". Клиенты, которые за последние полгода не сделали ни одного заказа.

Нужно по всей клиентской базе доставки сделать анализ: отследить эти три группы за каждый день и показать динамику изменения. С возможностью выгрузки клиентов из каждой группы (для адресных рассылок) и отслеживания переходов клиентов из одной группы в другую (и обратно).

Динамика групп клиентов 0-90-180 дней, fudov.ru

Первая версия решения. 30 секунд.

Семён Фудов кэширует все доставочные отчеты за день. Первый подход к решению состоял в том, чтобы из кэша взять всю историю, собрать всю клиентскую базу и выполнить анализ.

На базе Заказчика за год размером около 100 Мб анализ занимал больше 30 секунд. Слишком долго, даже учитывая нечастые обращения к отчету.

Если строить отчет на периоде последних 180 дней, то в iikoDelivery API (iikoTransport API) не хватает поля "Дата регистрации клиента" — мы видим только текущие заказы, без истории. Т.е. в первой неделе отчета все клиенты будут новыми и попадут в группу "0-90" дней. Плохо.

Вторая версия. 3 секунды.

Меняем архитектуру. Чтобы добавить интерактивности, передаем всю обработку и рендеринг HTML на сторону клиента (да, на дворе 2021 год, уже как лет 5 пора делать client-side рендеринг 💪).

Чтобы быстро собирать всю клиентскую базу, пробуем строить OLAP с минимумом полей. Несколько часов экспериментов и решение найдено:

{
  "reportType": "SALES",
  "buildSummary": "false",
  "groupByRowFields": [
    "Delivery.Number",
    "OpenDate.Typed",
    "Delivery.CustomerPhone",
    "Delivery.CustomerName",
    "Delivery.CustomerCreatedDateTyped"
  ],
  "groupByColFields": [
  ],
  "aggregateFields": [
  ],
  "filters": {
    "OpenDate.Typed": {
      "filterType": "DateRange",
      "periodType": "CUSTOM",
      "from": "",
      "to": "",
      "includeLow": "true",
      "includeHigh": "true"
    }
  }
}

На сервере проходим авторизацию Айко, выгружаем OLAP отчет и в сыром виде передаем клиенту (браузеру). Дальше на javascript выполняем всю обработку и рендерим HTML. Теперь в истории хранятся все заказы всех клиентов.

В финальной версии поле "Delivery.CustomerCreatedDateTyped" исключаем из запроса — если вместо этого взять дату первого заказа, не сильно потеряем в точности.

Во второй версии в цикле по всем дням истории открывали цикл по всем клиентам. И для каждого клиента на выбранную дату определяли, в какую группу он попадает с учетом истории всех заказов. При тех же условиях теперь анализ выполняется за 3 секунды.

Третья версия. 1,5 секунды.

Как ускорить отчет? В финальной версии сделали 2 ускорения:

  1. Отказались полностью от работы с датами и moment.js. Перевели все даты в метки времени timestamp, сравнения дат теперь — как сравнения двух больших чисел. Почти в полтора раза ускорили анализ.
  2. Поменяли логику вложенных циклов. Быстрее открыть цикл по всем клиентам и, зная историю заказов очередного клиента, заполнить массивы групп 0-90-180 дней.

Общее ускорение — на периоде в один год почти вдвое. Когда период будет 2, 3 года, 5 лет, ускорение будет еще заметнее.

Итоги

У Семёна Фудова появился новый отчет "0-90-180 дней": теперь можно подключиться к серверу Айко, выгрузить всех клиентов, "разбить" по группам, отследить динамику и главное — экспортировать контакты клиентов для рассылки индивидуальных предложений.

Клондайк для отдела маркетинга.

Если заинтересовались в таком отчете для своей службы доставки — пишите на почту hello@fudov.ru, подключим.

Находки

Когда замеряли время выполнения клиентского javascript, обнаружили изящный вариант:

console.time('function1');
...
console.timeEnd('function1');
// function1: 1748.093994140625 ms