
Привет, Хабр. На связи Екатерина Саяпина, Product Owner платформы МТС Exolve. В этом материале я расскажу, как автоматически добавить в CRM оптимальное время для общения с клиентами. В рамках этого кейса я покажу настройку нашей платформы и получение информации из нее в Битрикс24.
Подготовка МТС Exolve
Подключение
Этот процесс описан в одном из наших предыдущих материалов: нужно завести аккаунт, подписать договор, создать приложение и получить API-ключ. В списке приложений выберите нужное:

Затем перейдите в «Настройки» на панели слева:

Пролистайте до конца вниз и нажмите на кнопку «Подключить» в блоке «Умная проверка номеров»:

Так как это платная услуга, то нужно будет ознакомиться с условиями и тарифами и подтвердить ваше согласие.
Теперь нам доступна работа с Number Lookup API — набором методов, с помощью которых можно получить оптимальное время для звонка. Мы используем GetBestCallTime. Он покажет, когда лучше всего позвонить абоненту. Надо учитывать, что этот метод работает только с клиентами МТС.
Применение
Используем POST-запрос https://api.exolve.ru/hlr/v1/GetBestCallTime. Для авторизации берем Bearer-токен из нашего приложения МТС Exolve, а в тело запроса в JSON-формате добавляем проверяемый телефон. Отправку запроса и преобразование ответа к нужному виду делаем так:
def get_time(time_string: str): hstr = time_string.split(':')[0] return time(hour=int(hstr)) def get_time_range(recepient: str): payload = {'number': recipient} r = requests.post(r'https://api.exolve.ru/hlr/v1/GetBestCallTime', headers={'Authorization': 'Bearer '+sms_api_key}, data=json.dumps(payload)) print(r.text) if r.status_code == 200: ans = json.loads(r.text) text = ans['result'].split(',') since, till = [get_time(t) for t in text] ans.update({'since': since, 'till': till}) return ans return None
Функция get_time_range отправляет запрос и проверяет успешность передачи информации. В последнем случае при помощи get_time получаем время в часах, соответствующее началу и концу периода, наиболее благоприятного для звонка этому абоненту.
Готовим приложение
Импортируем все необходимые библиотеки, в том числе для работы с CRM Битрикс24, потому что именно в ней будет все храниться:
from flask import Flask import requests import json from dataclasses import dataclass import pandas as pd from fast_bitrix24 import Bitrix from datetime import time from datetime import datetime as dt
База данных
Для синхронизации используем функции получения записей о клиентах и номерах телефонов по их ID. Работу с ними мы описывали в одном из прошлых материалов. Номера, а вместе с ними начало и конец подходящих для звонка интервалов храним в pandas DataFrame. Эта библиотека используется для автоматизации обработки данных, а объекты класса DataFrame можно представить как таблицу с индексацией по строкам и столбцам.
Создадим класс для управления базой данных и инициализируем ее поля:
@dataclass class DB: UTC_plus = 3 phones_and_clients_table = pd.DataFrame(columns=['phone', 'client_id', 'call_since', 'call_till']).set_index(['phone'])
Они содержат сдвиг в часах вашей временной зоны относительно UTC+0, а также pandas DataFrame с информацией о номерах телефонов.
Синхронизация с CRM
Сначала функция add_client_phones получает номера клиента из Битрикс24 и передает их в локальную базу. Затем push_phone нормализует номер телефона, проверяет его наличие в базе и при необходимости добавляет. У каждого номера должен быть свой уникальный client_id. Если его нет, то он присваивается автоматически.
def add_client_phones(self, client_full_record): contact_phones = [ph.get('VALUE', '') for ph in client_full_record.get('PHONE', [{}])] if len(contact_phones) == 0: return 0 for c in contact_phones: self.push_phone(c, client_ID=[client_full_record.get('ID')]) return 1 def push_phone(self, phone, client_ID=None, since=None, till=None, replace=False): phone_str = str(phone).replace(' ', '').replace('(', '').replace(')', '').replace('-', '').replace('+', '') if not replace and phone_str in self.phones_and_clients_table.index: return if client_ID is None: client_ID = self.phones_and_clients_table.shape[0] self.phones_and_clients_table.loc[phone_str, 'client_id'] = client_ID self.phones_and_clients_table.loc[phone_str, 'call_since'] = since + self.UTC_plus self.phones_and_clients_table.loc[phone_str, 'call_till'] = till + self.UTC_plus
Получение списка телефонов
При синхронизации с Битрикс24 для новых клиентов устанавливаются значения границ интервалов доступности — когда стоит звонить. Для уже внесенных в базу номеров данные не обновляются повторно — это позволяет избежать лишних запросов к API, если не установлен флаг update=True.
def add_times(self, phone, update=False): if phone not in self.phones_and_clients_table.index: return if self.phones_and_clients_table.loc[phone, 'call_since'] and self.phones_and_clients_table.loc[phone, 'call_till'] and not update: return ans = get_time_range(phone) if ans is None: return self.phones_and_clients_table.loc[phone, 'call_since'] = ans['since'] self.phones_and_clients_table.loc[phone, 'call_till'] = ans['till']
Функция add_times_for_db запускает обновление интервалов для всех телефонов в базе:
def add_times_for_db(self, update=False): for ph in self.phones_and_clients_table.index: self.add_times(ph, update)
Функция select_for_call возвращает список номеров, которые подходят для звонка в ближайшее время:
def select_for_call(self, hour=None): if hour is None: hour = dt.now().hour since_indexes = self.phones_and_clients_table['call_since'] <= hour + 1 till_indexes = self.phones_and_clients_table['call_till'] >= hour indexes = since_indexes * till_indexes temp_table = self.phones_and_clients_table.loc[indexes].sort_values('call_till') return temp_table.index.to_list()
По умолчанию используется текущий час. Выбираются номера:
чей интервал еще не истек (call_till ≥ hour);
интервал уже идет или начнется в течение следующего часа (call_since ≤ hour + 1).
Затем список сортируется по времени окончания интервала (call_till) — в приоритете те, у кого он скоро закончится. Номера, которые пока недоступны, в выборку не попадают.
Для экспорта базы телефонов в Excel используем эту функцию:
def dump(self): self.phones_and_clients_table.to_excel('Dump.xlsx')
Как работает серверная часть
Создаем сервер на фреймворке Flask. Нам достаточно одной функции, возвращающей JSON-объект. Flask автоматически преобразует словарь в объект, который можно передать по сети.
@app.route('/get_phones', methods=['GET']) def get_phones(): ans = crm_pipeline() return json.dumps(ans) def main(): app.run(host='0.0.0.0', port=5000)
В результате приложение возвращает JSON, содержащий только номера, упорядоченные в рекомендуемом порядке:
[ "79101????24", "79101????09", "79101????32", ]
Можно получить сведения о доступности клиентов, выгрузив JSON для последующей программной обработки. Для этого отправляем запрос по адресу https://<your_server>/get_phones_list и вызываем функцию:
@app.route('/get_phones_list', methods=['GET']) def get_phones_list(): return db.phone_table.to_json(None, 'index')
Она возвращает JSON вида:
{ "79101????00": { "client_id": 0, "call_since": 16, "call_till": 17 }, "79101????01": { "client_id": 1, "call_since": 8, "call_till": 9 }, "79101????03": { "client_id": 2, "call_since": 11, "call_till": 12 } }
В таком виде результат подходит для обработки другими приложениями. Его можно сделать удобным и для чтения человеком. Используем функции экспорта базы данных в Excel и передачи файла по HTTP-запросу:
@app.route('/get_phones_table', methods=['GET']) def get_phones_table(): db.dump() try: return send_file('Dump.xlsx', mimetype='application/octet-stream', as_attachment=True) except Exception as e: return str(e), 405
Так будет выглядеть полученная таблица:

Получение номеров телефонов клиентов, заполнение границ интервалов для звонков и создание упорядоченного списка задаются одной небольшой функцией, которая вызывается при каждом запросе определения доступности:
def crm_pipeline(): clients = get_contacts() for c in clients: db.add_client_phones(c) db.add_times_for_db() ans = db.select_for_call() return ans
Это решение можно развернуть в докер-контейнере, в том числе на бесплатном хостинге, как мы описывали в другом нашем материале. Информация от МТС Exolve будет обновляться только для номеров, которые добавляются в базу при синхронизации с CRM, выполняемой с каждым запросом.
Мы собрали рабочее решение для автоматического определения лучшего времени звонка и интегрировали его с CRM Битрикс24. В основе — метод одиночной проверки GetBestCallTime, который показывает, когда абонент чаще всего на связи. Для работы с большими списками номеров есть отдельный метод GenerateBestCallTimeReport.
