Привет, Хабр! Сегодня поговорим о том, как расшифровывать звонки с клиентами через CRM-систему Битрикс24 вместо CoPilot. Для автоматизации подключим платформу МТС Exolve. Вы сможете получать все записи с транскрибацией в личном кабинете и сохранять их в карточке сделки с клиентом.

Так вы не потеряете важные данные и быстро найдёте нужную информацию в тексте, без повторного прослушивания разговоров. Хранить расшифровки в карточке удобно, а все договорённости можно преобразовать в список дел. При этом автоматизация процесса избавит менеджеров от рутины.

Почему не встроенный CoPilot? Решили сделать альтернативу на случай, если не подходят встроенные опции Битрикс24, тарифы на этот инструмент или не устраивают результаты работы.

Изображение 1. Создание списка дел в карточке сделки
Изображение 1. Создание списка дел в карточке сделки

Рассмотрим систему, которая находит записи, ранее не заносившиеся в CRM. Каждый абонент — это текущий или потенциальный клиент, он участвует в одной сделке. Сервис проверяет, есть ли он в списке, и создаёт контакт для новых.

Один только номер телефона привязать не получится, нужно создать контакт — вручную — и затем прикрепить к сделке либо заполнить поля в процессе её создания.

Руководитель может вручную заводить карточки, привязывать к ним данные потенциальных клиентов. Тогда система автоматически добавит расшифровки разговоров к карточкам соответствующих сделок.

На следующем рисунке — интерфейс CRM. Её можно выбрать на панели слева. Во вкладке вверху выбираем раздел «Сделки». Нажимаем на кнопку «Быстрая сделка» и добавляем новую карточку, чтобы сразу внести все данные.

Изображение 2. Создание сделки в CRM Битрикс24
Изображение 2. Создание сделки в CRM Битрикс24

Если позвонил новый клиент, запись разговора с ним не потеряется. Просто его данные и информацию о сделке нужно заполнить после автоматического появления карточки. Для этого система сразу создаёт новую сделку, привязывает к ней персональную информацию, а затем и расшифровку разговора.

Такой алгоритм предусматривает заключение сделки как с существующим клиентом, так и с новым потенциальным, контакт которого предварительно создаётся.

Хранение и расшифровка звонков

Транскрибация — это процесс преобразования устной речи в текстовый формат. С МТС Exolve вы получите записи телефонных разговоров в виде аудиофайлов и последующую расшифровку.

Так отделы контроля качества смогут быстрее анализировать работу операторов и менеджеров поддержки — прослушивать каждый звонок не придётся.

Система транскрибирует только записанные разговоры. Услугу можно подключить через API или в личном кабинете. Инструкция по шагам:

  • Зайдите во вкладку «Номера» вашего приложения на панели слева, выберите один из добавленных номеров и включите хранение записей и их расшифровку.

  • Подключите услугу на нужном номере в карточке соответствующего приложения. Опция «Текстовая расшифровка + речевая аналитика» включает в себя саму расшифровку, на базе которой платформа формирует результат аналитики (длительность тишины и речи абонентов, скорость речи, количество перебиваний, ключевые слова, резюме диалога).

Изображение 3. Подключение хранения записей и их расшифровки
Изображение 3. Подключение хранения записей и их расшифровки
Изображение 4. Подключение хранения записей и их расшифровки для номера
Изображение 4. Подключение хранения записей и их расшифровки для номера

Скачать разговор в текстовой версии можно в формате JSON через API. Для каждой реплики указывается номер абонента.

Записи хранятся бесплатно в течение месяца, но можно продлить срок хранения за деньги. Стоимость их расшифровки поминутная, рассчитывается по окончании вызова. Время округляется: если разговор длился 1 минуту и 1 секунду или 1 минуту и 59 секунд, будет списано за две минуты. Срок хранения текстовой расшифровки и самой аудиозаписи совпадает.

Получение расшифровок

Расшифровки можно получить по идентификатору либо списком за период. Здесь выберем второй способ. Возьмём список транскрибаций за последний день:

import os
import datetime
from datetime import date, timedelta
import requests
import json


EXOLVE_API_KEY = os.getenv('MTS_API_KEY')
GET_LIST_ENDPOINT = r'https://api.exolve.ru/statistics/call-record/v1/GetTranscribationsList'




def time2format(time_obj):
   return time_obj.strftime('%Y-%m-%dT%H:%M:%SZ')




def get_calls():
   t1 = time2format(date.today() - timedelta(days=1))
   t2 = time2format(date.today())
   payload = {'date_from': t1, 'date_to': t2}
   r = requests.post(GET_LIST_ENDPOINT, headers={'Authorization': 'Bearer ' + EXOLVE_API_KEY}, data=json.dumps(payload))
   print(r.text)
   assert r.status_code == 200
   return json.loads(r.text)

Ключ API следует вносить в переменные окружения и не хранить в репозитории. Заголовок запроса должен содержать поле 'Authorization' с ключевым словом 'Bearer ', которое задаёт тип авторизации, и сам ключ — он добавляется после пробела. В теле запроса передаётся JSON, содержащий даты начала и конца периода, за который нужно получить расшифровки. Даты записываются в формате RFC-3339 / ISO-8601.

Сохранение расшифровок

Для взаимодействия с Битрикс24 используем специальную библиотеку fast_bitrix24. Она упрощает взаимодействие с системой. Важная фича — возможность запросить все элементы одной командой. Так мы получим наиболее понятный, читаемый и короткий код.

from fast_bitrix24 import Bitrix


BITRIX_CODE = os.getenv('BITRIX_CODE')
BITRIX_URL = r'https://b24-d40p9s.bitrix24.ru/rest/1/' + BITRIX_CODE
endpoint = Bitrix(BITRIX_URL)

Взаимодействовать с Битрикс24 проще через создание локальных вебхуков. Настраиваем эндпоинт и права доступа. URL, по которому отправляются запросы, содержит выделенный вам домен, если вы используете облачную версию. Единица в адресе соответствует индексу пользователя Битрикс24, а последняя её часть содержит код доступа к API.

Получаем списки контактов и сделок:

def get_contacts():
   truncated_contact_items = endpoint.get_all("crm.contact.list")
   result = []
   for tci in truncated_contact_items:
       ans = endpoint.call("crm.contact.get", items={"ID": tci["ID"]})
       result.append(ans['order0000000000'])
   return result




def get_deals():
   return endpoint.get_all("crm.deal.list")

Функции поиска информации нужны, чтобы определить, есть ли абонент в контактах, фиксировался ли с ним ранее разговор — то есть добавлен ли клиент в одну из сделок.

def is_contact_in_deal(deal_item: dict, contact: dict | str):
   if type(contact) is dict: contact = contact["ID"]
   return deal_item['CONTACT_ID'] == contact




# Проверить, есть ли сделка с контактом или незнакомым номером, для которого контакт создаётся. Если нет, создадим сделку и звонок туда.
def search_deal_by_contact(deals: list[dict], contact: dict | str):
   for d in deals:
       if is_contact_in_deal(d, contact): return d




# Проверить, есть ли сделка с конкретно этим звонком. Если сделка с контактом есть, то либо звонок новый (прикрепляем),
# либо его нет в сделке с этим контактом.
def is_call_in_deal(deal_item: dict, call_id: str):
   comment = deal_item["COMMENTS"]
   if comment is None: comment = ''
   return call_id in comment




def are_phones_in_contact(contact_item, phones: list | str):
   if type(phones) is str: phones = [phones]
   present_phones = []
   contact_phones = [ph.get('VALUE', '') for ph in contact_item.get('PHONE', [{}])]
   for p in phones:
       if p in contact_phones: present_phones.append(p)
   return present_phones




def search_number_in_contacts(phone: str):
   items = get_contacts()
   for el in items:
       if len(are_phones_in_contact(el, phone)): return el
   return None

Функции добавления информации нужны для автоматического создания контакта и сделки или её обновления.

def add_call_to_deal(deal_item, dialog_string):
   comment = deal_item["COMMENTS"]
   if comment is None: comment = ''
   # Добавляем в сделку расширенный комментарий: существующий + "## Звонок время_начало:время:конец ID_звонка ##" + строка диалога.
   comment += f'\n' + dialog_string
   # Формируем данные об обновляемых полях
   update_data = [{
       "ID": deal_item["ID"],
       "fields":
           {
               "COMMENTS": comment,
           }
   }]
   endpoint.call("crm.deal.update", items=update_data)
   print('Call has been added')




def add_deal_with_contact(contact_item):
   phone = contact_item.get("PHONE", [{}])[0].get('VALUE', '')
   # Формируем данные об обновляемых полях
   update_data = {
       "FIELDS":
           {
               "TITLE": f'Сделка с {phone}',
               "CONTACT_IDS": [contact_item["ID"]],
           }
   }
   endpoint.call("crm.deal.add", items=update_data)
   print('Call has been added')




def add_contact(phone_num, name=None):
   if name is None:
       name = ''  # Current time?
   # Формируем данные об обновляемых полях
   update_data = {
       "FIELDS":
           {
               "PHONE": [{"VALUE": phone_num}],
               "NAME": name,
           }
   }
   endpoint.call("crm.contact.add", items=update_data)
   print('Contact has been added')
   return search_number_in_contacts(phone_num)

Варианты сценариев:

  1. Звонок с номера из контактов, но нет сделки с ним → создание сделки, привязка к ней контакта и разговора.

  2. Звонок с номера из контактов, сделка есть → привязка разговора к ней.

  3. Звонок с незнакомого номера (его в сделках не будет, там можно добавить только контакт) → создание контакта, сценарий из пункта 1.

Исходим из допущения: 1 контакт = 1 клиент = 1 номер = сколько угодно звонков.

Эти сценарии реализует главная функция:

def main():
   # Получить список звонков за сегодня. Для каждого:
   calls = get_calls()
   deals = get_deals()
   for c in calls:
       phone = c['number_a'] if c['number_a'] != EXOLVE_PHONE else c['number_b']
       # Проверить, есть ли этот номер в контактах. Создать если нет.
       contact = search_number_in_contacts(phone)
       if contact is None:
           contact = add_contact(phone)
       # Проверить, есть ли сделка с этим контактом. Создать если нет.
       deal = search_deal_by_contact(deals, contact)
       if deal is None:
           add_deal_with_contact(contact_item)
           deal = search_deal_by_contact(deals, contact)
       # Проверить, есть ли этот ИД звонка в сделке. Если нет - добавить.
       if not is_call_in_deal(deal, c['uid']):
           dialog_string = parse_transcrib(c, phone)
           add_call_to_deal(deal, dialog_string)

Здесь константа EXOLVE_PHONE содержит номер телефона, который привязан к вашему приложению, — она нужна, чтобы отличить номер от контактов клиента.

Запись звонка будет выглядеть как текст в виде реплик. Каждый разговор отделяется шапкой, с указанием времени и уникального номера, по нему система в дальнейшем будет понимать, добавлялся ли этот диалог.

Функция преобразования:

# Преобразовать JSON в строку, содержащую диалог.
def parse_transcrib(call: dict, client_number: str):
   channel1_num = call['number_a']
   channel2_num = call['number_b']
   # "## Звонок время_начало:время_конец ID_звонка ##"
   result_str = f'## Звонок {call["call_start"]} : {call["call_end"]} номер {call["uid"]} ##\n'
   # Сортируем чанки по времени , т.к. они отсортированы по номеру канала, а внутри по времени. Наша же цель - построить диалог.
   chunks = sorted(call['chunks'], key=lambda d: int(d['start_time']['seconds']))
   for ai, el in enumerate(chunks):
       current_num = [channel1_num, channel2_num][int(el['channel_tag'])-1]
       name = 'Клиент' if current_num == client_number else 'Менеджер'
       result_str += f'{name}: {el["text"]}\n'
   return result_str

Функция принимает словарь, соответствующий каждому разговору: номера абонентов, уникальный идентификатор, список чанков. Чанк — это одна реплика. Функция определяет, кто говорил, менеджер или клиент, по номеру канала в реплике. В свою очередь, он соответствует вызывающему и вызываемому абонентам. Чанки отсортированы по номеру канала, а затем по времени. Для построения диалога их нужно отсортировать заново по времени начала реплики.

Вот пример диалога, полученного при помощи транскрибации. API Exolve возвращает JSON такого вида:

[
	{
		"url": "",
		"uid": "7294769509444001792",
		"application_uuid": "af7c7357-d356-4aa6-8abf-3e615928c18a",
		"date_time": "2025-02-10T17:30:11Z",
		"number_b": "79802550478",
		"number_a": "375291712371",
		"redirect_number": "78005057298",
		"duration": 8,
		"chunks": [
			{
				"channel_tag": "1",
				"text": "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435.",
				"start_time": {
					"seconds": "7"
				},
				"end_time": {
					"seconds": "7"
				}
			},
			{
				"channel_tag": "2",
				"text": "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435, \u0432\u044b \u043f\u043e\u0437\u0432\u043e\u043d\u0438\u043b\u0438 \u0432 \u041c\u0422\u0421 \u042d\u043a\u0437\u043e\u0432, \u0440\u0430\u043d\u0435\u0435 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f \u041c\u0422\u0422.",
				"start_time": {
					"seconds": "0"
				},
				"end_time": {
					"seconds": "4"
				}
			}
		],
		"call_start": "2025-02-10T17:29:41.321939Z",
		"call_end": "2025-02-10T17:29:50.752448Z"
	}
]

Из него получаем диалог:

'## Звонок 2025-02-10T17:29:41.321939Z : 2025-02-10T17:29:50.752448Z номер 7294769509444001792 ##\nМенеджер: Здравствуйте, вы позвонили в МТС Экзолв, ранее компания МТТ.\nКлиент: Здравствуйте.\n'

В Битрикс24 получим карточку с таким комментарием:

На практике можно включить обработку только первого входящего вызова, всех исходящих, всех входящих или вообще всех звонков.

Расшифровка подойдёт для работы с лидами и сделками в CRM. При этом такая схема работает автоматически без ручного запуска CoPilot, без всяческих бустов. Более того, при желании API можно подключить к коробочной версии CRM.

Выводы

Сервис записи и расшифровки разговора МТС Exolve легко интегрировать с инструментами автоматизации бизнес-процессов. Вы сможете хранить информацию, быстро находить и формировать списки дел и договорённостей, а также разделять реплики клиента и менеджера.

Комментарии (0)