Architecting Evaluation Trend Analysis Dashboards with Rolling Average Performance Tracking

Architecting Evaluation Trend Analysis Dashboards with Rolling Average Performance Tracking

What This Guide Covers

This guide details the architectural pattern for constructing a Genesys Cloud CX dashboard that visualizes Quality Management (QM) evaluation scores using a 30-day rolling average. You will learn to utilize the Genesys Cloud Analytics API to retrieve granular evaluation data, implement the sliding window calculation logic in a frontend application, and bind the resulting dataset to a custom dashboard widget. The end result is a stable, noise-resistant performance metric that accurately reflects agent trends without the volatility of daily raw scores.

Prerequisites, Roles & Licensing

Licensing & Add-ons

  • Genesys Cloud CX License: Standard CX 1 or higher.
  • Quality Management Add-on: Required. This is a separate add-on license. Without it, the QM evaluation endpoints return empty datasets or 403 Forbidden errors.
  • WEM (Workforce Engagement Management) Add-on: Not strictly required for the data itself, but recommended if you intend to correlate QM scores with adherence or shrinkage data in a unified view.

Permissions & Roles

To build and query this dashboard, the user account must possess the following permissions:

  1. Quality Management:
    • Quality > Evaluation > View
    • Quality > Evaluation > Search
    • Quality > Form > View
  2. Analytics:
    • Analytics > Report > View
    • Analytics > Dashboard > View
    • Analytics > Dashboard > Edit (if creating the dashboard via UI)
    • Analytics > API > Query (critical for programmatic access)
  3. Administration:
    • Organization > View (to retrieve division IDs for scoping)

OAuth Scopes

If you are building a custom integration or using a third-party BI tool (Tableau, PowerBI) via the Genesys Cloud Connector, your OAuth client must have the following scopes:

  • analytics:report:read
  • quality:evaluation:read
  • admin:division:read

External Dependencies

  • Frontend Framework: React, Angular, or Vue.js with a charting library (Chart.js, D3.js, or ApexCharts).
  • Backend Service: Node.js, Python, or C# service to handle OAuth token management and API rate limiting.
  • Data Storage: Optional. If you are caching historical data to avoid hitting API rate limits, you need a persistent store like PostgreSQL or Redis.

The Implementation Deep-Dive

1. Understanding the Data Model: Evaluations vs. Scores

Before writing a single line of code, you must understand the distinction between an Evaluation object and a Score in Genesys Cloud. This distinction is the source of 90% of the bugs in QM reporting.

An Evaluation is the container. It represents a single interaction (call, chat, email) that has been assessed. It contains metadata: who was the agent, who was the evaluator, what form was used, and when it was completed.

A Score is the aggregate result. A single Evaluation can contain multiple Scores if the QM Form is structured with sections (e.g., “Compliance,” “Soft Skills,” “Technical Accuracy”). Each section returns a score. The “Total” score is often a weighted average of these sections.

The Trap: Querying the evaluations endpoint and assuming the score field on the root object is the final grade. In many complex forms, the root score field is null or represents only the first section. You must iterate through the scores array within the evaluation object to find the section marked as isTotal or calculate the weighted average manually based on the form definition.

Architectural Reasoning: We do not rely on pre-built “Quality” reports in the Genesys UI for this specific rolling average requirement. The standard Genesys QM reports provide point-in-time snapshots or simple averages over a fixed period (last 30 days total). They do not provide a rolling window (Day N-30 to Day N, then Day N-29 to Day N+1). To achieve a true rolling average that shifts daily, we must pull the raw evaluation data and perform the windowing logic in our application layer.

2. Retrieving Granular Evaluation Data via API

We use the GET /api/v2/analytics/evaluations/summary endpoint. This endpoint is preferred over GET /api/v2/quality/evaluations because the analytics endpoint is optimized for large datasets and supports aggregation filters that reduce payload size.

The API Payload Construction

You must filter by completedDate to ensure you only fetch evaluations that are finalized. Uncompleted evaluations should not influence performance trends.

Endpoint: GET /api/v2/analytics/evaluations/summary

Query Parameters:

  • interval: P1D (We fetch daily granularity to allow for flexible rolling window calculations in code).
  • dateFrom: 2023-10-01T00:00:00.000Z (Start of your historical window).
  • dateTo: 2023-10-31T23:59:59.999Z (End of your historical window).
  • groupBy: agentId,formId (We group by agent and form to track individual performance against specific criteria).
  • metrics: evaluations.score.average (The core metric).
  • filter: completedDate > 2023-10-01T00:00:00.000Z

JSON Body (if using POST for complex filters):

{
  "interval": "P1D",
  "dateFrom": "2023-10-01T00:00:00.000Z",
  "dateTo": "2023-10-31T23:59:59.999Z",
  "groupBy": [
    "agentId",
    "formId"
  ],
  "metrics": [
    {
      "name": "evaluations.score.average"
    }
  ],
  "filter": {
    "type": "and",
    "clauses": [
      {
        "type": "field",
        "field": "completedDate",
        "operator": ">",
        "value": "2023-10-01T00:00:00.000Z"
      }
    ]
  }
}

The Trap: Ignoring the formId in the groupBy clause. If an agent is evaluated against multiple forms (e.g., “Sales Call” and “Support Call”), averaging their scores across all forms creates a distorted metric. A 90% on a difficult technical form and a 90% on an easy greeting form are not equivalent if the forms have different weighting structures. Always segment by formId or ensure your filter restricts to a single form.

Architectural Reasoning: We fetch data at the P1D (one day) interval. Even though we want a rolling average, we need the daily granularity to recalculate the window. If we fetch P1M (one month) data, we lose the ability to shift the window day-by-day. The computational cost of processing daily rows in memory is negligible compared to the latency of making repeated API calls for each day.

3. Implementing the Rolling Average Logic

The rolling average calculation must be performed on the client side or in a backend service. This logic implements a “Sliding Window” algorithm.

The Algorithm

  1. Normalize Data: Flatten the API response into a list of objects: { date: "2023-10-01", agentId: "123", formId: "456", score: 85.5 }.
  2. Sort: Sort the list by date ascending.
  3. Window Definition: Define WINDOW_SIZE = 30 (days).
  4. Iteration: For each day D in the dataset:
    • Identify all evaluations where date >= D - WINDOW_SIZE AND date <= D.
    • Calculate the average of the score field for these evaluations.
    • Store the result as { date: D, rollingAvg: calculatedValue }.

Code Implementation (JavaScript/TypeScript)

This snippet demonstrates the efficient calculation using a sliding window approach to avoid O(N^2) complexity.

/**
 * Calculates a 30-day rolling average for agent evaluation scores.
 * @param {Array} dailyData - Array of objects { date: string, score: number }
 * @param {number} windowSize - Number of days in the rolling window
 * @returns {Array} Array of objects { date: string, rollingAverage: number }
 */
function calculateRollingAverage(dailyData, windowSize = 30) {
  // Ensure data is sorted by date
  const sortedData = [...dailyData].sort((a, b) => new Date(a.date) - new Date(b.date));
  
  const results = [];
  let windowSum = 0;
  let windowCount = 0;
  
  // Use a queue to manage the window
  const windowQueue = [];

  for (let i = 0; i < sortedData.length; i++) {
    const currentDay = sortedData[i];
    const currentDate = new Date(currentDay.date);

    // Add current day's score to the window
    windowSum += currentDay.score;
    windowCount += 1;
    windowQueue.push(currentDay);

    // Remove scores older than the window size
    while (windowQueue.length > 0) {
      const oldestDay = windowQueue[0];
      const oldestDate = new Date(oldestDay.date);
      const diffTime = Math.abs(currentDate - oldestDate);
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); 

      if (diffDays >= windowSize) {
        windowSum -= oldestDay.score;
        windowCount -= 1;
        windowQueue.shift();
      } else {
        break;
      }
    }

    // Calculate average only if we have sufficient data points
    // Optional: Only calculate if windowCount >= 10 to avoid early volatility
    if (windowCount > 0) {
      const rollingAvg = windowSum / windowCount;
      results.push({
        date: currentDay.date,
        rollingAverage: parseFloat(rollingAvg.toFixed(2))
      });
    }
  }

  return results;
}

The Trap: Dividing by windowSize (30) instead of windowCount (actual number of evaluations). If an agent has no evaluations for 5 days in the window, dividing by 30 artificially depresses the average. You must divide by the actual count of evaluations within that window. This preserves the integrity of the metric during holidays or low-volume periods.

Architectural Reasoning: We use a queue-based sliding window. A naive approach would filter the entire array for every single day, resulting in O(N^2) performance. For an agent with 5 years of data (1,800 days), this is 3.2 million operations. The queue approach is O(N), making it instantaneous even for large datasets.

4. Binding to the Dashboard Widget

Genesys Cloud does not have a native “Rolling Average” chart type. You must use a Custom Widget (if using the Dashboard Builder with custom HTML/JS) or export the data to an external BI tool. Assuming you are building a custom React component embedded in an iframe or a standalone app linked to the dashboard:

  1. Create the Chart: Use Chart.js or ApexCharts.
  2. Configure Axes:
    • X-Axis: Time scale (linear or time).
    • Y-Axis: Linear scale, min 0, max 100 (or 10 if using a 1-10 scale).
  3. Add Threshold Lines: Add static horizontal lines for “Target” (e.g., 90%) and “Minimum” (e.g., 75%).
  4. Tooltip Configuration: Configure the tooltip to show both the rollingAverage and the rawScore for that specific day (if available) to provide context.

The Trap: Mixing raw daily scores with the rolling average on the same line chart. This creates a “spaghetti” effect that is unreadable. If you must show both, use a dual-axis chart: the rolling average as a smooth line (primary axis) and the raw daily scores as a scatter plot or faint background line (secondary axis). Never overlay two line charts of different volatilities on the same axis.

Architectural Reasoning: We prioritize the rolling average as the primary visual element. The human brain detects trends in smooth lines better than in jagged data. By suppressing the noise of daily variance, the dashboard answers the business question: “Is this agent improving or declining?” rather than “Did this agent have a bad day?”

Validation, Edge Cases & Troubleshooting

Edge Case 1: The “Cold Start” Volatility

The Failure Condition: An agent starts a new role. For the first 20 days, their rolling average fluctuates wildly because the window contains only 10 evaluations. A single bad score drops the average by 10%.

The Root Cause: The denominator (windowCount) is too small. The statistical significance of the average is low.

The Solution: Implement a “Minimum Data Threshold.” Do not render the rolling average line until windowCount >= 15. In the UI, display a message: “Insufficient data for trend analysis.” Alternatively, switch the window size dynamically: use a 7-day window for the first 30 days, then switch to a 30-day window. This provides faster feedback during onboarding while stabilizing for long-term tracking.

Edge Case 2: Form Versioning Drift

The Failure Condition: The Quality team updates the QM Form. They change a question from “Did the agent greet the customer?” (Pass/Fail) to a 1-5 scale on “Quality of Greeting.” The rolling average suddenly spikes or drops, not because agent performance changed, but because the scoring methodology changed.

The Root Cause: The API query groups by formId. If the form is updated in place, the formId remains the same, but the scoring logic changes. The rolling average mixes old binary scores with new granular scores.

The Solution:

  1. Best Practice: Never edit a live QM Form. Always create a new version. When you switch to the new version, the formId changes.
  2. Data Handling: Your dashboard must allow filtering by formVersion. When analyzing trends across a form change, you must normalize the data. For example, map the old Pass/Fail (100/0) to a 5/0 scale.
  3. UI Indicator: Add a vertical dashed line on the chart at the date of the form change, labeled “Form Updated.” This contextualizes the anomaly for the viewer.

Edge Case 3: Evaluator Bias Skewing the Trend

The Failure Condition: One evaluator is known to be “strict” (averages 70%), while another is “lenient” (averages 90%). An agent is evaluated by the strict evaluator for two weeks, then the lenient one. The rolling average shows a sharp upward trend, but the agent’s performance did not change.

The Root Cause: The rolling average aggregates scores without normalizing for evaluator variance.

The Solution:

  1. Advanced Query: Include evaluatorId in the groupBy clause.
  2. Normalization Logic: Calculate the average score per evaluator across the entire team. Create a “Bias Factor” for each evaluator (e.g., Strict Evaluator = -10%, Lenient Evaluator = +10%).
  3. Adjusted Score: When calculating the rolling average, apply the bias factor to each score before averaging.
    AdjustedScore = RawScore * (1 + BiasFactor)
    This requires a secondary calculation step to determine the team-wide average per evaluator, which should be updated weekly.

Official References