Delayed entry means a case becomes observable only after surviving event-free to a later age, so it must not join earlier event denominators.
Delayed entry and left-truncated risk sets
Separate the two clocks
An escalation dashboard may first observe a case when it reaches the specialist queue, even though the case was created earlier. Case age remains measured from creation; observation entry is the age at specialist arrival. A case resolved before arrival never appears in this extract. That is left truncation, not right censoring. The data describe cases conditional on reaching the queue. Cohort entry determines whether that conditional population matches the decision.
Build the eligible risk set
At a resolution time t, include a case only if its observation-entry age is strictly before t and its exit age is at least t. This example uses day-level observations with an explicit (entry, exit] convention. Two cases start observation at age zero. Three enter at ages three or four. At day two, only two are eligible, so one resolution halves the estimated unresolved share. At day five, four cases are eligible and one resolves. At day seven, two remain and one resolves while another is censored.
Keep entry distinct from censoring
A case censored at day six contributed exposure before day six and then left the risk set. A case that entered at day four contributed no exposure to earlier denominators. Treating both as ordinary missing rows changes the meaning of the estimator. Store entry age, exit age, terminal status and the source timestamps; do not derive entry from the first status row unless that row reliably marks the start of observation. Tied outcomes still use the pre-time denominator.
Check the selection mechanism
Delayed-entry adjustment counts only observed survivors. It cannot recover cases that resolved before entering a specialist queue, nor can it describe all created support cases without a representative intake cohort. If high-complexity cases enter sooner, queue-entry timing may also depend on future resolution. Compare entry-age distributions and the operational routing rule across periods. An apparently improved specialist curve can reflect routing changes rather than faster work.
Choose an estimand a reader can act on
For service-wide time to resolution, start from a creation cohort and observe every case from age zero. For time after specialist assignment, reset the clock at assignment and define the population as assigned cases. Use delayed entry only when age since original creation matters and the inclusion process is defensible. The specialist-queue project asks for both views before a comparison is released.
Implementation
from bisect import bisect_left
def delayed_entry_curve(case_records):
if len({case_id for case_id, _, _, _ in case_records}) != len(case_records):
raise ValueError("duplicate case ID")
if any(entry < 0 or exit_day <= entry or event not in (0, 1)
for _, entry, exit_day, event in case_records):
raise ValueError("invalid observation interval")
entries = sorted(entry for _, entry, _, _ in case_records)
exits = sorted(exit_day for _, _, exit_day, _ in case_records)
event_days = sorted({exit_day for _, _, exit_day, event in case_records if event})
unresolved = 1.0
points = []
for day in event_days:
at_risk = bisect_left(entries, day) - bisect_left(exits, day)
events = sum(event for _, _, exit_day, event in case_records if exit_day == day)
if at_risk <= 0 or events > at_risk:
raise ValueError("invalid risk set")
unresolved *= 1 - events / at_risk
points.append((day, at_risk, unresolved))
return points
cases = [("S31", 0, 2, 1), ("S32", 0, 6, 0),
("S33", 3, 5, 1), ("S34", 4, 7, 1), ("S35", 3, 7, 0)]
curve = delayed_entry_curve(cases)
assert [(day, risk) for day, risk, _ in curve] == [(2, 2), (5, 4), (7, 2)]
assert [round(share, 4) for _, _, share in curve] == [0.5, 0.375, 0.1875]Performance and operating cost
Sorting N entry and exit ages costs O(N log N) time and O(N) space. Binary-searching U event times costs O(U log N); the direct event count in this compact code adds O(N × U). Pre-aggregating event counts reduces that scan to O(N log N + U log N).
Common Mistakes
- Do not count a later entrant in an earlier event denominator.
- Do not call cases absent before queue entry right-censored.
- Do not generalize a conditional specialist cohort to all created cases.
