
Привет, Хабр. На связи Екатерина Саяпина, 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.
0xC0CAC01A
Дорогой наш МТС, как достучаться до вашего саппорта, причём до тех, которые реально способны решать проблемы, а не повторять бесполезности вслед за роботом?