LastcalledAgent routing

Hi we have a requirement to route the call to LastCalledagent, to achieve below is my approach

  1. query for the Conversation details based on the caller Ani, from this take the latest call and fetch Agent user Id.
  2. Get the agent email/sip from using user id in step-1.

to do this i'm trying to create data action, and this is how my input JSON looks like

{
"type": "object",
"properties": {
"interval": {
"description": "An explanation about the purpose of this instance.",
"type": "string"
},
"order": {
"description": "An explanation about the purpose of this instance.",
"default": "asc",
"type": "string"
},
"orderBy": {
"description": "An explanation about the purpose of this instance.",
"default": "conversationStart",
"type": "string"
},
"paging": {
"description": "An explanation about the purpose of this instance.",
"default": {},
"type": "object",
"properties": {
"pageSize": {
"description": "An explanation about the purpose of this instance.",
"type": "integer"
},
"pageNumber": {
"description": "An explanation about the purpose of this instance.",
"type": "integer"
}
},
"additionalProperties": true
},
"segmentFilters": {
"description": "An explanation about the purpose of this instance.",
"default": [],
"type": "array",
"items": [
{
"title": "The Items Schema",
"description": "An explanation about the purpose of this instance.",
"default": {},
"type": "object",
"properties": {
"predicates": {
"description": "An explanation about the purpose of this instance.",
"default": [],
"type": "array",
"items": {
"title": "The Items Schema",
"description": "An explanation about the purpose of this instance.",
"default": {},
"type": "object",
"properties": {
"dimension": {
"description": "An explanation about the purpose of this instance.",
"type": "string"
},
"operator": {
"description": "An explanation about the purpose of this instance.",
"type": "string"
},
"value": {
"description": "An explanation about the purpose of this instance.",
"type": "string"
},
"type": {
"default": "dimension",
"type": "string"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
{
"title": "type",
"default": "and",
"type": "string"
}
]
}
},
"additionalProperties": true
}

I used https://jsonschema.net/home to get this input json,

Error

Failed Validation of contract.input.inputSchema as a simple properties schema. Must be an Object with properties and no sub-objects. JSON failed schema validation for the following reasons: Schema: # @/properties/properties/patternProperties/^[a-zA-Z][a-zA-Z0-9_-]*$/properties/type. Error location: /properties/paging/type. instance value ("object") not found in enum (possible values: ["boolean","integer","null","number","string"])

Are you planning to perform Last Agent Routing for all calls? What is your average call volume per day? The reason I ask, is your current approach of using the Analytics API could put an unexpected load on the system and could cause performance issues or API overage that you aren't expecting.

This IVR line is for Remote technical engineers, call volume per day is less than 100 per day.

So PureCloud Data Actions don't work the way you are thinking. Data Actions are meant to hide the complexity of HTTP GET/POST/PUT/PATCH/DELETE requests behind an easy to user interface for a business user. All the business user will now are what inputs are needed (the Input Schema) and what outputs they will get back (the Output Schema). The operations of the actual HTTP request are hidden inside the actual HTTP request/response (the Configuration tab).

Let me explain...

The Input Schema is NOT meant to mirror the request body. The Input Schema defines the variables that are needed to customize the request body. In your case I think you'd probably want to parameterize the search interval and the ANI. So your input scheme might look like this:

{
  "title": "Search for Previous Conversations By ANI",
  "description": "Get previous conversations for the given ANI.",
  "type": "object",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "properties": {
    "INTERVAL": {
      "description": "The time interval to search",
      "type": "string"
    },
    "ANI": {
      "description": "The ANI of the caller",
      "type": "string"
    }
  },
  "additionalProperties": true
}

Then on the Configuration tab you'll define the HTTP request, and THAT's where you'll have the JSON body for your analytics query, but instead of hard coded values for the interval and ANI, those will be the variables received from what the user entered in the Input Schema. So you'll want the following properties setup for the request:

Type: POST
UI: Simple
Request URL Template: /api/v2/analytics/conversations/details/query
Headers: none
Request Body Template:

{
    "interval": "${input.INTERVAL}",
	"order": "asc",
	"orderBy": "conversationStart",
	"paging": {
		"pageSize": 25,
		"pageNumber": 1
	},
	"segmentFilters": [
		{
			"type": "or",
			"predicates": [
				{
					"type": "dimension",
					"dimension": "ani",
					"operator": "matches",
					"value": "${input.ANI}"
				}
			]
		}
	]
}

At that point you can click "Save Draft" and then hop to the "Test" tab where you can use the Data Action like a business user would and you can enter an INTERVAL and ANI into the two input fields, uncheck the Flatten Outbound option, and run the action. You should get the JSON of the analytics query response back.

Then you'll have to parse the response and make the values that you want to expose available in the Output Schema.

Hope that helps.

Thank you, I'm able to get the output now, but each conversation will have multiple participants, so i want to pull the participants details of the agent leg, when i try this on http://jsonpath.com/ i'm getting the desired result, but same is not working one purecloud, could you please help with this

Output parsing as below and giving the error:

"$.conversations[*].participants[?(@.purpose =="agent")].userId"

" 1. Validate output against schema: JSON failed schema validation for the following reasons: Schema: # @/properties/agentID. Error location: /agentID. instance type (array) does not match any allowed primitive type (allowed: ["string"])"

I cannot do this way ""$.conversations[0].participants[3].userId", this will fail if call is transferred between multiple agents.

I think you're getting an array (with one element) from your JSONPath, you can use successTemplateUtils.firstFromArray to get the actual value inside.

1 Like

sample Response below, I want to get Conversation[0].Participants[3].userId.

( length of the participants arry will vary, if i give index as 3 , it will fail in many case, so i want some thing dynamic, to check if "userId" is available in Participants[*] and take the value.)

{
"conversations": [
{
"conversationId": "11a3fd5c-8ac2-4eea-ae61-e807296d00e6",
"conversationStart": "2020-04-15T14:42:43.380Z",
"conversationEnd": "2020-04-15T14:43:34.449Z",
"mediaStatsMinConversationMos":xxxxxxxxx,
"mediaStatsMinConversationRFactor": 9xxxxxxxxx,
"originatingDirection": "inbound",
"divisionIds": [
"36852a81-xxxxxxxxx"
],
"participants": [
{
"participantId": "42bb2e7b-2042-432c-899d-fef7b20edb54",
"participantName": "Bedford NH",
"purpose": "customer"
} ,
{
"participantId": "42bb2e7b-2042-432c-899d-fef7b20edb54",
"participantName": "Billing",
"purpose": "IVR"
},
{
"participantId": "42bb2e7b-2042-432c-899d-fef7b20edb54",
""userId": "4e908b2e-8c0e-4b0e-a564-fd591646b096",
"purpose": "agent",
}
}
]
}
]
}

The example that you posted wasn't valid JSON, so here is the version I tested with:
{
"conversations": [{
"conversationId": "11a3fd5c-8ac2-4eea-ae61-e807296d00e6",
"conversationStart": "2020-04-15T14:42:43.380Z",
"conversationEnd": "2020-04-15T14:43:34.449Z",
"mediaStatsMinConversationMos": 1,
"mediaStatsMinConversationRFactor": 1,
"originatingDirection": "inbound",
"divisionIds": [
"36852a81-xxxxxxxxx"
],
"participants": [{
"participantId": "42bb2e7b-2042-432c-899d-fef7b20edb54",
"participantName": "Bedford NH",
"purpose": "customer"
},
{
"participantId": "42bb2e7b-2042-432c-899d-fef7b20edb54",
"participantName": "Billing",
"purpose": "IVR"
},
{
"participantId": "42bb2e7b-2042-432c-899d-fef7b20edb54",
"userId": "4e908 b2e - 8 c0e - 4 b0e - a564 - fd591646b096 ",
"purpose": "agent"
}
]
}]
}

I found that a JSONPath like this:

$.conversations[0].participants[?(@.userId)]
will return the participant block or
$.conversations[0].participants[?(@.userId)].userId
will just return the userId.

FYI, I now use http://jsonpath.herokuapp.com/ for testing out my JSON Path expressions.

--Jason

Hi Jason
On http://jsonpath.herokuapp.com/ your suggestion is working, but when i do the same on purecloud, i'm getting an erro, below is how i have setup, am I doing it wrong way?

{
"translationMap": {
"agentID": ".conversations[0].participants[?(@.userId)].userId" }, "translationMapDefaults": {}, "successTemplate": "{\"agentID\":{agentID}\n}"
}

Error

1. Validate output against schema: JSON failed schema validation for the following reasons: Schema: # @/properties/agentID. Error location: /agentID. instance type (array) does not match any allowed primitive type (allowed: ["string"])

Hello,

I am not expert on Data Actions, but I've tried to investigate the topic following your question.

I have managed to get the last agent userId the following way.

  1. To explain why your JSONPath expression is failing.

Even if you may retrieve only one userId, filtering your "participants" array, as the filter is applied to an array, the result is an array of strings, and not a string.

So you would have to store the result of that JSONPath expression in a variable (output contract) defined as array.
In order to define an array variable, you have to both set the type to array AND provide a definition for the array items.

Like this:

  "UserIdArray": {
    "type": "array",
    "items": {
      "title": "UserId",
      "type": "string"
    }
  }
  1. The Analytics Query

This what I have used for my Analytics Query (POST /api/v2/analytics/conversations/details/query)

{
  "interval": "${input.INTERVAL}",
  "order": "desc",
  "orderBy": "conversationStart",
  "paging": {
    "pageSize": 1,
    "pageNumber": 1
  },
  "segmentFilters": [
    {
      "type": "and",
      "predicates": [
        {
          "type": "dimension",
          "dimension": "mediaType",
          "operator": "matches",
          "value": "voice"
        },
        {
          "type": "dimension",
          "dimension": "purpose",
          "operator": "matches",
          "value": "agent"
        },
        {
          "type": "dimension",
          "dimension": "ani",
          "operator": "matches",
          "value": "tel:${input.ANI}"
        }
      ]
    }
  ]
}

Few comments on this one:

  • Note that the maximum duration of the Interval is one month.
  • I have set the "order" attribute to "desc" so that most recent conversations are at the beginning of the conversations array (most recent to oldest conversations)
  • I have set "pageSize" to 1 so I have only two possible outcomes of my query: no conversation found, or just one (most recent call/conversation for this ANI)
  • In my predicates (using "and" condition), I have checked:
    -- mediaType=voice (to avoid getting a web callback conversation)
    -- purpose=agent (to make sure this was a call which was connected to an agent - and not a call in self-service IVR/Bot or a call abandoned in a Queue before reaching an agent)
    -- ani=tel:${input.ANI} (you may adapt this to your environment - in mine, the ANI will appear as "tel:+33123456789" in the conversation details) - I have set the "tel:" in the request to provide just the ANI as input in the Architect flow)
  1. The Data Action contracts and configurations

In the following Data Action, I am using 2 things:

  • NBFOUND in the Output Contract is there so that I can easily check if the query returned 0 or 1 conversation in my Architect Flow.
    In both case the Data Action request is considered as successful (Success path on the Data Action), so I needed something simple to check if it was necessary to analyze the UserIdArray (NBFOUND == 1) or not (NBFOUND == 0)
  • ARRAYUSERID is defined as an array.
    As explained above, it is there so that my JSONPath expression can leverage a filter on the participants array.
    But there is an additonnal reason. You could have a call that gets connected to Agent1. Agent1 then transfers the call to another Agent, or to a Queue that could deliver the call to an agent (Agent2, or even Agent1 again).
    So what I'm just trying to say is that there could be multiple participants of type agent in the conversation.
    I have preferred to leverage purpose == agent in my filter, in case the call was transferred to a PureCloud user (just regular telephony enabled user - internal user - non agent).

Input Contract:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "Search for Previous Conversations By ANI",
  "description": "Get previous conversations for the given ANI.",
  "type": "object",
  "properties": {
    "INTERVAL": {
      "description": "The time interval to search",
      "type": "string",
      "minLength": 1
    },
    "ANI": {
      "description": "The ANI of the caller",
      "type": "string",
      "minLength": 1
    }
  },
  "additionalProperties": true
}

I have defined the input variables with minLength = 1.
This is just to avoid an error in the Architect Flow, where by mistake, an empty string would be passed to the Data Action (via a variable).

Output Contract:

{
  "type": "object",
  "properties": {
    "ARRAYUSERID": {
      "type": "array",
      "items": {
        "title": "USERID",
        "type": "string"
      }
    },
    "ARRAYPARTICIPANTID": {
      "type": "array",
      "items": {
        "title": "PARTICIPANTID",
        "type": "string"
      }
    },
    "NBFOUND": {
      "type": "integer"
    }
  },
  "additionalProperties": true
}

The ARRAYPARTICIPANTID is not necessary. I just used it to make sure my data action was working properly, in case the same agent was connected twice to the call (same userId, but different participantId).

Input Configuration:

{
  "requestUrlTemplate": "/api/v2/analytics/conversations/details/query",
  "requestType": "POST",
  "headers": {
    "Content-Type": "application/json"
  },
  "requestTemplate": "{\n \"interval\": \"${input.INTERVAL}\",\n \"order\": \"desc\",\n \"orderBy\": \"conversationStart\",\n \"paging\": {\n  \"pageSize\": 1,\n  \"pageNumber\": 1\n },\n \"segmentFilters\": [\n  {\n   \"type\": \"and\",\n   \"predicates\": [\n    {\n     \"type\": \"dimension\",\n     \"dimension\": \"mediaType\",\n     \"operator\": \"matches\",\n     \"value\": \"voice\"\n    },\n    {\n     \"type\": \"dimension\",\n     \"dimension\": \"purpose\",\n     \"operator\": \"matches\",\n     \"value\": \"agent\"\n    },\n    {\n     \"type\": \"dimension\",\n     \"dimension\": \"ani\",\n     \"operator\": \"matches\",\n     \"value\": \"tel:${input.ANI}\"\n    }\n   ]\n  }\n ]\n}"
}

Output Configuration:

{
  "translationMap": {
    "ARRAYUSERID": "$.conversations[0].participants[?(@.purpose ==\"agent\")].userId",
    "ARRAYPARTICIPANTID": "$.conversations[0].participants[?(@.purpose ==\"agent\")].participantId",
    "NBFOUND": "$.conversations.length()"
  },
  "translationMapDefaults": {
    "ARRAYUSERID": "[]",
    "ARRAYPARTICIPANTID": "[]",
    "NBFOUND": "0"
  },
  "successTemplate": "{\n   \"ARRAYUSERID\": ${ARRAYUSERID}\n, \"ARRAYPARTICIPANTID\": ${ARRAYPARTICIPANTID}\n, \"NBFOUND\": ${NBFOUND}\n}"
}

I can use .conversations[0] as I have limited the analytics query to return 1 result max (pageSize = 1). I have also leveraged the translationMapDefaults - specifically for the NBFOUND - so it returns 0 if it can't "find" .conversations.
Indeed, if there are 0 matching conversations, the result of the analytics query is just {}.

  1. The Architect Flow

We are almost there.. :slight_smile:
As I couldn't find a way to just get the last element of the ARRAYUSERID directly in the Data Action (there is maybe a way via Data Action configuration - but I personally couldn't find one quickly), I found it was faster to perform this in the Architect flow.

Note that the items in the ARRAYUSERID will be from oldest agent participant in the conversation (index 0) to the most recent (last element/item of the array).

In order to store the ARRAYUSERID output from the data action, I have used a variable which is defined as a Collection of Strings (it is important to set it this way - as it needs to store an array of strings).
My variable was named: Flow.MyUserIds (used in the expression below)

In case NBFOUND == 1, I am retrieving the last element of the array with the following expression:
GetAt(Flow.MyUserIds, Count(Flow.MyUserIds) - 1)

Hope this helps.

Regards,

Jerome

1 Like

Thank you so much for your help, it worked. Great support helping me to understand Parsing the Arrays Output.

We created the firstFromArray tool to help with situations like this:

--Jason

Unfortunately, it is the last element in the array which is to be retrieved - not the first.

Would you know if there is a way to auto populate interval in the architect to check last 30 days?

No, I don't think there is a way to auto-populate the interval inside the data action (pretty sure but I may be proven wrong by someone else :slight_smile: ).

But what you can do is to create the interval (as a string variable) using Architect Expression functions.
I tried the following and it appeared to work.
I have defined a string variable (using Update Data block) to hold my interval with the following expression:

ToString(AddDays(GetCurrentDateTimeUtc(), -31)) + "/" + ToString(GetCurrentDateTimeUtc())

You could also use AddMonths with -1 as second parameter.

I have used AddDays with -31 as it appears the POST /api/v2/analytics/conversations/details/query can accept a search interval of 31 days if a filter (segmentFilter here) is set.

Without any filters set, it seems the POST /api/v2/analytics/conversations/details/query can accept a search interval of 7 days max.
In this case, you would use AddDays and -7.

1 Like

This topic was automatically closed 31 days after the last reply. New replies are no longer allowed.