Reopen rate¶
How often is a "resolved" ticket not really resolved?
Current value (service case data): 4.1% - ✓ On target. Open the case.
| Field | Value |
|---|---|
| Definition | Resolved tickets that were reopened at least once, divided by resolved tickets. |
| Formula | count(reopened) / count(resolved) |
| Unit | % |
| Granularity | Ticket; reported monthly by queue and agent group. |
| Direction | Lower is better ↓ |
| Target and thresholds | ≤ 5.0% (amber ≤ 8.0%) |
| Parameters | – |
| Owner | Quality Lead (Service) |
| Source | Ticketing system (synthetic generator) |
| Input | table tickets, columns status, reopened |
| Recommended chart | line - Trend next to resolution time to expose a speed-vs-quality trade-off. |
| Related KPIs | First contact resolution (FCR), Resolution time (median) |
How to read it¶
The quality counterweight to every speed KPI. If resolution time improves and reopen rate rises, speed was bought with quality.
Common misreadings¶
- Reading it without a time window - reopenings arrive days later, so the last weeks always look better than they are.
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: 25.00%. Reopened among resolved: only #3. 1 / 4 = 0.25.
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("reopen_rate")
def reopen_rate(df: pd.DataFrame) -> float:
r = _resolved(df)
return safe_ratio(r["reopened"].astype(bool).sum(), len(r))
Reference SQL (tested on SQLite):
SELECT CAST(SUM(CASE WHEN reopened = 1 THEN 1 ELSE 0 END) AS REAL) / COUNT(*)
FROM tickets WHERE status = 'resolved'