# Overview KPIs and per-tenant usage meters

> How the admin overview computes its rolling-24h call tiles, where the roster and session counts come from, and what the billing-cycle meters currently show.

The admin overview is the landing screen for a voice tenant. Its top strip
holds five KPI tiles and its lower-left panel holds four usage meters. The
tiles read from three different sources — the agent roster, the live session
list, and a ClickHouse rollup of `events_raw` merged with the tenant's
recording objects — and each source has its own window and its own idea of
what "a call" is. This page defines each number so that a tile and a report
that disagree can be reconciled.

| Tile / meter | Source | Window |
| ------------ | ------ | ------ |
| Agents in roster | `adminAgents.list` (`total`) | current |
| Active sessions | `adminSessions.list` (rows where `state != offline`) | current |
| Calls · 24h | `cdr.overviewKpis` → `events_raw` | rolling 24h |
| P50 answer time | `cdr.overviewKpis` → `events_raw` | rolling 24h |
| Recording success | `cdr.overviewKpis` → `events_raw` + recording objects | rolling 24h |
| Billing-cycle meters | roster count; otherwise static placeholders | see below |

## The 24-hour window and what counts as a call

`cdr.overviewKpis` is an org-scoped procedure. It resolves your org id to a
ClickHouse `tenant_id` and runs a single aggregate over `events_raw`:

```sql
SELECT
    uniqExact(call_id)                                  AS calls_24h,
    uniqExactIf(call_id, ans_reason = 0)                AS answered_24h,
    quantileExactIf(0.5)(ring_time_sec, ans_reason = 0) AS ring_p50_sec
  FROM events_raw
  WHERE tenant_id = '<tenant>'
    AND seg_start >= now() - INTERVAL 24 HOUR
```

Three properties follow from that query, and they explain most apparent
discrepancies:

- **The unit is a distinct `call_id`, not a row.** `events_raw` holds one row
  per segment, so a call that produced several segments still counts once.
- **The window is anchored on `seg_start`.** A call is in the window if its
  segments *started* in the last 24 hours. A long call that started 26 hours
  ago and is still up does not appear, and a call is not re-counted when it
  ends.
- **The window is rolling and evaluated server-side at query time** —
  `now() - INTERVAL 24 HOUR` — not aligned to midnight, to your billing
  cycle, or to your browser's timezone. Two loads a minute apart cover two
  slightly different windows.

The "Calls · 24h" tile shows `calls_24h`. Its subtitle shows
`answered_24h`, so the tile carries both the attempt count and the answered
count.

## Answered calls and ring time

Answered is defined by exactly one condition: `ans_reason = 0`. That is the
answer-reason discriminator on the segment row, and only segments carrying
`ans_reason = 0` are counted into `answered_24h`. Every other
`ans_reason` value — whatever the underlying disposition was — is an
unanswered call for the purposes of this screen. The tiles do not look at a
separate status column, a Q.850 cause, or call duration to make this
decision.

The tile labelled **P50 answer time** is the median of `ring_time_sec`, taken
over the same answered set:

- `ring_time_sec` is the segment's measured ring time — the interval the call
  spent ringing before it was answered — recorded per segment on
  `events_raw`.
- The quantile is computed with `quantileExactIf(0.5)(…, ans_reason = 0)`, so
  unanswered attempts contribute nothing. Ring-timeout expiries and
  no-answers do not drag the median upward, which is why this number is
  usually much lower than a naive "time spent ringing" average over all
  attempts.
- It is an exact median over the window's answered segments, not an
  approximate or decayed one.

The screen formats the value rather than showing raw seconds: under a minute
it renders seconds with one decimal (`8.4s`); at a minute or more it renders
minutes and whole seconds (`1m 12s`).

## Recording success: objects versus answered calls

The recording tile is the one place where the overview joins two unrelated
systems. `cdr.overviewKpis` fetches the tenant's recording objects alongside
the ClickHouse aggregate and counts the objects that satisfy both of:

- `source == 'tenant'` — objects that are not attributed to the tenant's own
  recording output are excluded from the count.
- `lastModified` within the last 24 hours, measured from the request time.

That count is `recordings_24h`. The percentage is then:

```
recording_success_pct = min(100, round(recordings_24h / answered_24h * 100))
```

**The denominator is answered calls, not all calls.** This is deliberate. A
call that was never answered produces no media, so it can never produce a
recording object; including unanswered attempts in the denominator would make
the metric track your answer rate instead of your recording pipeline. Dividing
by `answered_24h` asks the only question worth asking here — *of the calls
that actually had audio, how many left a recording behind?* — so a drop in
this tile points at recording configuration or storage, not at dialling
outcomes.

Two consequences of the two-system join are worth knowing before you treat
the percentage as exact:

- **The numerator and denominator are windowed on different clocks.** Objects
  are selected by storage `lastModified`; calls are selected by `seg_start`.
  A recording uploaded just inside the window for a call that started just
  outside it counts in the numerator only. Over a quiet 24 hours, or right
  after a backlog of uploads flushes, the ratio can exceed 100 — which is why
  the result is clamped with `min(100, …)`.
- **The object count is not filtered by call id.** Any tenant-sourced
  recording object touched in the window counts, so re-writing or
  re-uploading objects inflates the numerator.

The tile renders its "ok" (green) state when the percentage is at or above
95; below that it renders in the neutral state. That threshold is a display
choice in this screen only — it is not a service commitment and nothing
enforces it.

## Agents in roster and active sessions

These two tiles bypass ClickHouse entirely and read the admin routers
directly, so they describe *now*, not the last 24 hours.

- **Agents in roster** is the `total` from `adminAgents.list`. The screen
  requests a single-row page purely to read the count, so the tile reflects
  every agent in the roster regardless of pagination.
- **Active sessions** counts rows from `adminSessions.list` whose `state` is
  anything other than `offline`. Its subtitle counts the subset whose `state`
  is exactly `ready`, so "12 active · 9 ready" means three non-offline
  sessions are in some other state.

The same session query feeds the **Active sessions** panel below the strip,
which lists the first few non-offline sessions with a dot coloured by whether
the state is `ready`. When there are none, the panel shows an empty-state
line rather than a zero row.

## Refresh behaviour

Each source refreshes on its own cadence, so the tiles are not all as of the
same instant:

| Source | Behaviour |
| ------ | --------- |
| `adminSessions.list` | refetches every 10 seconds; paused while the tab is backgrounded |
| `cdr.overviewKpis` | refetches every 60 seconds |
| `adminAgents.list` | served from cache for 60 seconds before refetching |

Because session polling stops in the background, a tab left open on another
window can show a stale session count until it regains focus.

## Seat, minute, and storage meters

The **This billing cycle** panel is labelled "resets monthly" and shows four
meters: agents seated, LLM minutes, inbound minutes, and recording storage.

Only one of them is live today. The **agents seated** meter's *used* value is
the same roster total that feeds the KPI tile. The remaining three meters, and
every meter's *limit*, are hardcoded illustrative values in the screen — they
are not read from your plan, your contract, or a metering service. Do not
reconcile an invoice against this panel, and do not read a limit shown here as
a quota that will be enforced.

Wiring these to real numbers requires per-tenant metering on the BFF, which
this screen does not yet consume. When that lands, each meter will have a
documented source the same way the call tiles do above; until then, treat the
panel as layout. The **Plan** link in the panel header routes to the modules
screen, which is where plan and entitlement state actually lives.

## When a tile reads a dash

A dash (`—`) means "no value to show", and the reason differs per tile:

| Tile | Reads `—` when |
| ---- | -------------- |
| Calls · 24h | the `cdr.overviewKpis` query has not resolved yet, or failed |
| P50 answer time | the query has not resolved, **or** `answered_24h` is 0 |
| Recording success | the query has not resolved, **or** `recording_success_pct` is `null` |

`recording_success_pct` is returned as `null` in exactly one case: when
`answered_24h` is 0. With no answered calls there is no meaningful
denominator, so the procedure declines to divide rather than reporting 0%.

Two related states are easy to misread:

- **`0` calls is not a dash.** Once the query resolves, a genuinely quiet 24
  hours shows `0` in the calls tile (in the neutral rather than the ok state),
  and the P50 and recording tiles fall back to `—` because there were no
  answered calls.
- **A recording-storage failure reads `0%`, not `—`.** The object listing is
  wrapped so that a failure yields an empty list instead of an error. If
  storage is unreachable while calls were answered, the numerator is 0 and the
  tile shows `0%`. A recording tile at 0% with a healthy answered count
  therefore means *either* no recordings were written *or* the object listing
  failed — check storage before concluding the recorder is broken.

Because the agents and sessions tiles format a numeric count directly, they
render `0` rather than a dash while their queries are still loading or if no
org is selected.

## Related

- [Telemetry](/platform/telemetry) — the metric families, CDR schema, and
  trace fields the TeleQuick gateway emits
- [Telephony Metrics](/glossary/metrics) — industry definitions for ASR, ACD,
  PDD and the other rates these tiles approximate
- [Authentication](/concepts/authentication) — the API key and org scoping
  that gate these procedures
