Очереди(queues)

Все хоть раз слышал «Извините, в данный момент все операторы заняты» знайте это работа Automatic Call Distribution (ACD) Queues. Давайте сделаем и себе такое)
Для построения call-центра нам понадобится Астериск и логика. Следует уяснить раз и навсегда, когда абонент звонит в поддержку, он хочет получить помощь от ЧеЛоВеКа!!!!111, и пофигу на Ваше красивое голосовое меню)

Для начала стоит уяснить некоторые понятия:
member — это телефон или другое устройство
agent — оператор, который обрабатывает звонки
В принципе эти два понятия схожи, и с некоторой оговоркой их можно воспринимать как синонимы.
Переходим к созданию очередей, правим queues.conf
Создадим 2 очереди, с помощью шаблона, [sales] и [support].

[general]
autofill=yes             ; распределять всех ожидающих абонентов, по доступным операторам
shared_lastcall=yes      ; дать паузу оператору,который залогинен в нескольких очередях, 
                         ; после последнего вызова например закрытия заявки в теркере.

[StandardQueue](!)       ; Шаблон для создания очередей
musicclass=default       ; устанавливаем музыку во время ожидания
strategy=rrmemory        ; Стратегия распределения звонков между операторами 
                         ; напр(Round Robin Memory)
joinempty=no             ; не включать абонентов в очередь если в ней не 
                         ; зарегистрировано ни одного оператора 
leavewhenempty=yes       ; покинуть очередь если нет доступных операторов
ringinuse=no             ; не звенеть оператору если статус InUse. 
                         ; Оператор не может распылятся на 100500 абонентов 

[sales](StandardQueue)   ; создаем очередь на основе шаблона
                      
[support](StandardQueue) ; создаем очередь на основе шаблона
           

Перезагрузить модуль:

*CLI> module reload app_queue.so

Проверка очередей:

*CLI> queue show

Добавить оператора с помощью CLI:

*CLI> queue add member <channel> to <queue> [[[penalty <penalty>] as <membername>] state_interface <interface>]

где channel это канал который подкл, например SIP/0000FFFF0003
queue имя очереди, например support или sales
Удаление оператора через CLI

*CLI> queue remove member SIP/0000FFFF0001 from support 
Removed interface 'SIP/0000FFFF0001' from queue 'support'

С помощью CLI(queue pause member и queue unpause member ) можно также поставить на паузу/снять с паузы оператора

*CLI> queue pause member SIP/0000FFFF0001 queue support reason DoingCallbacks
paused interface 'SIP/0000FFFF0001' in queue 'support' for reason 'DoingCallBacks'
*CLI> queue unpause member SIP/0000FFFF0001 queue support reason off-break
unpaused interface 'SIP/0000FFFF0001' in queue 'support' for reason 'off-break'

Параметр penalty: Очередь вызовов могут обрабатывать люди с разными обязанностями и, следовательно, у одних это обязанность основная , а у других — второстепенная, и основную нагрузку по приему вызовов нам надо возложить на одних людей, а для подстраховки — других (к примеру, если у нас есть очередь, куда поступают вызовы от клиентов, которые хотят что-то купить, то основная обязанность ее обработки ложиться на менеджеров, следовательно, их определяем без пенальти. Еще некоторые люди могут принимать звонки, если у менеджеров полный завал, с пенальти 1, и, с пенальти 2, мы можем определить агентов совсем уж не относящихся к продажам, например, телефон в службе технической поддержки).

Правим extensions.conf для возможности подключить очередь

[Queues]
exten => 7001,1,Verbose(2,${CALLERID(all)} entering the support queue)
same => n,Queue(support)
same => n,Hangup()

exten => 7002,1,Verbose(2,${CALLERID(all)} entering the sales queue)
same => n,Queue(sales)
same => n,Hangup()

Для управления входом/выходом операторов из очереди, с помощью диалплана, будем использовать приложения:
AddQueueMember()
RemoveQueueMember()
А также возможность временного выхода из очереди(пауза)
PauseQueueMember()
UnpauseQueueMember()

Добавим этот функционал в диалплан

[QueueMemberFunctions]

exten => *54,1,Verbose(2,Logging In Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,AddQueueMember(support,${MemberChannel})

; ${AQMSTATUS}
;   ADDED
;   MEMBERALREADY
;   NOSUCHQUEUE

exten => *56,1,Verbose(2,Logging Out Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,RemoveQueueMember(support,${MemberChannel})

; ${RQMSTATUS}:
;    REMOVED
;    NOTINQUEUE
;    NOSUCHQUEUE

exten => *72,1,Verbose(2,Pause Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,PauseQueueMember(support,${MemberChannel})

; ${PQMSTATUS}:
;     PAUSED
;     NOTFOUND

exten => *87,1,Verbose(2,Unpause Queue Member)
   same => n,Set(MemberChannel=${CHANNEL(channeltype)}/${CHANNEL(peername)})
   same => n,UnpauseQueueMember(support,${MemberChannel})

; ${UPQMSTATUS}:
;     UNPAUSED
;     NOTFOUND

Опции секции [general] в queues.conf

Опция Значение Описание
persistentmembers yes, no Для хранения динамически добавляемого оператора в AstDB так что они могут быть вновь добавлены после перезагрузки Asterisk
autofill yes, no Распределять всех ожидающих абонентов, по доступным операторам
monitor-type MixMonitor, незадано Задает приложение для записи разговоров
updatecdr yes, no Запишет в CDR в dstchannel имя оператора,
shared_lastcall yes, no Позволяет Дать паузу оператору,который залогинен в нескольких очередях, после последнего вызова например закрытия заявки в теркере, поскольку его последний звонок учитывается во всех очередях

Опции доступные при описании очереди:

Опция Значение Описание
musicclass Класс MOH описанный в musiconhold.conf Устанавливает класс MOH для конкретной очереди. Может быть переназначено с помощью переменной CHANNEL(musicclass)
announce Имя файла анонса Этот файл воспроизводится агенту, который принимает звонок
strategy ringall, leastrecent, fewestcalls, random, rrmemory, linear, wrandom ringall — звонить всем доступным операторам (по умолчанию)
leastrecent: звонить оператору который дольше всех не отвечал на звонки.
fewestcalls: оператору обработавшему наименьшее к-во звонков
random: произвольному оператору
rrmemory: по кругу(round-robin)
linear: в заданном порядке, всегда обрабатывается с начала списка, первый будет работать не разгибая спины)
wrandom: произвольному оператору, но используя penalties как веса.
servicelevel значение в сек Для определения уровня обслуживания, отвечен ли звонок вовремя
context Dialplan context Позволяет звонящему выйти из очереди, с помощью одного DTMF символа
penaltymemberslimit больше 0 Для пренебрежения penalty, если значение операторов ниже заданного значения
timeout Значение в сек Время сколько звенеть оператору)
retry Значение в сек Определяет время ожидания перед попыткой следующего оператора в очереди, при истечении таймаута предыдущего оператора
timeoutpriority app, conf Контролирует 2 возможных таймаута, для оператора и глобальный для всей очереди(app), кому отдать приоритет
weight Больше или равно 0 Подразумевает приоритет вызова, для гарантии того, что вызов, ожидающий в очереди с более высоким приоритетом, будет обработан одним из первых. Будет задерживаться обработка вызовов только для менее приоритетных очередей, если оператор, который обрабатывает очередь, уже занимается вызовом из более высокоприоритетной очереди.
wrapuptime Значение в секундах значение минимального промежутка времени, с момента, когда работа с абонентом завершена и до того, как участник обработки может принять новый вызов из очереди.
autofill yes, no Распределять всех ожидающих абонентов, по доступным операторам
autopause yes, no, all Вкл/выкл автопаузу для операторов, кто не принял вызов. all автопауза для всех очередей, в каких присутствует данный оператор.
maxlen Больше или равно 0 Максимальное число ожидающих в очереди, 0-неограниченно
setinterfacevar yes, no Если yes, то следующие переменные будут заданы до установки связи звонящего и оператора:
MEMBERINTERFACE: Интерфейс оператора, например: Agent/1234
MEMBERNAME: имя оператора
MEMBERCALLS: количество принятых вызовов
MEMBERLASTCALL: время последнего вызова
MEMBERPENALTY: penalty оператора
MEMBERDYNAMIC: динамически ли был добавлен оператор или нет
MEMBERREALTIME: оператор был добавлен из real time или нет
setqueueentryvar yes, no Если yes, то следующие переменные будут установлены до соединения:
QEHOLDTIME: время ожидания в очереди
QEORIGINALPOS: позиция какую первоночально занимает звонящий
setqueuevar yes, no Если yes, то следующие переменные будут установлены до соединения:
QUEUENAME: имя очереди
QUEUEMAX: максимальное количество звонков разрешенное в этой очереди
QUEUESTRATEGY: стратегия распределения вызовов в очереди
QUEUECALLS: количество звонков в очереди в текущий момент
QUEUEHOLDTIME: среднее время ожидания в очереди
QUEUECOMPLETED: к-во завершенных звонков в очереди
QUEUEABANDONED: к-во неуспешных звонков
QUEUESRVLEVEL: уровень обслуживания в очереди
QUEUESRVLEVELPERF: текущий уровень обслуживания
membermacro Имя макроса определенного в диалплане Макрос будет выполнен при соединении оператора со звонящим
announce-frequency Значение в секундах Как часто абоненту, ожидающему в очереди вызовов, сообщать его позицию и/или приблизительное время ожидания обработки его вызова (0=выключено)
min-announce-frequency Значение в секундах Минимальный промежуток времени между анонсами
periodic-announce-frequency Значение в секундах Интервал периодических анонсов
random-periodic-announce yes, no Будет играть определенный анонс в произвольном порядке
relative-periodic-announce yes, no Если yes, periodic-announce-frequency таймер начнет отсчет во время окончания воспроизведения записи, вместо того чтобы начать сначала. По умолчанию нет. Например у нас установлено воспроизводить запись каждые 45 секунд, а продолжительность записи 30 секунд, если стоит no, то через 15 секунд снова начнется воспроизведение
announce-holdtime yes, no, once Играть ли время ожидания во время периодических анонсов
announce-position yes, no, limit, more yes — воспроизводить позицию
no — не воспроизводить позицию
limit — если позиция в очереди в пределах announce-position-limit
more — если позиция в очереди превышает announce-position-limit
announce-position-limit Больше равно 0 используется в связке с предыдущей опцией
announce-round-seconds К-во секунд Анонсим приблизительное(округленное) к-во секунд, например вместо “1 минута и 23 секунды” мы округляем до ближайшего 30сек интервала, в данном случае будет 1 минута 30 секунд.
queue-thankyou Имя файла Если не указано, то играет «Спасибо за оказанное внимание.» если пусто, то не играет ничего
queue-youarenext Имя файла Если не задано играет «Ваш вызов в настоящий момент первый в очереди. Оставайтесь на линии, вас соединят с первым освободившимся оператором.», если пусто, то не играет ничего
queue-thereare Имя файла Если не задано играет «В настоящий момент вы находитесь в очереди…», если пусто, то не играет ничего
queue-callswaiting Имя файла Если не задано играет «Ожидайте ответа.», если пусто, то не играет ничего
queue-holdtime Имя файла Если не задано играет «Оставшееся время ожидания в очереди составляет», если пусто, то не играет ничего
queue-minutes Имя файла Если не задано играет «минут», если пусто, то не играет ничего
queue-seconds Имя файла секунд
queue-reporthold Имя файла Время ожидания…
periodic-announce Имя файла Если не задано, то играет «В настоящий момент все операторы заняты. Пожалуйста, оставайтесь на линии, вас соединят с первым освободившимся оператором»
monitor-format gsm, wav, wav49, допустимый формат файлов Формат записи диалогов
monitor-type MixMonitor, не задано Задает приложение для записи разговоров, если не задано используется Monitor
joinempty paused, penalty, inuse, ringing, unavailable, invalid, unknown, wrapup Опции могут быть перечислены через запятую, при каких считать оператора недоступным
leavewhenempty paused, penalty, inuse, ringing, unavailable, invalid, unknown, wrapup Удалять все вызовы, поступившие в очередь, у которой нет участников или агентов, для ее обработки, задаются условия при которых считать что оператор недоступен
eventwhencalled yes, no, vars yes следующие события будут переданы в (AMI):
AgentCalled
AgentDump
AgentConnect
AgentComplete
vars — все переменные канала ассоциированные с агентом будут также переданы в AMI.
eventmemberstatus yes, no yes — QueueMemberStatus будет передано в AMI. !!Может сгенерировать много менеджмент событий
reportholdtime yes, no Обьявлять время ожидания до соединения
ringinuse yes, no Не звонить оператору который уже разговаривает, работает только на SIP
memberdelay Знач в сек время задержки между моментом, когда агент отвечает на вызов и соединения его с вызывающим абонентом
timeoutrestart yes, no Если параметр «timeoutrestart» установлен в значение yes, тогда значение таймаута для агента будет сбрасываться, если от него будет получен сигнал BUSY или CONGESTION. Это бывает полезно, когда агент имеет возможность отметить вызов, отвергая его или, производя некоторые действия, которые имеют подобный эффект. (Обнаружено, что если вызов агента завершился со статусом NOANSWER (ring, no-answer), это так же заставляет вызов отправить к следующему агенту в очереди по алгоритму roundrobin).
defaultrule Правило в queuerules.conf Ассоциирует правило очереди в queuerules.conf к данной очереди, которое будет использоваться для динамического изменения мин и макс penalties, которая используется для выбора доступных агентов
member Устройство Для статического задания оператора в очереди. Technology/Device_ID Пример: Agent/1234, SIP/0000FFFF0001, DAHDI/g0/14165551212).

Приоритет очереди
Бывает так, что нужно перевести звонящего из одной очереди в другую, но с наименьшим временем ожидания, поскольку человек уже достаточно наобщался(наждался) в предыдущей очереди), для этого используется опция weight.
Например есть 2 очереди с разными weight: support и support-priority.
Включив агентов в обе очереди можем динамически распределять нагрузку, но есть одно но, пока более приоритетная очередь не очистится, звонки в менее приоритетной будут без ответа, поэтому делают иерархию операторов, некоторые отвечают на звонки сугубо менее приоритетной очереди.
С помощью приоритетов мы задаем операторов, которые в случае чего придут на выручку основным операторам очереди, задается следующим образом:
Для трех очередей

 
[support](StandardQueue)
member => SIP/0000FFFF0001,0,James Shaw         ; preferred
member => SIP/0000FFFF0002,10,Kay Madsen        ; second preferred
member => SIP/0000FFFF0003,20,Danielle Roberts  ; least preferred

[sales](StandardQueue)
member => SIP/0000FFFF0002,0,Kay Madsen
member => SIP/0000FFFF0003,10,Danielle Roberts
member => SIP/0000FFFF0001,20,James Shaw

[billing](StandardQueue)
member => SIP/0000FFFF0003,0,Danielle Roberts
member => SIP/0000FFFF0001,10,James Shaw
member => SIP/0000FFFF0002,20,Kay Madsen

Из диалплана приоритеты задаются с помощью AddQueueMember

; AddQueueMember(queuename[,interface[,penalty[,options[,membername
; [,stateinterface]]]]])
   same => n,AddQueueMember(support,${MemberChannel},${QueuePenalty},,${MemberName})

Используя queuerules.conf можно динамически управлять Penalties с помощью переменных QUEUE_MIN_PENALTY и QUEUE_MAX_PENALTY. Можно указать какие операторы, с какими Penalties могут принимать звонки.

Изменяем минимальный penalty до 1 максимальный до 5 если звонящий ждет более 60 секунд.

[more_members]
penaltychange => 60,5,1
;
;можно еще так
;после 30 сек увеличиваем макс penalty на 1(станет 5)
;после 45 сек уменьшаем мин penalty на 1(станет 1)
; итд...
penaltychange => 30,+1
penaltychange => 45,,-1
penaltychange => 60,+1
penaltychange => 120,+2

Проверить правила:

*CLI> queue show rules
Rule: more_members
   After 60 seconds, adjust QUEUE_MAX_PENALTY to 5 and adjust QUEUE_MIN_PENALTY to 1
*CLI> queue show rules
Rule: more_members
  After 30 seconds, adjust QUEUE_MAX_PENALTY by 1 and adjust QUEUE_MIN_PENALTY by 0
  After 45 seconds, adjust QUEUE_MAX_PENALTY by 0 and adjust QUEUE_MIN_PENALTY by -1
  After 60 seconds, adjust QUEUE_MAX_PENALTY by 1 and adjust QUEUE_MIN_PENALTY by 0
  After 120 seconds, adjust QUEUE_MAX_PENALTY by 2 and adjust QUEUE_MIN_PENALTY by 0

Изменим наш Диалплан, чтобы он подхватывал правила изменения:

[Queues]
exten => 7000,1,Verbose(2,Entering the support queue)
   same => n,Set(QUEUE_MIN_PENALTY=2)   ; set minimum queue member penalty
   same => n,Set(QUEUE_MAX_PENALTY=4)   ; set maximum queue member penalty

; Queue(queuename[,options[,URL[,announceoverride[,timeout[,AGI[,macro
; [,gosub[,rule[,position]]]]]]]]])
   same => n,Queue(support,,,,,,,,more_members)  ; entering queue with minimum and 
                                                 ; maximum member penalties

Таймеры:
Всякое бывает и может так случится, что звонок из очереди по таймауту выйдет в диалплан. Рассмотрим случай
Абсолютный таймаут = 10 сек,
Длительность звонка оператору = 5сек
Пауза между попытками позвонить другому оператору= 4сек.
Мы звоним 1му оператору 5 секунд, потом ждем 4 секунды, и ….
А) звоним след оператору 1 секунду и выходим
Б) звоним полные 5 секунд оператору и выходим
Это задается с помощью timeoutpriority.
Случай А) = app
Случай Б) = conf

Local channels
Для «более лучшей» гибкости можно использовать local channels

Добавляется аналогично

member => Local/SIP-0000FFFF0001@MemberConnector

Следует описать контекст MemberConnector

[MemberConnector]
exten => _[A-Za-z0-9].,1,Verbose(2,Connecting ${CALLERID(all)} to Agent at ${EXTEN})

   ; filter out any bad characters, allowing alphanumeric characters and the hyphen
   same => n,Set(QueueMember=${FILTER(A-Za-z0-9\-,${EXTEN})

   ; assign the first field of QueueMember to Technology using the hyphen separator
   same => n,Set(Technology=${CUT(QueueMember,-,1)})

   ; assign the second field of QueueMember to Device using the hyphen separator
   same => n,Set(Device=${CUT(QueueMember,-,2)})

   ; dial the agent
   same => n,Dial(${Technology}/${Device})
   same => n,Hangup()

При использовании, Local channel как оператора, Queue() не обязательно знать состояние звонка, особенно если локальный канал оптимизирован. Queue будет мониторить состояние локального канала, а не реальное устройство.
Но мы можем передать в queue() реальное устройство(только SIP):

[support](StandardQueue)
member => Local/SIP-0000FFFF0001@MemberConnector,,,SIP/0000FFFF0001

Добавление/удаление оператора с локальным каналом:

[QueueMemberLogin]
exten => 500,1,Verbose(2,Logging in device ${CHANNEL(peername)} into the support queue)

   ; Save the device's technology to the MemberTech channel variable
   same => n,Set(MemberTech=${CHANNEL(channeltype)})
 
   ; Save the device's identifier to the MemberIdent channel variable
   same => n,Set(MemberIdent=${CHANNEL(peername)})

   ; Build up the interface name and assign it to the Interface channel variable
   same => n,Set(Interface=${MemberTech}/${MemberIdent})

   ; Add the member to the support queue using a Local channel. We're using the same
   ; format as before, separating the technology and the device indentifier with
   ; a hyphen and passing that information to the MemberConnector context. We then
   ; use the IF() function to determine if the member's technology is SIP and, if so,
   ; to pass back the contents of the Interface channel variable as the value to the
   ; state interface field of the AddQueueMember() application.
   ;
   ; *** This line should not have any line breaks
   same => n,AddQueueMember(support,Local/${MemberTech}-${MemberIdent}
@MemberConnector,,,${IF($[${MemberTech} = SIP]?${Interface})})
   same => n,Playback(silence/1)

   ; Play back either the agent-loginok or agent-incorrect file, depending on what
   ; the AQMSTATUS variable is set to.
   same => n,Playback(${IF($[${AQMSTATUS} = ADDED]?agent-loginok:agent-incorrect)})
   same => n,Hangup()

Лог очереди хранится в /var/log/asterisk/queue_log
Параметры задаются в logger.conf

queue_log Включено или нет логирование очереди (по умолчанию yes).
queue_log_to_file нужно ли писать в файл даже если включена поддержка реалтайма(по умолчанию no).
queue_log_name имя файла. По умолчанию queue_log.
queue_log это разделенный пайпами | список ссобытий. Поля в файле:

Дата|UID звонка|имя очереди|Имя соединенного канала|тип события|параметры события(опционально)

1292284222|MANAGER|7100|Local/9990@MemberConnector|REMOVEMEMBER|
1292284222|MANAGER|7200|Local/9990@MemberConnector|REMOVEMEMBER|
1292284491|MANAGER|7200|Local/9990@MemberConnector|ADDMEMBER|
1292284519|psy1-1292284515.93|7100|NONE|ENTERQUEUE||4165551212|1
1292284519|psy1-1292284515.93|7100|Local/9996@MemberConnector|RINGNOANSWER|0
1292284521|psy1-1292284515.93|7100|Local/9990@MemberConnector|CONNECT|2
|psy1-1292284519.96|0
1292284519|psy1-1292284515.93|7100|NONE|ENTERQUEUE||4165551212|1

ID Может быть не уникальным. В некоторых случаях АМI может чет делать, в таких случаях появляются записи с MANAGER

queue
Более детально о queues realtime Тут


Комментарии: