Pulling raw interval data via analytics:query:report for a queue. I need to calculate the Service Level percentage (e.g., 80% answered in 20s) from the returned JSON. The payload contains answeredWithinTarget and totalAnswered metrics, but the aggregation across multiple intervals feels off. Here’s a snippet of how I’m summing the arrays:
const sl = (sum(answers) / sum(total)) * 100;
Is there a specific metric key I should be targeting instead of summing the intervals manually?
- SDK: Genesys Cloud JS SDK v4
- Endpoint:
/api/v2/analytics/queue/summary/query
- Timezone: Africa/Lagos
You’re hitting a classic aggregation trap here. When you pull raw interval data from /api/v2/analytics/queues/summary or similar endpoints, you’re dealing with time-sliced buckets. If you just sum the numerators and denominators blindly, you might be including intervals where totalAnswered is zero but the target wasn’t technically “missed” in a way that skews the denominator if you’re not careful. It’s safer to filter out empty intervals first.
Here’s how I handle it in my Python scripts for compliance reporting. It’s a bit more verbose, but it prevents those weird 0/0 errors or inflated percentages when a queue has dead spots.
def calculate_service_level(intervals):
total_answered = 0
total_within_target = 0
for interval in intervals:
# Only process intervals where actual work was done
if interval['totalAnswered'] > 0:
total_answered += interval['totalAnswered']
total_within_target += interval['answeredWithinTarget']
if total_answered == 0:
return 0.0 # Avoid division by zero
return (total_within_target / total_answered) * 100
The key is that if check. If you skip it, you might end up dividing by a larger number than necessary if the API returns placeholder zeros for quiet hours. Also, watch out for the targetAnswerTimeSeconds. If your SLA is 20 seconds, make sure the report filter matches that exactly, otherwise answeredWithinTarget will be meaningless.
Be careful with timezone offsets in the startTime and endTime parameters. The API expects UTC, but if you’re building this for a London-based team, you’ll need to convert your local start/end times to UTC before sending the request. I’ve seen too many reports fail because they used BST instead of GMT during the switch-over. Just double-check your datetime objects are naive or explicitly UTC.
The approach above is fine for a quick dash, but it’s dangerous for reporting. You’re ignoring the interval duration. Service Level is a rate, not a count. If you sum answeredWithinTarget and totalAnswered across a 15-minute window where only 2 minutes had traffic, your percentage looks inflated because you’re treating low-volume silence as “good performance.”
You need to weight by the actual time or, better yet, use the pre-calculated fields if the endpoint supports it. The /api/v2/analytics/queues/summary endpoint has percentAnsweredWithinTarget right in the response. Use that. If you’re stuck with raw intervals from query:report, you have to handle the zero-division and the weighting manually.
Don’t just sum the numerators. Check the interval length.
let totalWeightedSL = 0;
let totalCalls = 0;
data.intervals.forEach(interval => {
// Skip empty intervals to avoid skewing the baseline
if (interval.totalAnswered === 0) return;
const intervalSL = interval.answeredWithinTarget / interval.totalAnswered;
// Weight by the number of calls in that specific bucket
totalWeightedSL += (intervalSL * interval.totalAnswered);
totalCalls += interval.totalAnswered;
});
const finalSL = totalCalls > 0 ? (totalWeightedSL / totalCalls) * 100 : 0;
This gives you the true weighted average. If you just do sum(a) / sum(b), you’re assuming every interval has equal importance regardless of volume. A quiet night with 1 call answered in 1s will skew your SL to 100% if you don’t weight it against the 500 calls in the morning rush.
Also, watch out for the target value. If you’re mixing intervals with different service level targets (e.g., shifting from 20s to 30s mid-day), your answeredWithinTarget field changes meaning. Make sure your query filters for a single target duration. Otherwise, you’re comparing apples to oranges.
The API returns answeredWithinTarget based on the target defined in the query parameters. If you change the target in the query, the metric changes. Keep it consistent.
The logic in the first post is technically correct for a simple average, but it breaks down when you have mixed traffic types or partial intervals. You can’t just sum answeredWithinTarget and totalAnswered if those fields don’t align on the same granular level.
In if you are pulling this via a REST Proxy, you need to check the groupBy parameter. If you group by interval, you get time slices. If you group by queueId, you get totals. The issue is often that totalAnswered includes abandoned calls in some metric sets, while answeredWithinTarget strictly counts those that met the SLA.
Here is how I handle it in a script to avoid the “empty interval” skew mentioned by . You need to filter out intervals where the denominator is zero before dividing.
// Assuming `intervals` is the array from the REST Proxy response
const validIntervals = intervals.filter(i => i.totalAnswered > 0);
if (validIntervals.length === 0) {
return 0; // Avoid division by zero
}
const totalAnswered = validIntervals.reduce((acc, curr) => acc + curr.totalAnswered, 0);
const answeredWithinTarget = validIntervals.reduce((acc, curr) => acc + curr.answeredWithinTarget, 0);
// Calculate Service Level
const serviceLevel = (answeredWithinTarget / totalAnswered) * 100;
console.log("Service Level:", serviceLevel.toFixed(2) + "%");
Also, watch out for the includeZeroCount flag in your query. If that is true, you are summing zeros which drags the percentage down if your logic is inverted, or inflates it if you are counting “good” intervals. Stick to filtering out zero denominators.
One more thing. The API returns these as integers. If you are doing this in a flow, ensure you cast to float before dividing. Integer division in some actions truncates the result to 0 if the numerator is smaller than the denominator.
Check your query payload. Make sure you are requesting the metrics array explicitly. Sometimes the default view omits answeredWithinTarget to save bandwidth. You won’t see it in the JSON if you didn’t ask for it.