First response time (median)¶
How long does a customer wait before a human acknowledges the ticket?
Current value (service case data): 0.9 h - ✓ On target. Open the case.
| Field | Value |
|---|---|
| Definition | Median elapsed hours between ticket creation and the first agent response, over tickets that have received a response. |
| Formula | median(first_response_at - created_at) [hours] |
| Unit | hours |
| Granularity | Ticket; reported weekly by priority and queue. |
| Direction | Lower is better ↓ |
| Target and thresholds | ≤ 2.0 h (amber ≤ 4.0 h) |
| Parameters | – |
| Owner | Service Desk Lead |
| Source | Ticketing system (synthetic generator) |
| Input | table tickets, columns first_response_hours |
| Recommended chart | line - Trend over weeks against the target line; the question is 'are we drifting?'. |
| Related KPIs | Resolution time (median), SLA compliance |
How to read it¶
Half of the tickets got a first answer faster than this value. Read it per priority: a good fleet median can hide P1 tickets waiting overnight.
Common misreadings¶
- Using the mean - a handful of tickets answered after a weekend dominates it.
- Counting auto-replies as a first response makes the KPI look perfect and meaningless.
- Ignoring unanswered tickets - they are censored, not fast; track them as backlog.
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: 2.00 h. Responded tickets: 0.5, 1, 3, 6 h (ticket 5 has no response yet). Median of 4 values = (1 + 3) / 2 = 2.0 h.
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("first_response_time")
def first_response_time(df: pd.DataFrame) -> float:
return median_or_nan(df["first_response_hours"])
Reference SQL (tested on SQLite):
WITH x AS (
SELECT first_response_hours AS v,
ROW_NUMBER() OVER (ORDER BY first_response_hours) AS rn,
COUNT(*) OVER () AS n
FROM tickets WHERE first_response_hours IS NOT NULL)
SELECT AVG(v) FROM x WHERE rn IN ((n + 1) / 2, (n + 2) / 2)