Microsoft and Nuance a Brilliant Play for Redmond

Bloomberg reports that Microsoft “is in advanced talks” to purchase Nuance. I’ve not been able to stop thinking of this move and I’ll be the first to admit that it surprised me, but the more I think about it and the more I talk to others in the industry this is an absolutely brilliant move. Here are my crystal ball predictions:

The patent play: Nuance comes with over 2000 patents. This is a huge cache which will no doubt be useful for the upcoming AI wars. This will be a huge boosts to Microsoft’s already huge R&D commitment in this space.

This hospital bill is brought to you by Microsoft: Nuance makes the majority of their money from the healthcare sector. We’re not talking just dictations or document management, we’re talking EHR, billing, and diagnosis software. Windows and Office are already prevalent in the healthcare space, this puts MS in the heart (get it?) of hospital operations and processes.

Cortana, it was the best of times it was the worst of times: Did you know that Windows 10 was Cortana’s big debut in the desktop space? Yeah neither did anyone else. Cortana began in 2014 as a direct competition to Alexa which was released the year before. At the time Microsoft was beginning to make some heavy bets in to the mobile space with Windows mobile. Well it’s a decade later and Windows mobile is dead, Cortana’s OS integration has been neutered and I’ve never seen another human being speak to their Windows PC. I believe this is going to change that with a huge marketing push of some college student dictating their final paper to their Microsoft Surface device while getting a manicure.

Where we’re going we don’t need passwords: Nuance comes with perhaps one of the oldest if not best speech biometrics software. Imagine joining a Microsoft Teams meetings where you call in and start speaking and you’re authenticated immediately. Or allow for “signatures” based solely on your voice. Verification and authentication continue to be huge and the rise of better and better “deep fake” technology will allow for some sort of trust verification service with Nuance biometrics in the middle of it.

(Part 1) We’re taking our ball and going home: This one is near and dear to me heart. If a Cisco call center customer wanted to have speech recognition or text to speech there was only one name in town. Nuance. This has changed a bit in the last few years with the introduction of LumenVox as an additional option. And this has changed even further in the last 18 months with Cisco supporting Google’s DialogFlow, but Nuance still reigns supreme. I can see MS increasing the pricing of an already very expensive product making it prohibitive for some call centers to run their software.

(Part 2) I can see clearly now the rain is gone: Did you know that Azure stands for the color blue of a cloudless sky? Microsoft will be able to create a very defensible moat around their Azure offerings by being the only provider to have the latest and greatest Nuance services. In addition, some telephony cloud provider, who are already battling Amazon and who white label Nuance products as part of their offering, might be forced to either consume more Azure resources to get better pricing or completely get priced out from this technology and watch the competition pass them by. This sets the stage for Microsoft to be in the driver’s seat of what UC or CC provider you might choose next if you have an already deep Nuance integration or if your call center must use Nuance.

 

 

 

 

 

 

What “Zoom Fatigue” can teach us about using video in the contact center

My significant other researches human behavior at work and she brought this study to my attention. She thought it would be very relevant to what I do. I want to summarize some key points and how they relate to the contact center, but first, I really wanted to title this post as “Why video will never kill the phone contact center star”, but that seemed too childish.

So what can Zoom Fatigue teach us about using video in the call center? Let me highlight a few key points from the article.

…in one-on-one meetings conducted over Zoom, coworkers and friends are maintaining an interpersonal distance reserved for loved ones.

Is your customer relationship what you would consider intimate? I can’t think of a single service or product where I would use that term, so the answer is more than likely no. Now, imagine having to handle customer video calls all day and feeling your personal space invaded. It would be exhausting for agents and off-putting for customers. Video calls should be reserved for customers with a long-standing relationship and limited to a few key agents who know the customer well. Additionally, considerations should be made around how many video calls an agent should handle in a short amount of time.

One of the remarkable aspects of early work on nonverbal synchrony (i.e., Kendon, 1970) is how nonverbal behavior is simultaneously effortless and incredibly complex. On Zoom, nonverbal behavior remains complex, but users need to work harder to send and receive signals.

Processing these extra nonverbal signals contributes to what my significant other and other researchers call “cognitive load” or the amount of information our brains can process at any given time. Video calls divert precious mental resources away from the task at hand, making it more likely your agents will make mistakes on complex tasks like financial services or billing. Traditional audio-only phone calls enable them to focus better while doing their work.

There is no data on the effects of viewing oneself for many hours per day. Given past work, it is likely that a constant “mirror” on Zoom causes self-evaluation and negative affect.

Self-view is very distracting for me, however that’s the only way for me to know if I’m in view or not as I use a standing desk. In addition to being distracting, this article argues that it is also stressful. If your agents are handling video calls, consider the ability to turn on and off self-view. Vendors should come up with a technology solution which notify the users when they are out of view without relying on the equivalent of looking in the mirror all day long.

…cultural norms are to stay centered within the camera’s view frustrum and to keep one’s face large enough for others to see. In essence users are stuck in a very small physical cone, and most of the time this equates to sitting down and staring straight ahead.

We first had handsets and they were terrible to hold and work at the same time. We then got wired headsets and life was better, but we needed to make sure not to get tangled or have someone kick them. We then moved on to wireless headsets and we got freedom! … only to have it taken away by video that creates a “lock in” effect. You can no longer just stand up and stretch. You can’t just run to the fax machine or to refill your water bottle. If your agents collaborate in a team to handle customer requests or handle calls which can be very lengthy avoid having them on camera.

~david

UCCE Call Flow Outbound Option with SIP Proxy

Cisco UCCE Outbound Dialer IVR Campaign Log Walkthrough

I wanted to document this specifically as I feel it’s the most complex flow you will see within UCCE. Before you go through this you will absolutely want to get familiar with the documentation as there are a lot of moving pieces. The diagram below comes straight from said documentation and should help you visualize what you see the logs doing.

 

UCCE Call Flow Outbound Option with SIP Proxy

UCCE Call Flow Outbound Option with SIP Proxy

Ideally I would have had all the logs for all the devices at the same time, but unfortunately that wasn’t the case. You will notice that the timestamps jump around as some logs are from other time frames. However, I’ve tried to match up all unique identifiers across all the logs so you can follow it along. The unique identifiers you want to take note of are:

2145551234: Customer phone number campaign is going to dial.
016: The dialer port.
10241: Correlation ID of initial call to customer.
10242: Correlation ID of call to IVR.

1. An unattended IVR campaign starts. Customer records are delivered to the Dialer. (The ANI and the port are key to be able to trace what the dialer is doing.)

badialer:
10:55:33:059 dialer-baDialer Trace: (Customer) SetCallResults(): ID: [-2147483601 in DL_5008_5031], skill: [6924], result: [1] [DIAL_RESERVED], now: [Thu Mar 4 10:55:33 2021], callback: [Thu Mar 4 10:55:32 2021]. 
10:55:33:059 dialer-baDialer Trace: (CUSTMGR) SendRecord(): Send customer: [2145551234] record ID: [-2147483601], in: DL_5008_5031 to port: [016]. 
10:55:33:059 dialer-baDialer Trace: (IVR) Record available event, port: [016], phone: [2145551234]. 
10:55:33:059 dialer-baDialer Trace: (CPORT) SetState(): Port: [016], state: [PORT_DEVICE_ATTRIB].

2. The Dialer asks the SIP Proxy to forward an invite to an available gateway to start a call. (CUSP logs are a bit of a pain, I recommend you download them via FTP instead of doing it from the GUI. This is a great guide on log settings. Ultimately, here we want to make sure that the CUSP knows where to send the request.)

badialer:
10:55:33:059 dialer-baDialer Trace: (SIPDisp) Dial, port: [016], phone: [22222912145551234], lDialTimeoutSec: [32]
CPA parameters: AP: [2500], MinSP: [608], MinVSpeech: [112], MaxTA: [5000], MaxTone: [30000]. 
10:55:33:059 dialer-baDialer Trace: (IVR) Dialing, phone: [22222912145551234], port: [016], ring timeout: [32], state: [PORT_DEVICE_ATTRIB]. 
10:55:33:059 dialer-baDialer Trace: (CPORT) SetState(): Port: [016], state: [DIAL_CUSTOMER].
CUSP:
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:468 nrs.FieldSelector - getUriPart: URI - sip:22222912145551234@{CUSP} part 6
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:468 nrs.FieldSelector - Requested field 45
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:468 nrs.FieldSelector - Returning key 22222912145551234
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:468 nrs.XCLPrefix - Leaving getKeyValue()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:468 modules.XCLLookup - table=Prod-CCE-Table, key=22222912145551234
[REQUESTI.26] INFO 2021.03.04 14:27:34:469 modules.XCLLookup - table is Prod-CCE-Table
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:469 routingtables.RoutingTable - Entering lookup()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:469 routingtables.RoutingTable - Looking up 22222912145551234 in table Prod-CCE-Table with rule prefix and modifiers=none
...
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 loadbalancer.LBBase - Entering getServer()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 loadbalancer.LBBase - Entering initializeDomains()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 servergroups.ServerGlobalStateWrapper - Prod-CCE:{Gateway}:5060:2 numTries=1--->isServerAvailable(): true
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 servergroups.AbstractNextHop - Entering compareDomainNames()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 servergroups.AbstractNextHop - Leaving compareDomainNames()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 servergroups.ServerGlobalStateWrapper - Prod-CCE:{Gateway0}:5060:2 numTries=1--->isServerAvailable(): true
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:470 loadbalancer.LBBase - Leaving initializeDomains()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:471 servergroups.AbstractNextHop - Entering compareDomainNames()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:471 servergroups.AbstractNextHop - Leaving compareDomainNames()
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:471 loadbalancer.LBBase - Server group dc-dialer.fqdn.tld selected {reSgElementQValue=1.0, reSgElementHost={Gateway}, reSgElementTransport=TCP, reSgElementPort=5060, reSgElementWeight=100, reSgElementSgName=dc-dialer.fqdn.tld}
[REQUESTI.26] DEBUG 2021.03.04 14:27:34:471 loadbalancer.LBBase - Leaving getServer()

3. The Gateway places the call (ccapi inout and ccsip mess are the debugs you need to enable to get relevant information. Biggest gotcha are dial peers not matching.)

Gateway:
2131549: Mar 4 10:55:33.260: //-1/xxxxxxxxxxxx/SIP/Msg/ccsipDisplayMsg:
Received: 
INVITE sip:22222912145551234@dc-dialer.fqdn.tld SIP/2.0
Via: SIP/2.0/TCP {CUSP}:5060;branch=z9hG4bKPpG+UtfThNEwGF7BsjXL3Q~~23121513
Via: SIP/2.0/UDP {MRPG}:58800;branch=z9hG4bK-d8754z-400d871379428a5a-1---d8754z-;rport=58800
Max-Forwards: 69
To: <sip:22222912145551234@{CUSP}>
From: <sip:5551412012@{MRPG}>;tag=0e52a576
Contact: <sip:5551412012@{MRPG}:58800>
Require: 100rel
Remote-Party-ID: <sip:18885461234@{CUSP}>;party=calling;screen=no;privacy=off
Call-ID: 3d5bfc40-093d7432-4a122a63-e2664942
CSeq: 1 INVITE
Content-Length: 630
Session-Expires: 1800
Min-SE: 90
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, UPDATE, NOTIFY, PRACK, REFER, NOTIFY, OPTIONS
Content-Type: Multipart/mixed;boundary=uniqueBoundary
Supported: timer, resource-priority, replaces
User-Agent: Cisco-SIPDialer/UCCE10.0

2132806: Mar 4 10:55:36.362: //16876944/44B8C2C0A6A6/SIP/Msg/ccsipDisplayMsg:
Received: 
SIP/2.0 180 Ringing
Via: SIP/2.0/UDP {PSTN}:5060;branch=z9hG4bK72D75B260F
From: <sip:18885461234@{PSTN}>;tag=4E815A86-1116
To: <sip:12145551234@{PSTN}>;tag=gK02b0b650
Call-ID: 44B9FBA8-7C4111EB-A6ACFA5D-15A1A7BA@{PSTN}
CSeq: 101 INVITE
Contact: <sip:12145551234@{PSTN}:5060>
Allow: INVITE,ACK,CANCEL,BYE,UPDATE
Content-Length: 236
Content-Disposition: session; handling=required
Content-Type: application/sdp

4. Voice Gateway does Call Progress Analysis and detects an answering machine. The Dialer is notified (I don’t think the above gateway debug levels will show you CPA information so I was not able to capture CPA from the gateway.)

baDialer:
10:58:41:845 dialer-baDialer Trace: (DDist) Softphone connection event: phone: [22222912145551234], result: [VOICE], port: [016], state: [DIAL_CUSTOMER]. 
10:58:41:845 dialer-baDialer Trace: (Customer) SetCallResults(): ID: [-2147483599 in DL_5008_5031], skill: [6924], result: [10] [VOICE], now: [Thu Mar 4 10:58:41 2021], callback: [Thu Mar 4 10:58:33 2021]. 
10:58:41:845 dialer-baDialer Trace: (IVR) Received telephony event port: [016], connection state: [20].

5. The Dialer asks the MR PG where the IVR is

baDialer:
10:58:41:845 dialer-baDialer Trace: (CPORT) SetState(): Port: [016], state: [TRANSFER]. 
10:58:41:845 dialer-baDialer Trace: (IVR) Transferring Customer port: [016], to IVR route point: [6515555678]. 
10:58:41:845 dialer-baDialer Trace: (CPORT) SetState(): Port: [016], state: [GET_TARGET].

6. MR PG forwards the request to the Router (It is important to note that up to this point everything that was happening was outside of the central controller.)

PIM:
14:23:09:556 PG2A-pim1 Trace: VRU->PG:REQUEST_INSTRUCTION(172 bytes):DID=570876 SendSeq#=1 TrkGrpID=200 TrkNum=1 SrvID=2 ANI=12145551234 DNIS=666666666610241 CorrID=10241 CallGUID=410885C97C5E11EBAABBFA5D15A1A7BA PstnTrkGrpID={Gateway} PstnTrkGrpChann#=2147483647 SIPHeader=f:<sip:12145551234@{Gateway}>;tag=4F3F6BE6-173D 
14:23:09:556 PG2A-pim1 Trace: FromVRU_RequestInstruction:REQUEST_INSTRUCTION RCID=5001 PID=5001 DID=570876 DIDRelSeq#=1 CorrID=10241 CalledParty#= CallingParty#=12145551234 CallGUID=410885C97C5E11EBAABBFA5D15A1A7BA PstnTrkGrp(ID={Gateway} ,Chann#=2147483647) SIPHeader=f:<sip:12145551234@{Gateway}>;tag=4F3F6BE6-173D 
14:23:09:556 PG2A-pim1 Trace: ProcessConnect:CONNECT RCID=5001 PID=5001 DID=570876 DIDRelSeq#=0 CRS(RtrDate=153464,RtrCID=18384) RCKSeq#=0 ErrorCode=0 TRTargetID=-1 CorrID=10242 EventSel=119 SvcType=4 NICCallID={PCID=5001,RCID=5001,Remote=0,0,DID=0x8b5fc,RemDID=0x0,Grp=0,Data=0,RtrData=0,CCID=x00000001/x00000000} PGCallID={N/A} OperationCode=CLASSIC OperationFlags=COOP_NONE NetworkTransferEnabled=F ECCPayloadID=1 Label(Type=8)=8888888881 NICCalledParty#=6515555678 SGSTID=-1 PQID=-1 SvcSTID=-1 AGSTID=-1 AGInfo=, MRDID=0 Interruptible=0 CallGUID=410885C97C5E11EBAABBFA5D15A1A7BA SIPHeader=f:<sip:12145551234@{Gateway}>;tag=4F3F6BE6-173D
rtr:
14:23:09:446 ra-rtr Trace: (1741 x 0 : 0 0) NewCall: CID=(153464,18384), DN=6515555678, ANI=2145551234, CED=, RCID=5003, MRDID=1, CallAtVRU=1, OpCode=0

7. Routing Script identifies the IVR and notifies the MR PG. (The script used here is the Hello World CVP script. Note that at this point we’re working with one corrID, but when the call goes to the IVR we will have a second corrID.)

rtr:
14:23:09:446 ra-rtr Trace: Script-Execute CID=(153464,18384) Default\\ZZZ_HelloIVR Start 1 
14:23:09:446 ra-rtr Trace: Script-Execute CID=(153464,18384) Default\\ZZZ_HelloIVR Set Variable 2 
14:23:09:446 ra-rtr Trace: Script-Execute CID=(153464,18384) Default\\ZZZ_HelloWorld Send To VRU 6 
14:23:09:446 ra-rtr Trace: (1741 x 0 : 0 0) Customer (1) has no valid network vru defined - using default. 
14:23:09:446 ra-rtr Trace: (1741 x 0 : 0 0) Customer (1) has no valid network vru defined - using default. 
14:23:09:446 ra-rtr Trace: (1741 x 0 : 0 0) Correlation id for dialog is (10241). 
14:23:09:446 ra-rtr Trace: (1741 x 10241 : 0 0) TransferToVRU: Label=6666666666, CorID=10241, VRUID=5000, RCID=5003 ECCPayloadID=1 
14:23:09:446 ra-rtr Trace: (1741 x 10241 : 0 0) TransferConnect sent. Dialog pending.
PIM:
14:23:09:556 PG2A-pim1 Trace: PG->VRU:TEMPORARY_CONNECT(214 bytes):DID=570876 SendSeq#=1 Label=8888888881 CorrID=10242 RCK=18384 RCKDay=153464 RCKSeq#=0 CallGUID=410885C97C5E11EBAABBFA5D15A1A7BA SIPHeader=f:<sip:12145551234@{Gateway}>;tag=4F3F6BE6-173D

8. The MR PG forwards the route response to the Dialer

baDialer:
10:58:41:877 dialer-baDialer Trace: (IVR) MR target acqusition succeeded for port: [016], state: [GET_TARGET], target: [666666666610241]. 
10:58:41:877 dialer-baDialer Trace: (CPORT) SetState(): Port: [016], state: [TRANSFER]. 
10:58:41:877 dialer-baDialer Trace: (SIPDisp) Transfer, port: [016], phone: [666666666610241].

9. The Dialer notifies the voice gateway to transfer the call to the IVR

baDialer:
10:55:42:249 dialer-baDialer Trace: (RESIP) Adding message to tx buffer to: [ V4 {Gateway}:5060 UDP target domain={Gateway} mFlowKey=0 ] 
10:55:42:265 dialer-baDialer Trace: (IVR) MR target acqusition succeeded for port: [016], state: [GET_TARGET], target: [666666666610241]. 
10:55:42:265 dialer-baDialer Trace: (CPORT) SetState(): Port: [016], state: [TRANSFER]. 
10:55:42:265 dialer-baDialer Trace: (SIPDisp) Transfer, port: [016], phone: [666666666610241]. 
10:55:42:265 dialer-baDialer Trace: (CLMGR) Agent event, agent: [111100208], ext: [5551510241], state: [TALKING]. 
10:55:42:265 dialer-baDialer Trace: (CLMGR_SIP) tOnBeginCallEvent(): Port: [003], ID: [38914319], device ID: [5551510241], IsReservationPort: [No]. 
10:55:42:265 dialer-baDialer Trace: (CLMGR) Agent event, agent: [111100208], ext: [5551510241], state: [TALKING]. 
10:55:42:281 dialer-baDialer Trace: (RESIP) Dialog::makeRequest: 
...
10:55:42:281 dialer-baDialer Trace: (RESIP) SEND: 
REFER sip:22222912145551234@{Gateway}:5060 SIP/2.0
Via: SIP/2.0/ ;branch=z9hG4bK-d8754z-5178e07be820b14e-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:5551412012>
To: <sip:22222912145551234@{CUSP}>;tag=4E816590-135
From: <sip:5551412012@{MRPG}>;tag=0e52a576
Call-ID: 3d5bfc40-093d7432-4a122a63-e2664942
CSeq: 3 REFER
User-Agent: Cisco-SIPDialer/UCCE10.0
Refer-To: <sip:666666666610241@{CUSP}>
Referred-By: <sip:5551412012@{MRPG}>
Content-Length: 0

10 The Gateway initiates the transfer to the SIP Proxy, and the SIP Proxy forwards the invitation onto Unified CVP.

Gateway:
2134751: Mar 4 10:55:42.320: //16876944/44B8C2C0A6A6/CCAPI/ccCheckClipClir:
In: Calling Number=12145551234(TON=Unknown, NPI=Unknown, Screening=Not Screened, Presentation=Allowed)
2134752: Mar 4 10:55:42.320: //16876944/44B8C2C0A6A6/CCAPI/ccCheckClipClir:
Out: Calling Number=12145551234(TON=Unknown, NPI=Unknown, Screening=Not Screened, Presentation=Allowed)
2134753: Mar 4 10:55:42.321: //16876944/44B8C2C0A6A6/CCAPI/ccCallSetupRequest:
Destination Pattern=6666666666....., Called Number=11102666666666610241, Digit Strip=FALSE
2134754: Mar 4 10:55:42.321: //16876944/44B8C2C0A6A6/CCAPI/ccCallSetupRequest:
Calling Number=12145551234(TON=Unknown, NPI=Unknown, Screening=Not Screened, Presentation=Allowed),
Called Number=11102666666666619114(TON=Unknown, NPI=Unknown),
Redirect Number=, Display Info=
Account Number=18885461234, Final Destination Flag=TRUE,
Guid=44B8C2C0-7C41-11EB-A6A6-FA5D15A1A7BA, Outgoing Dial-peer=2208
CUSP:
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 nrs.FieldSelector - getUriPart: URI - sip:11102666666666610241@{CUSP} part 6
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 nrs.FieldSelector - Requested field 45
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 nrs.FieldSelector - Returning key 11102666666666610241
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 nrs.XCLPrefix - Leaving getKeyValue()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 modules.XCLLookup - table=Prod-CCE-Table, key=11102666666666610241
[REQUESTI.7] INFO 2021.03.04 14:27:39:643 modules.XCLLookup - table is Prod-CCE-Table
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 routingtables.RoutingTable - Entering lookup()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:643 routingtables.RoutingTable - Looking up 11102666666666610241 in table Prod-CCE-Table with rule prefix and modifiers=none
...
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBBase - Entering getServer()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBBase - Entering initializeDomains()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 nrs.NRSRoutes - routes before applying time policies: [Ruri: dc1-cvp.fqdn.tld, Route: null, Network: Prod-CCE, q-value=1.0radvance=[502, 503]]
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 nrs.NRSRoutes - routes after applying time policies: [Ruri: dc1-cvp.fqdn.tld, Route: null, Network: Prod-CCE, q-value=1.0radvance=[502, 503]]
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBBase - Leaving initializeDomains()
[REQUESTI.7] INFO 2021.03.04 14:27:39:644 loadbalancer.LBHashBased - list of elements in order on which load balancing is done : Ruri: dc1-cvp.fqdn.tld, Route: null, Network: Prod-CCE, q-value=1.0radvance=[502, 503], 
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBBase - Server group route-sg selected Ruri: dc1-cvp.fqdn.tld, Route: null, Network: Prod-CCE, q-value=1.0radvance=[502, 503]
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBBase - Leaving getServer()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 nrs.XCLNRSShiftRoutes - Leaving ShiftRoutes.execute()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBFactory - Entering createLoadBalancer()
[REQUESTI.7] INFO 2021.03.04 14:27:39:644 loadbalancer.LBFactory - lbtype is 5(weight)
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBFactory - Leaving createLoadBalancer()
[REQUESTI.7] DEBUG 2021.03.04 14:27:39:644 loadbalancer.LBBase - Entering getServer
rtr:
07:55:48:615 ra-rtr Trace: (1743 x 10241 : 0 0) TransferToVRU: Label=6666666666, CorID=10241, VRUID=5000, RCID=5003 ECCPayloadID=1 
07:55:48:615 ra-rtr Trace: (1743 x 10241 : 0 0) TransferConnect sent. Dialog pending. 
07:55:48:709 ra-rtr Trace: (1743 585271 10241 : 0 0) RequestInstr: CID=(153465,18384), CallState=1 
07:55:48:709 ra-rtr Trace: (585271 585271 10241 : 0 0) Dialog initiating 2nd phase of transfer. 
07:55:48:709 ra-rtr Trace: (585271 585271 10241 : 0 0) Correlation id for dialog is (10242). 
07:55:48:709 ra-rtr Trace: (585271 585271 10242 : 0 0) TransferToVRU: Label=8888888882, CorID=10242, VRUID=5000, RCID=5006 ECCPayloadID=1 
07:55:48:709 ra-rtr Trace: (585271 585271 10242 : 0 0) TransferConnect sent. Dialog pending. 
07:55:48:802 ra-rtr Trace: (585271 585272 10242 : 0 0) RequestInstr: CID=(153465,18384), CallState=1 
07:55:48:802 ra-rtr Trace: (585271 585272 10242 : 0 0) Dialog resuming (Request Instruction received.) status (0) 
07:55:48:802 ra-rtr Trace: Script-Continue CID=(153465,18384) Default\\ZZZ_HelloIVR Send To VRU 6 
07:55:48:802 ra-rtr Trace: Script-Execute CID=(153465,18384) Default\\ZZZ_HelloIVR Set Variable 7 
07:55:48:802 ra-rtr Trace: Script-Execute CID=(153465,18384) Default\\ZZZ_HelloIVR Run External Script 8 
07:55:48:802 ra-rtr Trace: (585271 585272 10242 : 0 0) Skipping the VRU verification because of Peripheral's ClientType is DBCT_MEDIA_ROUTING 
07:55:48:802 ra-rtr Trace: (585271 585272 10242 : 0 0) Runscript sent. ECCPayloadID = 1 Dialog pending. 
07:55:54:366 ra-rtr Trace: (585271 585272 10242 : 0 0) CallEventReport: CID=(153465,18384),Event=DISCONNECT, DlgEnds=1, FromVRU=0, CallState=2, Cause=NORMAL 
07:55:54:366 ra-rtr Trace: (585271 585272 10242 : 0 0) Dialog received event report 6 from NIC during RunScript. 
07:55:54:570 ra-rtr Trace: (585271 585272 10242 : 0 0) CallEventReport: CID=(153465,18384),Event=DISCONNECT, DlgEnds=1, FromVRU=0, CallState=22, Cause=NORMAL 
07:55:54:570 ra-rtr Trace: (585271 585272 10242 : 0 0) Dialog (callstate:22) received event(6)(Call disconnected. (Event has dialog end set.)) 
07:55:54:570 ra-rtr Trace: (585271 585272 10242 : 0 0) Dialog resuming (Call disconnected. (Event has dialog end set.)) status (3) 
07:55:54:570 ra-rtr Trace: Script-Continue CID=(153465,18384) Default\\ZZZ_HelloIVR Run External Script 8 
07:55:54:570 ra-rtr Trace: (585271 585272 10242 : 0 0) Dialog aborted and was deleted. 
07:55:54:570 ra-rtr Trace: (585271 585272 10242 : 0 0) Dialog sending release call to VRU 
07:55:54:570 ra-rtr Trace: (585271 585272 10242 : 0 0) Deleting Dialog.

From this point forward it’s just an inbound CVP call.

~david

Cisco Courtesy Callback (CCB) and CCE Hours of Operations

A project from last year requested that CCB not be provided if the expected wait time (EWT) was longer than how long this group was going to be open. The other requirements was to try and do it without using a custom element so they could better support the application if I was no longer around. There were two challenges with this request. First, getting the hours of operations. Second, parsing through that data to figure out if the EWT would be greater than close time or not. This blog will not provide the whole solution, but it should give you enough to be able to piece things together to fit your needs.

Getting the Hours of Operations

Assuming you’re running Contact Center Enterprise (UCCE/PCCE)  12.x, Cisco has an API to get the business hours using a GET request to https://{CCEAdminFQDN}/unifiedconfig/config/businesshour/ and using basic authentication you can see this:

GET Request Business Hours

GET Request Business Hours

Let’s take a closer look at the payload. Let’s focus on the two parts that are marked with ###.

</pre>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<businessHour>
<refURL>/unifiedconfig/config/businesshour/5000</refURL>
<changeStamp>0</changeStamp>
<configuredStatus>
<status>0</status>
</configuredStatus>
<name>TestHours</name>
<runTimeStatus>1</runTimeStatus>
<runTimeStatusReason>Week Day open reason</runTimeStatusReason>
<specialDaySchedules/>
<timezone>
<refURL>/unifiedconfig/config/timezone/v2/5023</refURL>
<displayName>(UTC-06:00) Central America</displayName>
</timezone>
<type>1</type>
<weekDaySchedules>
<weekDaySchedule>
<refURL>/unifiedconfig/config/businesshour/5000/weekdayschedule/5000</refURL>
<changeStamp>0</changeStamp>
<endTime>17:00</endTime>
<startTime>09:00</startTime>
<dayOfWeek>1</dayOfWeek>
</weekDaySchedule>
<weekDaySchedule>
<refURL>/unifiedconfig/config/businesshour/5000/weekdayschedule/5001</refURL>
<changeStamp>0</changeStamp>
<endTime>17:00</endTime>
<startTime>09:00</startTime>
<dayOfWeek>2</dayOfWeek>
</weekDaySchedule>
<weekDaySchedule>
<refURL>/unifiedconfig/config/businesshour/5000/weekdayschedule/5002</refURL>
<changeStamp>0</changeStamp>
<endTime>17:00</endTime>
<startTime>09:00</startTime>
<dayOfWeek>3</dayOfWeek>
</weekDaySchedule>
<weekDaySchedule>
<refURL>/unifiedconfig/config/businesshour/5000/weekdayschedule/5003</refURL>
<changeStamp>0</changeStamp>
<endTime>17:00</endTime>
<startTime>09:00</startTime>
<dayOfWeek>4</dayOfWeek>
</weekDaySchedule>
<weekDaySchedule>
<refURL>/unifiedconfig/config/businesshour/5000/weekdayschedule/5004</refURL>
<changeStamp>0</changeStamp>
<endTime>17:00</endTime>
<startTime>09:00</startTime>
<dayOfWeek>5</dayOfWeek>
</weekDaySchedule>
</weekDaySchedules>
</businessHour>
<pre>
The runTimeStatus tells us if the Business Hours are currently opened or closed and the next part tells us what the hours of operations are for each day. Based on your use case you an just use the runTimeStatus to make a decision to offer CCB, but if you need to check if EWT is greater than close time, then we need to do a bit of extra work.
Parsing Through the Data
First, using an action element you first need to convert the XML into an array:
&lt;/pre&gt;&lt;pre&gt;importPackage(com.audium.server.cvpUtil)
var xml = {Data.Element.RESTGetBH.response_body}
var path = ""
var dow, dowPath, endTimePath
var endTimeArr=["00:00","00:00","00:00","00:00","00:00","00:00","00:00"]

for(var ctr=1; ctr&amp;amp;lt;=7;ctr++)
{
dowPath="/businessHour/weekDaySchedules/weekDaySchedule["+ctr+"]/dayOfWeek"
dow = XpathUtil.eval(xml,dowPath)

endTimePath = "/businessHour/weekDaySchedules/weekDaySchedule["+ctr+"]/endTime"
endTimeArr[dow]=XpathUtil.eval(xml,endTimePath )
}

print("\nendTimeArr=" + endTimeArr)

endTimeString = endTimeArr.join(";")
print("\nendTimeString=" + endTimeString)

endTimeString

Next, we need to parse what time we close today. Notice that we pass today’s day from ICM:

&lt;/pre&gt;&lt;pre&gt;var arr = {LocalVar.LocalEndArrayString}.split(";")
var dow={LocalVar.dayOfWeekBH}
var time = arr[dow]
print("\nendTime=" + time)
time

Finally, we need to calculate in seconds what time we close:


var closeTime = {LocalVar.todayClose};

var hours = closeTime.substring(0,2);
var minutes = closeTime.substring(closeTime.length-2, closeTime.length);
closeTimeInSeconds = Number(hours)*3600+Number(minutes)*60;

At this point we have an array containing all the close hours, we have what time we closed today, and finally we have how many seconds from midnight we’re going to close. Now you can make some calculations to figure out if you should offer CCB or not.

~david

Extending Your Call Center’s Life

While the CCaaS providers want you to think that everyone is fleeing to the cloud and the traditional on premise CC providers want you to think that the shift is very slow, the truth is somewhere in the middle. One thing we can be sure of is that all of our on premise customers are doing this:

4g04ry

Every vendor is playing defend and take over. The days of an Avaya IVR with a Cisco ACD are starting to go the way of the dinosaur. Really no one is interested in playing reach out and integrate, but this is where the most value is present and where I think we, as consultants, should be focusing our energy. Let’s look at a couple of scenarios where reach out and integrate make sense.

Scenario 1: You’re on an old ACD which, while still supported, your only upgrade path is to go cloud as the vendor has stopped on-premise development. You are not sold on going cloud and are considering replacing your current platform with another on premise solution.

Scenario 2: There is a worldwide state of emergency, let’s pretend a pandemic is happening, and you need to spin up small clusters of call centers quickly to handle increased demand due to new customer requests. You’re projecting that these call centers and agents might only be online for a maximum of a year so you want to avoid having to purchase perpetual licenses and new hardware to handle these new capacity long term.

Scenario 3: Business is absolutely booming and the two server solution you love has reached capacity. The only way to scale is to go to a completely different solution, but you absolutely love your current system and you don’t have enough data to make a decision to replace your system or not.

We are seeing a lot of requests coming in which fit the above 3 scenarios and we have spent a lot of time figuring out what the perfect reach out and extend strategy is for each request.  I would love to hear what other scenarios or interesting pairing others have run into or are currently working on. I’ll be posting more about one interested pairing we’ve made and the solution we’ve developed to increase the life of our customer’s call center.

~david

Do virtual agents need virtual breaks?

If you’ve been paying attention to the contact center world you would have noticed the rise of virtual agents. The term is a bit broad and confusing to be honest, but here’s my take on it. Virtual agents are just a rebranding of self service with a lot more personality and capabilities. For example, you could always make a payment online, but before the flow was:

  • For account information press 1. “1”
  • Please enter your account number. “1234”
  • To hear your balance press 1. “1”
  • To make a payment press 1. “1”

Now with virtual agents the flow is a lot more flexible:

  • What would you like to do? “I want to a pay my account.”
  • What’s your account number? “1234”
  • Would you like to pay your account in full? “yes”

Now with the rise of virtual agent services you can use speech, TTS, send out an SMS, process a credit card and integrate with a backend all within the same platform and under the same cost. The rise of these agents aligns perfectly with the proliferation of AI services such as DialogFlow, AWS Comprehend, and even MindMeld (RIP) which have removed a lot of the complexity in delving in to AI/Machine Learning. Additionally, the XaaS model where you don’t have to buy the cow, care for it, feed it. All you do is rent it when you want some milk.

The other huge benefit a lot of these virtual agents tout is the ability to be multichannel out of the box. You no longer have to build parallel logic for voice and SMS. You now have a single place for all your logic and can go to Facebook messenger or WhatsApp. While this is partially true, the customer experience across different channels does need to change and the reality is that you should be building semi-parallel logic depending on your channel.

Ultimately, I’ve started dabbling in this space and got my Inference certification. I’m looking forward to getting more and more exposure with this technology and get a deeper understanding how it really augments the contact center world I know and love.

~david

 

WireShark RTP Player

Troubleshoot RTP issues with WireShark when using Jabber or IP Communicator

This was an interesting one that I wanted to document. We have our agents and supervisors on either VDI Jabber or Windows Jabber or CIPC and we could not get silent monitoring to work. When the supervisor activated it everything looks correct, but there was no audio for the supervisor while the agent and caller had no issues. Supervisor could then barge in to the call and audio would work just fine. Here are the steps we took to troubleshoot this.

  1. Get the IP addresses of the agent and supervisor device. Then get a packet capture of both end points while performing silent monitoring.
  2. Using this filter get all the packets coming from the other device to your computer: ip.addr == {other parties IP}. You should see a good bit of UDP packets. At this point if you don’t see any packets coming from the other endpoint you know that more than likely the network or far end device configuration is at fault as your device didn’t send or receive any RTP.

Wireshark Packet Capture

Wireshark Packet Capture

3. Click click on any of the packets Decode As… Set Current to RTP.

WireShark Decode Packets

WireShark Decode Packets

4. All your previous UDP packets should now be RTP packets.

WireShark Decoded Packets as RTP

WireShark Decoded Packets as RTP

5. Go to Telephony > RTP Streams and Analyze the stream that is detected. You will then be able to Play Streams to confirm you get the expected audio.

WireShark RTP Stream Analysis

WireShark RTP Stream Analysis

6. Confirm audio stream is correct.

WireShark RTP Player

WireShark RTP Player

At this point we’ve confirmed our device is getting RTP, but our soft phone isn’t playing it. So a likely culprit could be the Windows firewall. Using your favorite text editor go to c:\Windows\System32\LogFiles\Firewall and open domainfw.log and publicfw.log. What you want to look for is the IP of the other device and see if you see any drops.

2020-01-01 12:03:48 DROP UDP {RemoteIP} {LocalIP} 17488 24576 200 – – – – – – – RECEIVE

If you look at the port this was received on you’ll notice that it is the RTP range Cisco recommends to have open. So at this point you can disable the firewall, which I don’t recommend, or create a new firewall rule and add UDP ports 16384-32767 as allowed.

~david

Windows ODBC Data Source Success

Windows ODBC Connection to CVP Reporting Database

This one took me a good bit of hours and googling with very little results. I wanted to post this for posterity and for anyone else out there trying to do the same thing. The end goal is to create a Windows ODBC Data Source connection to your Cisco CVP Reporting database.

First, a bit of a recap. Cisco’s CVP Reporting server utilizes IBM’s Informix database. It’s been like that for many years and to be honest it’s both great and terrible. It’s great because you don’t have to pay (directly) for a license to run the database. And horrible because it feels like Informix is just one step above using an sqlite database. Informix is used in Cisco CommunicationsManager (UCM), Contact Center Express (UCCX), Unity Connections (UCxN), and of course CVP Reporting.

On to the main course.

  1. You must obtain a free IBM account and go to this link. You must choose the SDK option and download the Informix Client SDK Developer Edition.
    • If you want to do this with ODBC x64 then download clientsdk.4.10.FC14.windows64.zip
    • For the x32 version download clientsdk.4.10.TC14.zip.
  2. Install either download, you can install them both too. They both work independently or side by side. Then reboot your computer.
  3. From there you must go to your CVP Reporting server and find your onconfig file. It should be in c:\db\Informix\etc. Open the file and find the following two lines. Make note of the values you see.
    • DBSERVERNAME
    • NETTYPE
  4. Open ODBC Data Source Administrator on your local machine. Go to System DSN > Add…
  5. Choose IBM Informix ODBC Driver > Finish.
  6. General > Data Source Name > Give it any name
  7. Connection > Server Name > The value of DBSERVERNAME in the onconfig file.
    1. Host Name: IP, FQDN, or hostname of server
    2. Service: 1526
    3. Protocol: The value of NETTYPE in the onconfig file.
    4. Database Name: cvp_data
    5. User Id: Generally cvp_dbuser but you an connect with any valid user.
    6. Password: Your password.
  8. Environment > Client Locale > EN_US.UTF8
    • Database Locale: EN_US.UTF8 If you can’t set this value, hit apply to close the ODBC properties and then set this property.
  9. Go back to the Connection tab and Apply & Test Connection and give it a minute or two and you should see:

Windows ODBC Data Source Success

Windows ODBC Data Source Success

Hope this helps.

~david

2020 Cisco Forums Profile

It’s nice to be recognized

I got a nice surprise in my inbox today. An email from Cisco letting me know that I was the first ever winner of the English Community Developer of the Month. Per Cisco the Community Spotlight Awards:

… recognizes members whose significant contributions designate leadership and commitment to their peers within their respective communities, including the Cisco Learning Network (CLN) and Cisco Community. Spotlight Awards Program is designed to recognize and thank individuals who help make our communities the premier online destination for Cisco enthusiasts.

I get a cool badge to show off too.

2020 Cisco Forums Profile

2020 Cisco Forums Profile

You can find current and past winners here or try to spot me in the picture below.

Current Spotlight Winners

Current Spotlight Winners

Looking back through my blog posts in 2008 I talked about getting my first star due to my contributions in the Cisco NetPro forums and how happy I was about it. In that blog I have a picture of my profile showing a total of 103 posts made with 8 questions resolved. That number, 12 years later, has ballooned to 3030 posts and 208 solutions.

I encourage anyone starting out or a seasoned veteran to contribute in the various Cisco Communities it’s a great way to network with your Cisco peers and try to tackle some very interesting technical problems while you procrastinate from your not as interesting technical problems.

~david