> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# QA scorecard trends: read the org-wide score series and act on drift

> The supervisor read of GET /quality/trends — the per-day average official QA score over the evaluations pipeline, its parameters and response shape, how to spot drift after coaching, compare agents, and tie dips to Practice Studio sessions.

# QA scorecard trends: reading the org-wide score series

The rest of the Quality suite answers point-in-time questions: the evaluations ledger answers "what was scored", the leaderboard answers "who ranks where right now", the hub's auto-scoring rollup answers "how are we doing this window". The **trend series** answers a different one: **is quality moving, and where?** It charts the per-day average of official QA scores across your whole organization over a window of up to 90 days, with breakdowns by agent and by scorecard form.

Use this guide when the leaderboard tells you someone is low and you need to know whether it is a blip or a slide, when you ran coaching and need to see whether scores recovered, or when you want the trend in your own tooling via `GET /api/v1/quality/trends`.

## 1. What the trend answers — and what counts as official

Every point on the trend is a per-day average of the **weighted total score** (0–100) over the `qa_evaluations` pipeline — the same scorecards you grade under **Quality → Evaluations**. Three exclusion rules decide which evaluations count, so the trend tracks the current quality signal rather than history or self-assessment:

* **Self-evaluations never enter the trend.** An agent scoring their own conversation is excluded (agent equals reviewer means it is not an official score) — the same rule the leaderboard applies, so the trend and the ranking can never drift on self-grading.
* **Appealed and resolved rows are out.** An appealed score is under dispute and a resolved row is superseded; both are audit trail, not signal. Pending and acknowledged rows **do** count — acknowledgement is a notification step, the score stands either way.
* **The day bucket is the evaluation's creation day.** A backfilled score lands in the day it was authored, not the day the call happened, so heavy backfill weeks show up as eval-volume spikes on recent days.

The dashboard renders this series as the QA score trend bars on the **Quality** hub (7-, 30-, or 90-day window, drillable by agent and form). The API endpoint below is the same series, so anything you read on the hub you can also pull into a wallboard or BI tool.

## 2. `GET /quality/trends` walk-through

```
GET /api/v1/quality/trends?days=30&agent_id=agent_123&form_id=form_456
```

Reviewer-scoped: an owner, admin, or supervisor JWT or API key. Read-only.

### Query parameters

| Parameter  | Type          | Default    | Behavior                                                                                                                                                                         |
| ---------- | ------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `days`     | integer, 1–90 | `30`       | Look-back window over each evaluation's creation time. Values above 90 are capped; anything out of range or non-numeric falls back to 30 — a malformed read never returns a 400. |
| `form_id`  | string        | all forms  | Restricts the whole series to one scorecard form (rubric). Use a form id from **Quality → Evaluations → Scorecards** to isolate one rubric.                                      |
| `agent_id` | string        | all agents | Drills the whole series into one evaluated agent — trend, totals, and breakdowns all re-scope.                                                                                   |

All three are lenient by design: a bad value degrades to the safe default instead of rejecting the request, so dashboards and scripts can pass user input straight through.

### Response shape

```json theme={null}
{
  "data": {
    "window": { "days": 30, "form_id": null, "agent_id": null },
    "totals": {
      "total_evals": 412,
      "avg_score": 78.4,
      "scored_agents": 23,
      "active_forms": 2
    },
    "trend": [
      { "day": "2026-09-01", "evals": 14, "avg_score": 76.2 },
      { "day": "2026-09-02", "evals": 12, "avg_score": 79.8 }
    ],
    "by_agent": [
      { "agent_id": "agent_123", "evals": 31, "avg_score": 84.1 },
      { "agent_id": "agent_087", "evals": 28, "avg_score": 61.3 }
    ],
    "by_form": [
      { "form_id": "form_456", "evals": 240, "avg_score": 79.9 },
      { "form_id": "form_789", "evals": 172, "avg_score": 76.0 }
    ]
  },
  "meta": { "request_id": "req_...", "timestamp": "2026-09-15T08:00:00.000Z" }
}
```

* **`window`** echoes the resolved scope — what was actually applied after clamping, not necessarily what you sent.
* **`totals`** is the window rollup: official evaluations scored, mean weighted total (or `null` when nothing was scored), distinct scored agents, and distinct scorecard forms in play. Read `avg_score` as the org's QA level for the window and `evals` as how much evidence backs it.
* **`trend`** is the per-day time series, chronological, one point per day that had evaluations: the day's evaluation count and its average score. Days with zero evaluations produce no point — treat a missing day as "nothing scored", not "scored zero".
* **`by_agent`** ranks up to 25 agents by evaluation volume (heaviest first) with each agent's average — the org-level "compare agents" cut.
* **`by_form`** is the same cut per scorecard form, so you can tell whether two rubrics grade the same team consistently.

### Example request and a minimal dashboard render

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl "https://api.orbit.devotel.io/api/v1/quality/trends?days=30&agent_id=agent_087" \
      -H "X-API-Key: $ORBIT_API_KEY"
    ```
  </Tab>

  <Tab title="Node.js SDK">
    ```typescript theme={null}
    import { Orbit } from '@devotel-orbit/node';

    interface TrendPoint { day: string; evals: number; avg_score: number | null; }
    interface TrendsData {
      window: { days: number; form_id: string | null; agent_id: string | null };
      totals: { total_evals: number; avg_score: number | null; scored_agents: number; active_forms: number };
      trend: TrendPoint[];
      by_agent: { agent_id: string; evals: number; avg_score: number | null }[];
      by_form: { form_id: string; evals: number; avg_score: number | null }[];
    }

    const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! });
    const { data } = await orbit.request<TrendsData>(
      'GET',
      '/api/v1/quality/trends?days=30&agent_id=agent_087',
    );
    ```
  </Tab>
</Tabs>

Render the series as a line or bar per day and color the point by the same thresholds the rest of the Quality suite uses — green from 80, amber from 60, red below — so the trend matches the KPI tiles and the hub bars one-to-one:

```typescript theme={null}
function scoreColor(avg: number | null): string {
  if (avg === null) return 'gray';
  if (avg >= 80) return 'green';
  if (avg >= 60) return 'amber';
  return 'red';
}

for (const point of data.trend) {
  renderPoint({ day: point.day, value: point.avg_score, color: scoreColor(point.avg_score) });
}
```

## 3. Supervisor workflows

### Spot drift after coaching, not after a quarter

Coaching that works shows up in this series within a week; coaching that didn't shows up as a flat line.

1. Before the coaching session, note the agent's trend over the last 30 days (`?agent_id=...`) and the rubric group that is failing (the per-agent scorecard's group breakdown names it).
2. Run the coaching — a calibration disagreement, a one-on-one, or a Practice Studio scenario. Record the date.
3. Re-read the same agent-scoped trend a week later. A working intervention walks the daily average up off its floor; a flat series on unchanged evaluation volume means the coaching missed the cause, not that the agent ignored it.

The series alone cannot tell you *why* it moved — pair it with the scorecard group breakdown so the recovery is on the rubric group you actually coached, and check `evals` so a two-evaluation week is not mistaken for a signal.

### Compare agents on evidence, not on rank

The leaderboard ranks on composed points from several inputs; the trend's `by_agent` cut compares official QA scoring only, in the same 0–100 units, over the same window. Two read patterns:

* **High volume, low average** is a coaching target: row `evals` shows the score is well-evidenced, and the trend drills it by day to confirm it is a pattern, not a bad Monday.
* **Low volume, any average** is a sampling problem first: an agent with two evaluations in 30 days has no trend — fix the [sampling quota](/guides/qa-sampling-settings) before drawing a conclusion from the number.

Normalize agents against the org line from `totals.avg_score` over the same window; comparing an agent to the team leader flatters no one.

### Tie trend dips to practice sessions

The closed loop downward-dip → diagnosis → rehearsal:

1. **Read the dip.** The org trend or an agent-drilled trend drops over several days. Open the per-agent scorecard from the hub roster to find the failing rubric group ("closing", "identity verification") before naming any scenario — sending the agent to practice first is guessing (see [QA Evaluations](/guides/quality-evaluations)).
2. **Diagnose against the evaluation.** Pull the actual evaluations behind the dip from the ledger and read the lowest-scoring criteria; the worked conversation beats the average.
3. **Rehearse it.** Author a [Practice Studio](/guides/practice-studio-roleplay) scenario for the failing pattern and assign it. The agent's sessions are scored automatically, but they do not re-enter the evaluation ledger — rehearsal volume never cancels a bad official score.
4. **Confirm on the trend.** The next real evaluations on the failing rubric group move the agent's trend; if a week of normal scoring volume shows no recovery, the scenario targeted the wrong skill.

The full weekly loop across recordings, evaluations, leaderboard, and practice is laid out in [The Quality hub: run the supervisor loop](/guides/quality-hub-supervisor-loop); the [leaderboard guide](/guides/quality-leaderboard) covers how the composed ranking relates to this QA-only series.

## 4. Reading pitfalls

* **Do not trend before the evaluators have calibrated.** If reviewers score the same conversation apart, the daily average is reviewer noise, not quality movement; run a calibration session first (see [Build a contact-center QA program](/guides/quality-management-program)).
* **Backfill weeks inflate the right edge.** A reviewer catching up on a backlog lands many evaluations on the same day — the `evals` count on that point tells you it is a volume artifact.
* **An empty window is not a failing window.** `totals.avg_score: null` with `total_evals: 0` means nothing official was scored; the trend has no points, not low points.

## See also

* [QA Evaluations](/guides/quality-evaluations) — the scorecard pipeline the trend is computed over
* [Quality leaderboard](/guides/quality-leaderboard) — the composed ranking that pairs with the QA-only trend
* [The Quality hub: run the supervisor loop](/guides/quality-hub-supervisor-loop) — the weekly loop across all four QM surfaces
* [Train agents with AI roleplay in Practice Studio](/guides/practice-studio-roleplay) — the rehearsal step a trend dip assigns
* [Build a contact-center QA program](/guides/quality-management-program) — calibration and evaluator governance behind the official scores
