Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Следующая версия
Предыдущая версия
avtomaticheskaja_sistema_obzvona_klientov [2013/11/18 17:27]
ansealk создано
avtomaticheskaja_sistema_obzvona_klientov [2013/11/18 17:27] (текущий)
Строка 1: Строка 1:
 +====== Автоматическая система обзвона клиентов (теория) ======
 +Как сделать автоматический обзвон написано уже много, в том числе и на этом сайте. Гибкость asterisk'​a не имеет границ. Написано огромное количество статей по реализации простейших действий встроенными средствами,​ либо с использованием сторонних продуктов и решений. Поэтому на мой взгляд наиболее интересно будет решение не стандартной задачи,​ для которой пришлось полностью с нуля разработать и внедрить систему,​ учитывая совместимость с текущей схемой колл-центра.
  
 +===== Предыстория =====
 +
 +На данный момент с момента внедрения прошло уже больше года. Сейчас переписано около 90-95% всего программного кода системы и я четко представляю как развивается и как должна развиваться система. Но на тот момент когда перед мной поставили задачу,​ у меня было расплывчатое представление как должен выглядеть код судя по ТЗ, а опыта в общении с asterisk'​ом не было в принципе. Сразу скажу что основная идея не моя, моя задача была реализовать или даже скорее изобразить,​ то что было расписано в условиях задачи. Но при этом что самое важное я был практически ничем не ограничен в выборе технологий и способов решения — что на мой взгляд позволило мне довести всю схему к виду, который я хотел.
 +
 +На тот момент в компании уже был рабочий колл-центр. Около 10 очередей,​ от 4 до 20 операторов в каждой очереди и около 12-15 тысяч звонков круглосуточно. 5 провайдеров для местных,​ междугородних и международных вызовов. За долгое время колл-центр оброс большим количеством различного функционала и собственных разработок. Основной софт платформой являются сервера на астерисках,​ база со статистикой звонков и бизнес логикой на MySQL, а так же обвязка из скриптов на AGI.
 +
 +===== Задача =====
 +
 +Периодически возникает необходимость обзвонить клиентов по различному роду вопросов. Это может понадобится как периодически,​ например раз в месяц (задолженность,​ уведомления по акции или рекламе ), как и эпизодически (нерешенные проблемы,​ технические вопросы). На текущий момент из очереди выбираются несколько человек,​ которые обзванивают таких клиентов и формируют отчёт по своей работе,​ который затем передается друг другу. Если возникает большая очередь звонков — операторов возвращают на входящие звонки и они помогают устранить нагрузку,​ затем снова возвращаются обратно. В итоге возникает много проблем как человеческого фактора,​ человек может ошибиться в номере,​ забыть кого нибудь из клиентов,​ так и в административном управлении — требуется следить за очередью и перекидывать операторов из обзвона на входящие звонки и обратно. ​
 +
 +Поэтому требуется некий автомат,​ который будет сам обзванивать нужных клиентов,​ при этом учитывая нагрузку в очереди,​ так чтобы по простым вопросам,​ можно было обзванивать только при минимальной нагрузке,​ а по более сложным и важным — в приоритете даже над входящими звонками. Автомат должен напрямую соединять оператора и клиента,​ так чтобы клиент сразу же начинал разговор уже с живым человеком,​ без прохождения IVR'a или ожидания в очереди. Оператор которого выбрали для обзвона — должен оставаться в очереди и после завершения разговора без каких либо действий мог принять обычный входящий звонок или очередной обзвон. После соединения с оператором нужен таймаут для того, чтобы оператор успел вникнуть в суть звонка — понять для чего он звонит клиенту,​ а после завершения звонка в независимости от результата таймаут на то, чтобы откомментировать задачу. Планируемая нагрузка 2000 звонков в месяц а затем в перспективе до 10 000 — 15 000.
 +
 +===== План реализации =====
 +
 +Судя по описанию условий задачи формируем техническую модель: ​
 +  * Требуется планировщик задач. Нужно формировать сами задания,​ выдавать их как в любое время так и в определённое (никому не понадобится информация по балансу в 3 часа ночи). Необходимо следить за нагрузкой очереди,​ чтобы задания на обзвон не забили все слоты и не мешали обычным входящим звонкам.
 +  * Нужен механизм для прямого соединения оператора и клиента. В первую очередь выбрав свободного оператора,​ поднимается плечо оператора и только потом плечо клиента. На время вызова должна сохранится вся логика обработки очереди. Оператор должен быть занят и вызов должен быть засчитан в статистике.
 +  * По возможности нужно минимальное участие оператора. Здесь в данном случае я подразумеваю максимальную автоматизацию со стороны работы оператора. Для него должен прийти входящий вызов, оператор отвечает на него и система сама делает вызов клиента,​ выбирая как направление,​ так и сама меняя статус задаче,​ в независимости от того, что дозвонились мы или нет до клиента. Подробнее объясню ниже.
 +  * Требуется балансировка и резервирование как исходящих направлений,​ так и самих серверов asterisk’a. Так же при выборе исходящего провайдера нужно учитывать что кроме принадлежности к зоне — ещё есть и различная стоимость звонка.
 +  * Обязательно нужно резервирование логики сопровождения звонка. Если оператор не ответит на вызов — зависнет компьютер или раздумает,​ звонок не должен просто отпасть и потеряться. Если при обычном входящем вызове,​ мы получив событие “AgentRingNoAnswer”,​ можем просто выбрать другого оператора,​ то в данном случае мы получаем узкое место в промежутке между ответом оператора и дозвоном до клиента.
 +
 +===== Воплощение в жизнь =====
 +==== Планирование ====
 +
 +Сейчас по прошествии времени уже трудно вспомнить хронологию исполнения,​ но первое что я сразу решил — все планирование должно быть в биллинге. Задачи будут формироваться на основе существующего клиента и его признаков:​ привязанные к нему номера телефонов,​ баланс,​ состояние привязанного к нему оборудования,​ бизнес статистика и прочее. Изначально было понятно какие широкие перспективы дает такая автоматизация для связи с клиентом. Мы можем запросить всех — кто подключив какую либо услугу — не использует её. При выходе новой прошивки,​ обзвонить клиентов,​ чтобы предложить обновится,​ если нет auto provisioning'​a. Предупредить клиентов о проведении плановых работ, или назойливо продвигать дополнительные услуги компании. Но такая задача с технической стороны телефонии просто выглядит как номер телефона и причина,​ по которой мы звоним. ​
 +
 +Мы можем позвонить по банальной проблеме,​ но нам может понадобится и более срочная связь с клиентом — например во время разговора звонок прервался. Нам нужно как можно быстрее связаться с клиентом и решить его вопрос. Поэтому нам нужен такой параметр как приоритет. По нему можно позвонить в любое время и в первую очередь.
 +
 +Каждой задаче — своё решение. Если у нас вопрос административного плана, мы решаем его через абонентский отдел. Технический вопрос решается через техническую поддержку. Соответственно при формировании задачи нужно выбирать какая из служб получит задание.
 +
 +В итоге через внутреннюю логику биллинга была сделана вьюшка,​ из которой можно получить задачу в виде: id задачи,​ номер телефона,​ приоритет и очередь для которой сформированное задание. С моей стороны был написан демон, который постоянно мониторит загрузку очереди — берет текущее количество звонков,​ ожидающих ответа к максимально допустимому в процентах,​ а так же количество свободных операторов. Например,​ для задач с низким приоритетом требуется 0% загрузка очереди и хотя бы 1 свободный оператор. Если приоритет более высокий то загрузка не более 70%. Для более высшего приоритета загрузка не равная 100%. При благоприятных условиях демон получив задачу,​ меняет ей статус на «выполняется» и закидывает звонок в очередь. Номер задачи записывается в локальный кэш демона,​ по которому происходит мониторинг задачи в таблице-словаре в нашей базе вида: id задачи -> dialstatus. Если появился статус — то меняем статус задачи в биллинге и удаляем задачу из словаря и локального кэша. Соответственно при старте демона,​ из биллинга собираются все задачи со статусом “выполняются” для мониторинга их состояния.
 +
 +==== Работа с оператором ====
 +
 +Разрабатывая схему работы с оператором — я выделил основные характеристики:​
 +
 +  * Процесс выбора и соединения с оператором должен проходить максимально быстро. Любой занятый оператор должен быть проверен на незанятость и пригодность для звонка не более чем за 3-5 секунд.
 +  * Если произошел fail и оператор не ответил на звонок или завис компьютер. Необходимо сопроводить звонок обратно в очередь,​ а проблемного оператора вывести из очереди.
 +  * Необходимо отслеживать статус звонка чтобы по завершению вызова проставить статус задаче либо выполнена либо нет.
 +
 +Когда я стал искать решение — то сразу понял что текущая статистика не правильно увидит и не зачтет звонок оператору как успешный,​ кроме того не понятно было как выдёргивать оператора для приёма таких особых звонков,​ поэтому были предприняты попытки сделать костыли типа сделать дозвон оператору и сбриджевать его с плечом клиента,​ а так же различные махинации со слотами очереди. Собранные схемы были рабочие,​ но крайне нестабильные и трудоёмкие при сборе статистики. В итоге я отказался от попыток подстроиться под текущую схему и решил написать независимую часть логики по обработки входящих звонков нового типа. Механизмом для работы с оператором был выбран Originate. Многие в противовес ставят call file безусловно технология прикольная,​ и для своего рода задач имеет право на существование,​ но для моей задачи не подошла бы: для соединения с оператором требуется мгновенный ответ может или нет оператор принять вызов, в случае с файлом нужно мониторить сам файл, что с моей точки зрения не удобно и не рационально. Я просто выполняю ami запрос,​ жду около 5 секунд,​ если оператор снял трубку значит звонок идёт в следующую стадию,​ если нет, то возвращаем звонок в очередь и ищем следующего оператора. Так же для файлов характерна локальная работа на астериске,​ для которого он сформирован. То есть нам нужен будет некий локальный скрипт на каждом сервере,​ который будет локально формировать файл, попутно выясняя кто сейчас из коллег серверов наименее нагружен. Полная децентрализация управления. В моем случае я просто запрашиваю нагрузку по всем серверам и выбрав наименее нагруженный отправляю ему ami запрос. Для работы была использована библиотека Asterisk::​AMI.
 +После того как оператор ответил на вызов, в сформированном звонке через локальные переменные выбирается «exten» для звонка,​ где проставлен таймаут через «Wait» перед «Dial’ом». Основная проблема была в том, что если оператор раздумает в момент таймаута или вызова и положит трубку — то звонок пропадает. Поэтому при поступлении dialstatus’a “Cancel” звонок считается не обработанным и возвращается в очередь. Оператора деактивируем из очереди. Дальше по dialplan’y происходит Dial с дополнительным параметром “g”, чтобы продолжить выполнение после завершения разговора. По завершению таймаута после разговора через «hangup» экстен добавляется запись в словарик — идентификатор задания и dialstatus, insert запросом. Таким образом имея рабочую машину и sip клиента на ней, включив функцию «auto answer» — оператор только комментирует задачи в интерфейсе,​ вся обработка вызова происходит за него. Возможно кто то скажет что это плохая идея, но так сделано по нескольким причинам. Если мы хотим, чтобы клиентам гарантировано пытались дозвонится 30 секунд — то мы просто задаем таймаут для команды dial, а не ждем каких либо действий со стороны оператора. Опять таки мы получаем схему при которой сокращается монотонная работа для людей. Так получилось,​ что в самом начале я работал в этой же компании оператором технической поддержки и я не по наслышке знаю, как утомляют однообразные действия,​ по мимо самих клиентов.
 +
 +==== Балансировка и резервирование ====
 +
 +Если поискать в интернете информацию на тему как резервировать исходящие вызовы при недоступности одного из провайдеров — чаще всего вы найдете рекомендацию типа сделать в диаплане несколько Dial’ов друг за другом. Мне же со своей стороны хотелось сделать,​ что то более гибкое и динамичное. Основная проблема была в том что у нас используется много разных железок и перед формированием звонка нет времени обходить их в поисках наименее нагруженного и доступного транка. Основная идея была в том чтобы работать уже с агрегированной информацией. Для этого был написан отдельный демон, задачи которого сводились к следующему:​ собирать с разного типа оборудования нагрузку,​ с TDM разность занятых слотов к свободным,​ с voip провайдеров доступность через sip peers и на основе текущих каналов с астерисков выбирать кто больше из ни используется. Но помимо выбора направления надо учитывать что любой из транков может упасть,​ поэтому надо выбрать альтернативу. В моём случае это были voip провайдеры,​ но у них так же существуют свои расценки,​ поэтому балансировка должна выбирать с наиболее дешёвых к дорогим (по качеству они абсолютно равнозначны). Для такой выборки я сделал для каждого провайдера свой вес или свою стоимость. Таким образом получился список из ключей и значений такого вида:
 +  * Местная зона => 10
 +  * Voip провайдер А => 20
 +  * Voip провайдер B => 30
 +  * Voip провайдер C => 40
 +
 +Так если один из провайдеров не доступен — мы ищем ему замену с низшего к более высокому,​ или дорогому.
 +
 +Но по мимо этого существует ещё и распределение в зависимости от принадлежности номера. То есть на номера Москвы например следует звонить через своего местного провайдера,​ в Сибирь или другие страны через своего,​ если такой имеется,​ либо через междугороднее и международное направление. Здесь возникает потребность определить к какому региону относится номер. Для этого, я написал парсер реестра росвязи в базу. Выглядит как:
 +<​code>​
 +mysql> select local.get_region(903599****);​
 ++-------------------------------------+
 +| voicecon_new.get_region(903599****) |
 ++-------------------------------------+
 +| Moskva i Moskovskaya oblast ​        |
 ++-------------------------------------+
 +</​code>​
 +
 +==== Общая схема работы и различные нюансы ====
 +
 +Для каждой задачи на текущий момент можно задать количество попыток,​ которое декрементируется каждым статусом «не выполнено». Через опять таки задаваемый таймаут задание снова вернется на выдачу.
 +
 +Так же недавно появилась возможность приоритетно сделать дозвон через заданного провайдера,​ если он не перегружен и доступен.
 +Демон для агрегации нагрузки по voip провайдерам ориентируется по префиксу в названии sip каналов. Можно было бы использовать например группы,​ но пока всё прекрасно работает.
 +Он же периодически пингает как Voip провайдеры так и все железки на пути до транков,​ если какая из них не доступна — провайдер выводится из балансировки.
 +После выбора свободного оператора — он помечается как занятый и происходит следующая логика:​
 +  - Мы запрашиваем все asterisk сервера,​ находящиеся в балансировке.
 +  - Для текущей очереди запрашиваются параметры обзвонов. Это время ожидания ответа от оператора и клиента,​ «source» номер который увидит клиент при входящем звонке — для каждых служб он разный.
 +  - По номеру клиента выбираем направление через таблицу с реестром номеров.
 +  - Выбираем аплинк для вызова. Если выставить 0 — аплинк не будет участвовать в выборе,​ соответственно можно балансировать сдвигая приоритет провайдерам. Выбранный транк проверятся на доступность а затем на загрузку. Если условия не соблюдены,​ аплинк пропускается и мы переходим к следующему по весу.
 +  - Далее мы выбираем asterisk сервер с наименьшим количеством каналов. По скольку в Redis'​e у нас хранятся все каналы со всех серверов,​ мы просто получаем общее количество через встроенную функцию hlen. Выбрав сервер,​ мы пытаемся к нему подключится — если сервер не ответил,​ мы берём следующий,​ опять с наименьшей нагрузкой.
 +  - На последок для параметра «variable» формируется массив из служебных переменных,​ на основе которых происходит как учёт звонка,​ так и выбор направления. Так же там используется привязка текущего звонка оператора к задаче по которой происходит обзвон.
 +
 +
 +===== Заключение =====
 +
 +Не смотря на заявленную нагрузку сейчас цифра достигла около 30785 звонков за последний месяц. Изначально схема планируемая только для узкого круга задач, разрослась до сервиса способного решить обширный круг задач и справится с большой нагрузкой. ​
avtomaticheskaja_sistema_obzvona_klientov.txt · Последние изменения: 2013/11/18 17:27 (внешнее изменение)
GNU Free Documentation License 1.3
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0 Яндекс.Метрика