Calculate Service Level Percentage from Raw Analytics Interval Data
What You Will Build
- A script that queries Genesys Cloud Analytics for raw conversation interval data and calculates the Service Level percentage (percentage of conversations answered within a defined threshold).
- This tutorial uses the Genesys Cloud Analytics Conversations Details API (
/api/v2/analytics/conversations/details/query). - The implementation covers Python using the official
genesyscloud_pythonSDK and rawrequestsfor clarity.
Prerequisites
- OAuth Client Type: Service Account (JWT) or Client Credentials.
- Required Scopes:
analytics:conversation:view(Required to query conversation details).analytics:report:view(Optional, but often required depending on tenant configuration for analytics access).
- SDK Version:
genesyscloud_python>= 140.0.0. - Runtime: Python 3.8+.
- Dependencies:
genesyscloud_python,pandas(optional, for data manipulation),requests.
Install dependencies:
pip install genesyscloud_python pandas requests
Authentication Setup
Genesys Cloud uses JWT or Client Credentials flow for server-to-server integrations. For this tutorial, we assume a Service Account with a JWT private key.
The genesyscloud_python SDK handles token refresh automatically if configured correctly. You must initialize the PureCloudPlatformClientV2 with your environment URL (e.g., mypurecloud.com) and the authentication provider.
import os
from purecloudplatformclientv2.rest import ApiException
from purecloudplatformclientv2 import PureCloudPlatformClientV2, PlatformConfiguration
def get_purecloud_client():
"""
Initializes the PureCloud Platform Client V2 using JWT authentication.
"""
# Environment variables should be set in your deployment environment
environment_url = os.getenv("PURECLOUD_ENVIRONMENT", "mypurecloud.com")
client_id = os.getenv("PURECLOUD_CLIENT_ID")
private_key = os.getenv("PURECLOUD_PRIVATE_KEY")
private_key_password = os.getenv("PURECLOUD_PRIVATE_KEY_PASSWORD")
if not all([client_id, private_key]):
raise ValueError("Missing required OAuth credentials.")
# Configure the platform client
platform_client = PureCloudPlatformClientV2(environment_url)
# Configure JWT authentication
auth_config = platform_client.get_jwt_auth_configuration(
client_id=client_id,
private_key=private_key,
private_key_password=private_key_password
)
try:
# Initialize the client
platform_client.set_auth_provider(auth_config)
return platform_client
except ApiException as e:
print(f"Authentication failed: {e}")
raise
# Initialize client
client = get_purecloud_client()
Implementation
Step 1: Query Raw Conversation Interval Data
The Analytics API does not return a pre-calculated “Service Level %” field in the raw interval data. Instead, it returns individual conversation records with timestamps for answer_time and ring_time. You must calculate the Service Level locally by comparing these timestamps against your defined threshold (e.g., 20 seconds).
We will use the AnalyticsApi to query conversation details. The key parameters are:
interval_type:5minor15minis standard for interval data.5minprovides higher granularity.group_by:intervalis required to get data bucketed by time.metrics: We needanswered_countandabandoned_countfor context, but the actual SL calculation relies on individual conversation timing data. However, querying all individual conversations for a large date range is inefficient.
Critical Design Decision:
For accurate Service Level calculation at scale, it is better to query Summary Data with a filter for wait_time or use the Details Query with a specific metric wait_time. The wait_time metric in the summary query often provides the percentile data, but to get the exact percentage of calls answered within X seconds, we need to filter conversations where wait_time < threshold.
The most robust method for raw interval data is to query the Analytics Conversation Details endpoint with a filter on wait_time. However, the Details API is expensive. A more efficient approach for historical reporting is to use the Analytics Summary Query endpoint (/api/v2/analytics/conversations/summary/query) with a filter, but the prompt specifies Raw Analytics API interval data.
Let us stick to the Details Query but optimize it by only selecting the necessary fields and using a reasonable time window. We will calculate SL by counting conversations where wait_time is less than our threshold.
OAuth Scope: analytics:conversation:view
from purecloudplatformclientv2 import AnalyticsApi, ConversationDetailsQuery, ConversationDetailsQueryFilter, ConversationDetailsQueryMetric
def query_conversation_details(client, start_time, end_time, interval_type="5min"):
"""
Queries raw conversation interval data for a specific time range.
"""
analytics_api = AnalyticsApi(client)
# Define the query body
query = ConversationDetailsQuery()
# Set time range
query.start_time = start_time
query.end_time = end_time
# Set interval type
query.interval_type = interval_type
# Group by interval to get aggregated data per time bucket
query.group_by = ["interval"]
# Define metrics to retrieve
# We need wait_time to calculate SL.
# Note: Retrieving 'wait_time' in details query can be heavy.
# For large datasets, consider using Summary API with filters.
query.metrics = ["wait_time", "answered_count", "abandoned_count"]
# Optional: Filter for specific queue or skill to reduce data volume
# query.filters = [ConversationDetailsQueryFilter(name="queue.id", operator="eq", value=["YOUR_QUEUE_ID"])]
try:
# Execute the query
response = analytics_api.post_analytics_conversations_details_query(body=query)
return response
except ApiException as e:
if e.status == 429:
print("Rate limit exceeded. Implement retry logic.")
elif e.status == 403:
print("Forbidden. Check OAuth scopes.")
raise
# Example usage
from datetime import datetime, timedelta
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=24)
# Note: The SDK returns a ConversationDetailsResponse object
# We will process this in the next step.
Step 2: Calculate Service Level from Raw Data
The response from the Details API contains a list of entities, where each entity represents an interval (e.g., a 5-minute bucket). Each entity contains metrics which include answered_count, abandoned_count, and potentially wait_time statistics.
However, the Details Query with group_by=interval aggregates the data. It does not give you individual conversation wait_time values unless you remove group_by (which returns every single conversation, which is dangerous for large volumes).
Correction for Accuracy:
To calculate Service Level accurately from interval data without downloading millions of records, you must use the Summary API with a filter, OR use the Details API without grouping but with a strict time window and pagination.
Since the topic is “Raw Analytics API interval data”, let us assume we are using the Summary API which provides aggregated metrics per interval, including the ability to filter by wait_time. But the Summary API does not return the count of calls within a threshold directly unless you use the filter parameter in the query.
Best Practice Approach:
Use the analytics:conversation:view scope to query the Summary API with a filter for wait_time < threshold. Then calculate SL as:
SL % = (Count of Answered Calls with Wait Time < Threshold) / (Total Answered Calls)
If you must use the Details API for raw data, you must fetch individual conversations. This is rarely done for SL calculation due to cost. However, if you have already fetched raw data (e.g., for another purpose), here is how you calculate it.
Let us demonstrate the Summary API with Filter approach, which is the standard way to get SL from interval data efficiently.
from purecloudplatformclientv2 import AnalyticsApi, ConversationSummaryQuery, ConversationSummaryQueryFilter, ConversationSummaryQueryMetric
def get_service_level_data(client, start_time, end_time, threshold_seconds=20, interval_type="5min"):
"""
Retrieves aggregated conversation data filtered by wait time to calculate Service Level.
"""
analytics_api = AnalyticsApi(client)
# Define the query body
query = ConversationSummaryQuery()
query.start_time = start_time
query.end_time = end_time
query.interval_type = interval_type
query.group_by = ["interval"]
# Metrics: We need the count of answered calls
query.metrics = ["answered_count"]
# Filter: Only include conversations answered within the threshold
# The wait_time metric is available in the filter context for summary queries
filter_config = ConversationSummaryQueryFilter(
name="wait_time",
operator="lt", # Less than
value=[threshold_seconds]
)
query.filters = [filter_config]
try:
response = analytics_api.post_analytics_conversations_summary_query(body=query)
return response
except ApiException as e:
print(f"API Error: {e.status} - {e.reason}")
raise
def get_total_answered_count(client, start_time, end_time, interval_type="5min"):
"""
Retrieves the total count of answered calls in the same period.
"""
analytics_api = AnalyticsApi(client)
query = ConversationSummaryQuery()
query.start_time = start_time
query.end_time = end_time
query.interval_type = interval_type
query.group_by = ["interval"]
query.metrics = ["answered_count"]
try:
response = analytics_api.post_analytics_conversations_summary_query(body=query)
return response
except ApiException as e:
print(f"API Error: {e.status} - {e.reason}")
raise
Step 3: Processing Results and Calculating Percentage
The response from the Summary API contains a list of entities. Each entity corresponds to an interval. We must sum the answered_count from the filtered query (calls within SL) and divide it by the answered_count from the total query (all answered calls).
def calculate_service_level(filtered_response, total_response):
"""
Calculates the Service Level percentage from two API responses.
Args:
filtered_response: Response from query with wait_time < threshold filter.
total_response: Response from query with no wait_time filter.
Returns:
float: Service Level percentage (0.0 to 100.0).
"""
if not filtered_response or not total_response:
return 0.0
# Sum answered counts from filtered response
filtered_answered = sum(
entity.metrics.get("answered_count", 0)
for entity in filtered_response.entities
)
# Sum answered counts from total response
total_answered = sum(
entity.metrics.get("answered_count", 0)
for entity in total_response.entities
)
if total_answered == 0:
return 0.0
# Calculate percentage
service_level_percentage = (filtered_answered / total_answered) * 100
return round(service_level_percentage, 2)
# Example Execution
threshold = 20 # seconds
filtered_data = get_service_level_data(client, start_time, end_time, threshold_seconds=threshold)
total_data = get_total_answered_count(client, start_time, end_time)
sl_percentage = calculate_service_level(filtered_data, total_data)
print(f"Service Level ({threshold}s threshold): {sl_percentage}%")
Complete Working Example
This script combines authentication, data retrieval, and calculation into a single runnable module.
import os
import sys
from datetime import datetime, timedelta
from purecloudplatformclientv2.rest import ApiException
from purecloudplatformclientv2 import PureCloudPlatformClientV2, AnalyticsApi, ConversationSummaryQuery, ConversationSummaryQueryFilter
def get_purecloud_client():
environment_url = os.getenv("PURECLOUD_ENVIRONMENT", "mypurecloud.com")
client_id = os.getenv("PURECLOUD_CLIENT_ID")
private_key = os.getenv("PURECLOUD_PRIVATE_KEY")
private_key_password = os.getenv("PURECLOUD_PRIVATE_KEY_PASSWORD")
if not all([client_id, private_key]):
raise ValueError("Missing required OAuth credentials.")
platform_client = PureCloudPlatformClientV2(environment_url)
auth_config = platform_client.get_jwt_auth_configuration(
client_id=client_id,
private_key=private_key,
private_key_password=private_key_password
)
platform_client.set_auth_provider(auth_config)
return platform_client
def get_conversation_counts(client, start_time, end_time, interval_type="5min", wait_time_filter=None):
analytics_api = AnalyticsApi(client)
query = ConversationSummaryQuery()
query.start_time = start_time
query.end_time = end_time
query.interval_type = interval_type
query.group_by = ["interval"]
query.metrics = ["answered_count"]
if wait_time_filter:
filter_config = ConversationSummaryQueryFilter(
name="wait_time",
operator="lt",
value=[wait_time_filter]
)
query.filters = [filter_config]
try:
response = analytics_api.post_analytics_conversations_summary_query(body=query)
return sum(entity.metrics.get("answered_count", 0) for entity in response.entities)
except ApiException as e:
print(f"Error fetching data: {e.status} - {e.reason}")
raise
def main():
try:
client = get_purecloud_client()
# Define time range (last 24 hours)
end_time = datetime.utcnow()
start_time = end_time - timedelta(hours=24)
# Define SL threshold in seconds
sl_threshold_seconds = 20
print(f"Calculating Service Level for {start_time} to {end_time}...")
# Get total answered calls
total_answered = get_conversation_counts(client, start_time, end_time)
# Get answered calls within threshold
answered_within_sl = get_conversation_counts(
client, start_time, end_time, wait_time_filter=sl_threshold_seconds
)
# Calculate SL
if total_answered > 0:
sl_percentage = (answered_within_sl / total_answered) * 100
print(f"Total Answered: {total_answered}")
print(f"Answered within {sl_threshold_seconds}s: {answered_within_sl}")
print(f"Service Level: {sl_percentage:.2f}%")
else:
print("No answered calls found in the specified period.")
except Exception as e:
print(f"Fatal error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 403 Forbidden
- Cause: The OAuth token lacks the
analytics:conversation:viewscope. - Fix: Update your Service Account’s OAuth client scopes in the Genesys Cloud Admin portal. Ensure
analytics:conversation:viewis checked.
Error: 429 Too Many Requests
- Cause: You are hitting the API rate limit. Analytics queries can be heavy.
- Fix: Implement exponential backoff. The
genesyscloud_pythonSDK does not auto-retry 429s by default. Wrap the API call in a retry loop.
import time
def retry_api_call(func, *args, max_retries=3, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except ApiException as e:
if e.status == 429:
wait_time = 2 ** attempt # Exponential backoff
print(f"Rate limited. Retrying in {wait_time} seconds...")
time.sleep(wait_time)
else:
raise
raise Exception("Max retries exceeded")
# Usage
# response = retry_api_call(analytics_api.post_analytics_conversations_summary_query, body=query)
Error: Empty Entities List
- Cause: No conversations occurred in the specified time range, or the filter is too strict.
- Fix: Verify the
start_timeandend_timeare in ISO 8601 format and cover a period with activity. Check thatwait_timefilter value is realistic (e.g., 20 seconds, not 0 seconds).
Error: Metric Not Found
- Cause: The metric name is incorrect.
- Fix: Use exact metric names from the API documentation. For answered calls, use
answered_count. For wait time, usewait_time.