Реализация оценки работы операторов в Elasix

в 14:34, , рубрики: Песочница, метки: ,

В 2012 году на Хабре был опубликован пост об организации системы оценки операторов. Весьма полезный материал. Руководствуясь оным, попробовал реализовать такую функцию у себя. Структуру БД оставил такую же, как в примере, с полями для идентификации звонящего, взявшего звонок, очереди, оценки и времени внесения записи. Работает все на Elastix, потому что есть некоторые особенности, связанные с требованием не вмешиваться в дефолтные конфигурационные файлы.

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

Соответственно, в extensions_custom.conf вписываем это в виде отдельного контекста, для корректной очередности обработки экстеншенов астериском.

Кусок отвечающий за обработку нажатий кнопок при оценке. Обрабатываются нажатия 1-5.

[opinion-inc]; «функция» для фиксации кнопки
exten => _[1-5],1,Set(MYEXT=${EXTEN}); устанавливаем переменную с нажатой кнопкой
exten => _[1-5],n,Background(thanks); подтверждаем, что приняли оценку
exten => _[1-5],n,Playtones(425/350,0/350); проигрываем отбой чтобы клиент не висел на линии
exten => _[1-5],n,Wait(5); без этого не слышно тона
exten => _[1-5],n,Goto(opinion,${MAIN-EXT},WriteBase); записываем в БД

Относительно того, что не применил команду Read(), которая имеет лучший функционал. У меня она просто не срабатывала.

Контекст для проигрывания приветствия и отвечающей за реализацию записи в БД выглядит так. Усложнение связано с тем, что односимвольные экстеншены попадают под более общий шаблон _Х. Для того, чтобы они обрабатывались отдельно, приходится выносить их в отдельный контекст, описанный выше.

Большое количество переменных объясняется тем, что просто с строками часто не срабатывает, да и читается/контролируется так проще.

[opinion] ;###########################################
include=> opinion-inc; в этом контексте обрабатываются одиночные нажатия кнопок на телефоне, а ниже обрабатываются последовательности
exten => _X.,1,NoOp(${CDR})
exten => _X.,n,Set(MYEXT=0); оценка по умолчанию, такая оценка в базу не пишется
exten => _X.,n,Set(MAIN-EXT=${EXTEN}); запоминаем текущий экстеншен

exten => _X.,n,Set(TIMEOUT(digit)=3); таймаут для обработки цифр
exten => _X.,n,Set(TIMEOUT(response)=4); таймаут для обработки бездействия

exten => _X.,n,Background(opinion); просьба оценить разговор
exten => _X.,n,WaitExten(); при получении результата нажатия начинается его поиск с начала текущего контекста, т.е. проверяется соответствие в opinion-inc

exten => _X.,n,GotoIf($["${MYEXT}"=«0»]?${MAIN-EXT},Hgp:${MAIN-EXT},WriteBase); записываем в БД предварительно проверив значение введенной цифры, «0» не пишем
exten => _X.,n(WriteBase),Set(MSQL=INSERT INTO opinion (`callerid`, `exten`, `queues`, `opinion`,`time` ) VALUES (${CALLERID(num)}, ${MAIN-EXT}, ${NODEST}, ${MYEXT}, ${STRFTIME(${EPOCH},,"%Y-%m-%d %H:%M:%S")})); формируем строку запроса к БД
exten => _X.,n,MYSQL(Connect connid localhost login password statbase); открываем соединение
exten => _X.,n,MYSQL(Query resultid ${connid} ${MSQL}); выполняем запрос
exten => _X.,n,MYSQL(Disconnect ${connid}); закрываем соединение

exten => _X.,n,Goto(h,Hgp); закрываем
exten => i,1,Playback(goodbye)
exten => i,n,Hangup; если пользователь нажал не то просто закрываем

exten => h,1,GotoIf($["${MYEXT}"=«0»]?h,Hgp:${MAIN-EXT},WriteBase); записываем в БД предварительно проверив значение введенной цифры
exten => h,n(Hgp),Hangup

Для того, чтобы все вышеприведенное работало корректно, необходимо внести изменения в работу очередей. Делаем дополнительный контекст в extensions_custom.conf.

Команда DIAL(SIP/${EXTEN},,trg) приводит к тому, что звонок передается агенту без учета его статуса. И даже если он занят, queue благополучно перекидывает звонящего на агента и он слышит сигналы о вызове на вторую линию. Что крайне неприятно, во-первых, а во-вторых, вместо того, чтобы пытаться вызвать другого, абонент зависает до того, как агент не закончит текущий разговор. Вот эту проблему и обходим.

[opinion-ivr]
exten => _X.,1,Set(Used=${DEVICE_STATE(SIP/${EXTEN})})
exten => _X.,n,NoCDR(); без этого БД CDR начинает забиваться ненужными данными о проверке статуса
exten => _X.,n,GotoIf($["${Used}"=«INUSE»]?NO:Dl); проверяем статус агента, и если он не «INUSE» вызываем
exten => _X.,n(Dl),DIAL(SIP/${EXTEN},,trg); опция g позволяет звонку пройти далее по диалплану
exten => _X.,n,GotoIf($["${DIALSTATUS}"=«ANSWER»]?Opnn:NO); если звонок был отвечен то переходим к оценке, иначе ничего не делаем
exten => _X.,n(Opnn),Goto(opinion,,1); переход к этапу оценки разговора
exten => _X.,n(NO),NoOp(${DIALSTATUS})

Если теперь сформировать очередь в GUI Elastix, то это работать не будет, поскольку по умолчанию там происходит регистрация как Local/###@from-queue/n, а нам надо Local/###@opinion-ivr/n.

Для этого пришлось сделать регистрацию в очередях в обход встроенной системы. Для этого делаем так, чтобы человек вручную регистрировался, набирая номер вида 90###. В примере очередь имеет номер 120.

Все в том же extensions_custom.conf:

[from-internal-custom]
; регистрация
exten => 90120,1,Set(Qid=${CALLERID(num)}); выясняем экстеншен будущего участника очереди
exten => 90120,n,AddQueueMember(120,Local/${Qid}@opinion-ivr/n,0); регистрируем
exten => 90120,n,Playback(agent-loginok); подтверждаем, что регистрация принята
exten => 90120,n,Hangup()


  1. Матвей:

    Блин зачем два раза записывать в базу данных одно и тоже значение. Удалил строчку _[1-5],n,Goto(opinion,${MAIN-EXT},WriteBase);
    И ели нашел ошибку в синтаксисе.
    exten => _X.,n,GotoIf($[${DIALSTATUS}=ANSWER]?Opnn:NO) .
    А так спасибо.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js