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)