Skip to content

Resolution time (median)

How long does it take to solve a customer's problem end to end?

Current value (service case data): 11.1 h - ✓ On target. Open the case.

Field Value
Definition Median elapsed hours from creation to resolution over resolved tickets.
Formula median(resolved_at - created_at) for status = resolved [hours]
Unit hours
Granularity Ticket; reported weekly by priority and queue.
Direction Lower is better ↓
Target and thresholds ≤ 24.0 h (amber ≤ 36.0 h)
Parameters –
Owner Service Desk Manager
Source Ticketing system (synthetic generator)
Input table tickets, columns status, resolution_hours
Recommended chart histogram - The distribution (with median and P90 marked) shows the long tail that a single number hides.
Related KPIs First response time (median), SLA compliance, Aged backlog (> 7 days), Reopen rate

How to read it

The typical end-to-end wait. Always pair it with the 90th percentile: the customers who complain live in the tail, not at the median.

Common misreadings

  • The mean is pulled up by a few very old tickets; the median alone hides them - show both or the P90.
  • Only resolved tickets are counted, so a growing backlog of hard tickets makes this KPI improve (survivorship).
  • Calendar hours vs business hours must be stated; mixing them across teams breaks comparisons.

Worked example (from the test suite)

ticket_id status first_response_hours resolution_hours sla_target_hours contacts reopened age_days
1 resolved 1.0 10.0 24.0 1 False –
2 resolved 3.0 30.0 24.0 2 False –
3 resolved 0.5 4.0 8.0 1 True –
4 resolved 6.0 50.0 72.0 1 False –
5 open – – 24.0 1 False 9.0

Expected result: 20.00 h. Resolved: 4, 10, 30, 50 h. Median = (10 + 30) / 2 = 20.0 h. (The mean would be 23.5 h, pulled up by the 50 h ticket.)

This example is asserted in tests/test_formulas.py against the Python function and in tests/test_sql.py against the SQL below.

Implementations

Python function (the single implementation used by the site):

@kpi("resolution_time")
def resolution_time(df: pd.DataFrame) -> float:
    return median_or_nan(_resolved(df)["resolution_hours"])

Reference SQL (tested on SQLite):

WITH x AS (
  SELECT resolution_hours AS v,
         ROW_NUMBER() OVER (ORDER BY resolution_hours) AS rn,
         COUNT(*) OVER () AS n
  FROM tickets WHERE status = 'resolved')
SELECT AVG(v) FROM x WHERE rn IN ((n + 1) / 2, (n + 2) / 2)