Rebuild first-event histories and compare resolution by deadline without misclassifying withdrawal as ordinary follow-up loss.
Project: audit support resolution when customers can withdraw
Write the decision target
A support team reports that a new triage policy resolved more tickets by day twelve. Define the eligible ticket cohort, policy assignment, clock origin, resolution event, customer withdrawal, and last reliable follow-up. A ticket with no recorded end after its export date is censored; a ticket explicitly withdrawn is a competing event. Do not count a reopening as a first resolution in the same episode. The incidence lesson describes the required first-event frame.
Reconcile every transition
Link ticket IDs to resolution, withdrawal, and export logs. Reject impossible ordering such as withdrawal before entry, and settle same-time event priority before analysis. Compare follow-up completeness under old and new policy; a system migration that drops late resolution scans can imitate lower performance. Make counts of resolved, withdrawn, still observed, and censored available by assignment group. The missingness ledger guards against silently deleting unknown endings.
Estimate the operational probability
Use a competing-risk cumulative incidence for resolution by the declared day, and show withdrawal incidence and event-free remainder at the same horizon. Include uncertainty that respects the assignment unit, especially if support teams rather than tickets received the policy. A cause-specific resolution hazard can be supplementary, but it does not replace the deadline probability. The rate-versus-risk lesson explains why they can move in different directions.
Publish a cautious release packet
The packet contains cohort SQL version, event map, timing convention, censoring summary, incidence curves, horizon contrasts, uncertainty, and the assignment unit. The gate below stops a headline if event order or policy capture is unresolved. Passing it permits statistical review rather than guaranteeing a causal policy effect. If withdrawal patterns changed under triage, disclose that change next to resolution so the result is not interpreted as a simple speed improvement.
Implementation
def competing_event_gate(audit):
if audit["unresolved_event_order"]:
return "hold:event-order"
if audit["unmatched_policy_assignments"]:
return "hold:assignment-ledger"
if not audit["censoring_reconciled"]:
return "hold:follow-up"
if not audit["withdrawal_reported"]:
return "hold:competing-outcome"
return "review:deadline-incidence"
audit = {"unresolved_event_order": 2, "unmatched_policy_assignments": 0,
"censoring_reconciled": True, "withdrawal_reported": True}
assert competing_event_gate(audit) == "hold:event-order"
assert competing_event_gate({**audit, "unresolved_event_order": 0}) == "review:deadline-incidence"
Performance and operating cost
The release gate is O(1). Sorting n ticket events is O(n log n), and joining logs can dominate runtime. The largest cost is resolving ambiguous transitions; a fast curve made from the wrong event order is less useful than a delayed but auditable result.
Common Mistakes
- Censoring explicit withdrawal.
- Counting a reopened ticket as a first-event resolution.
- Ignoring changes in follow-up after a logging migration.
- Claiming a hazard contrast alone proves a deadline benefit.
Read next
- Competing events: calculate the probability of each first outcome
- Cause-specific hazard versus cumulative incidence: know which question changed
- Survival risk sets: keep censored cases until they leave observation
- Missing outcomes: count absence before choosing an estimator
- Experiment design: assign the right unit and guard against interference
