Acute Event Deduplication Window
A pre-specified gap (or "blackout") rule that collapses multiple codes or encounters for the same acute condition occurring within a defined time window into a single counted event episode, so that one clinical event is not scored as several.
On this page
When a patient has a heart attack, their insurance records often show multiple separate charges for the same event — the hospital stay, a transfer to a second hospital, and follow-up office visits that still list the same diagnosis code. An acute event deduplication window is a pre-specified rule that says: any claim that arrives within a set number of days of the first claim (say, 30 days for a heart attack) belongs to the same event, not a new one. Without this rule, one heart attack can get counted as four or five events, inflating incidence rates and corrupting cost analyses. The window collapses those fragments into a single counted episode; only a claim that arrives more than 30 days after the first discharge is treated as a true new event.
An acute event deduplication window is the operational rule that answers a single, decisive question during outcome ascertainment: given several codes or encounters carrying the same acute diagnosis, when do they represent ONE event and when do they represent TWO? Administrative and EHR data fragment a single clinical event across many records — an acute myocardial infarction (AMI) generates an inpatient stay, often a transfer to a second facility, physician (carrier) claims, follow-up office visits still carrying the I21.x code, and rehospitalization within days.
Counted naively, that one infarction becomes four or five "events," inflating incidence, double-counting in cost/utilization, and corrupting time-to-event analyses. The deduplication window collapses records whose dates fall within a pre-specified gap of an anchor record into the same event episode, and only resets to a new episode once a record falls beyond that gap.
Core conceptual distinction — and what this is NOT
The window has two parameters that must be pre-specified separately: (1) the anchor/index rule (which record opens an episode — e.g., first inpatient claim with the diagnosis in the primary position) and (2) the gap rule (the blackout/clean interval that must elapse before a subsequent qualifying record can open a new episode — e.g., 30 days from the index discharge for AMI; 14 days for COPD or asthma exacerbations and for sepsis). This concept is narrowly about within-condition episode grouping.
It is distinct from three neighbors it is constantly confused with. A pre-index washout / clean look-back period (`washout-clean-lookback-period-rwe`) clears the baseline so the first observed event is plausibly incident rather than prevalent; the deduplication window operates during follow-up to merge fragments and to separate genuinely distinct recurrences. A restart / new-episode rule for treatment (`restart-rechallenge-new-episode-rwe`) builds exposure episodes from fills; this builds outcome episodes from diagnoses/encounters.
Recurrent-event analysis
(`recurrent-events-analysis-rwe`) is the downstream model (Andersen-Gill, PWP, frailty) that consumes the episode stream the window produces — get the window wrong and every recurrent-event estimate is wrong.
Estimand link
The window is not cosmetic data cleaning; it defines the counting unit and therefore the estimand. A short or zero gap counts each fragment, so the implied estimand is "number of records," not "number of events." A long gap can merge two true recurrences into one, biasing recurrence rates downward and shrinking the per-protocol event count. Pre-specify the gap to match the clinical natural history of the condition and the question (first-event time-to-event vs. recurrence rate vs. annualized count), and pre-register it in the protocol/SAP before touching data.
Pros, cons, and trade-offs
- vs. no deduplication (count every qualifying record): A window removes the dominant upward bias from administrative fragmentation (transfers, carrier + facility claims for the same stay, resolved-condition follow-up coding). Cost: it introduces a tuning parameter that can mask true early recurrences; the gap length is a researcher degree of freedom that must be pre-specified and sensitivity-tested. Prefer a window for essentially every acute-event count, rate, or cost analysis in fragmented data.
- vs. a "one event per patient ever" (first-event-only) rule: First-event-only is the correct, simplest choice when the estimand is time to first event in a survival model and recurrences are not of interest; it needs no gap parameter. Cost: it discards all recurrence information and is wrong for incidence-rate, utilization, or cost questions where repeat events matter. Prefer the window when recurrences carry information or cost; prefer first-event-only for a clean first-event hazard.
- vs. encounter/claim-line based counting with manual chart adjudication: Adjudication is the gold standard for what constitutes a distinct event but does not scale and is usually unavailable in large claims. The window is the scalable proxy; its validity should be anchored to an adjudicated or validated reference (PPV/sensitivity of the resulting event count, not just the diagnosis code). Prefer the window for scale, but validate it against a chart-reviewed or registry-adjudicated subset (`claims-outcome-algorithm-ppv-sensitivity-rwe`).
When NOT to use — and when it is actively misleading
- Chronic, continuously coded conditions (diabetes, hypertension, CKD stage). There is no discrete "event" to deduplicate; an arbitrary window manufactures pseudo-events from routine maintenance coding. Use prevalence/algorithm definitions instead (`diagnosis-phenotype-algorithm-1ip-2op-time-window-rwe`).
- First-event time-to-event questions where recurrences are irrelevant. A window adds a needless parameter; use a first-event rule plus a pre-index washout.
- When the gap is tuned to the result. Choosing the gap after seeing how it moves the effect estimate is p-hacking by another name; it is most dangerous when the gap length differs by exposure arm or is data-driven. The gap must be pre-specified, applied identically to all arms, and varied only in pre-planned sensitivity analyses.
- When a single window is applied across heterogeneous conditions. A 30-day AMI rule misapplied to a 14-day exacerbation condition (or vice versa) systematically miscounts; gaps are condition-specific by clinical natural history.
Data-source operational depth
- Claims (FFS): The classic failure is treating each claim line as an event. One AMI hospitalization yields an inpatient facility claim, a separate carrier (Part B) professional claim, possibly an interhospital transfer to a second facility, and downstream office visits still carrying I21.x weeks later. Anchor on the inpatient facility claim with the diagnosis in the primary/principal position, set the gap from the index discharge date (not admit date), and treat transfers — overlapping or contiguous inpatient stays — as the same episode before applying the gap. Preserve raw admit/discharge dates alongside the derived `episode_id`. Claim-reversal and adjudication lag can make an event appear, vanish, and reappear; freeze the data cut and use service dates, not paid/process dates.
- Medicare Advantage vs FFS, and plan switching: MA encounter data are notoriously incomplete and underreport inpatient events, so episode counts are not comparable between MA and FFS person-time (`medicare-ffs-ma-commercial-claims-differences-rwe`). A patient who switches plans mid-window can have the back half of an episode unobserved, splitting one event into two or truncating recurrence follow-up. Require continuous, FFS- observable enrollment across each episode window and censor at plan switches; do not pool MA-only person-time with FFS for event rates.
- EHR: Capture is encounter-driven and site-bounded. A patient readmitted to an outside hospital for a true recurrence is missed, so a fixed gap can wrongly merge two events (the second is invisible) or, conversely, resolved- condition problem-list carry-forward can manufacture spurious follow-up "events." Prefer linkage to claims or a regional HIE to capture out-of-system events, define observation windows explicitly, and treat out-of-network care as informative missingness.
- Registry / linked data: Registries often carry adjudicated event dates that should override code-derived anchors when available; linkage to claims supplies the fragmentation (transfers, professional claims) that the registry omits, and to a death index so a fatal event is not mistaken for "no recurrence." Reconcile registry, claims, and vital-records dates before assigning episode boundaries, and check transportability of the chosen gap to the analysis population.
- Differential competing risks and immortal-time traps: In elderly or sicker arms, death competes with recurrence; a long gap that spans a death interval can suppress observed recurrences differentially by arm — handle death as a competing risk (`competing-risks-cause-specific-fine-gray-rwe`), not as censoring. In procedure-anchored studies, counting only patients who survived to a post-discharge code creates immortal time (`immortal-time-bias-handling`); align time zero to the index event, not to the deduplicated follow-up code.
Worked claims example (AMI, 30-day rule)
Question: incidence and 1-year recurrence of AMI in a Medicare FFS cohort.
- Eligibility / washout: ≥365 days of continuous FFS Parts A/B enrollment with no inpatient I21.x before the first qualifying claim, so the first episode is incident, not prevalent (the deduplication window is separate from this clean period).
- Anchor rule: an episode opens on the admit date of an inpatient facility claim with ICD-10 I21.x in the principal position.
- Transfer handling: any inpatient claim whose admit date is on or within 1 day of a prior episode's discharge date is folded into that episode (an interhospital transfer is one event).
- Gap rule: the next principal-position I21.x inpatient claim with an admit date ≥30 days after the index episode's discharge opens a new episode; anything within 30 days of discharge — including carrier claims and post-MI office visits — is attributed to the index episode and does not count.
- Output: one row per `episode_id` with index admit/discharge dates, arm, and an `is_incident` flag; recurrence rate = (episodes − first episodes) per person-year of FFS-observable follow-up, censoring at disenrollment, death, and data cut.
- Sensitivity / validation: rerun at 14- and 60-day gaps, report how incidence and recurrence move, and benchmark the event count's PPV against a chart-reviewed or registry-adjudicated subset rather than trusting the code alone.
Interpreting the output
. Applying the 30-day deduplication window to Margaret's (person 2001) four raw AMI claims collapses them into two distinct episodes. Episode 1 spans the January 3 index admit through the January 12 discharge; all claims with admit dates within 30 days of that discharge are attributed to Episode 1 and do not count as new events. Episode 2 opens on March 2, 49 days after the Episode 1 discharge — outside the 30-day gap — and ends March 9.
Formal interpretation: the 30-day window is a modeling assumption, not a clinical truth. Any two claims whose admit dates fall within 30 days of the preceding discharge are treated as one continuous episode, regardless of whether the second admission represented a genuine readmission or a billing artifact. The window choice is consequential: a 14-day window would potentially split what the 30-day rule treats as one episode; a 60-day window would collapse events the 30-day rule treats as distinct.
Incidence and recurrence estimates are therefore partly a function of the window specification, and sensitivity analyses at 14 and 60 days are mandatory before presenting results to a regulator or HTA body.
Practical interpretation: always report the episode count alongside the raw claim count and the window length. Reviewers who see "218 AMI events" without knowing the deduplication rule cannot evaluate whether recurrence was over- or under-counted. The window choice should be pre-specified and motivated by the condition's natural history — for AMI, 30 days is the standard clinically meaningful readmission threshold, aligning the deduplication rule with the readmission outcome.
Decision diagram
timeline title One patient, four AMI claims collapsed into two episodes (30-day rule) Jan 03 - IP admit I21.x : Episode 1 opens (index) Jan 09 - transfer IP I21.x : Same event (<=1d after discharge) - merged into Episode 1 Jan 20 - office visit I21.x : Inside 30d blackout - attributed to Episode 1, NOT counted Mar 02 - IP admit I21.x : >=30d after Ep1 discharge - Episode 2 opens (recurrence)
flowchart TD
Rec[Next qualifying claim<br/>principal-position dx] --> Open{An episode already open<br/>for this person?}
Open -- No --> New1[Open episode 1<br/>set index + discharge]
Open -- Yes --> Tx{Admit date within<br/>transfer window of<br/>running discharge?}
Tx -- Yes --> Merge[Merge: same event<br/>extend discharge, do not count anew]
Tx -- No --> Gap{Admit date >= gap_days<br/>after running discharge?}
Gap -- No --> Same[Inside blackout<br/>attribute to current episode]
Gap -- Yes --> New2[Close current episode<br/>open new episode = recurrence]Worked example
Scenario
Margaret is a 72-year-old Medicare patient who has a heart attack on January 3, 2023. She is admitted to Community Hospital that day, transferred to University Medical Center on January 5 (her records show a second inpatient claim), and then sees her cardiologist in an office visit on January 20, at which the cardiologist still lists the heart-attack diagnosis code. On March 2, she is admitted again with a new heart attack. A researcher applying a 30-day deduplication window wants to count how many distinct heart-attack episodes Margaret had in 2023.
Dataset
Raw qualifying claims for Margaret (person 2001) — every claim where the principal diagnosis is I21.x (acute MI), sorted by date.
| person_id | claim_type | admit_date | discharge_date | dx_principal |
|---|---|---|---|---|
| 2001 | IP | 2023-01-03 | 2023-01-05 | I21.9 |
| 2001 | IP | 2023-01-05 | 2023-01-12 | I21.9 |
| 2001 | Office | 2023-01-20 | 2023-01-20 | I21.9 |
| 2001 | IP | 2023-03-02 | 2023-03-09 | I21.9 |
Steps
Result
4 raw claims for patient 2001 collapse to 2 distinct episodes under the 30-day deduplication window: Episode 1 (index admit Jan 3, discharge Jan 12, spanning 3 raw claims) and Episode 2 (index admit Mar 2, a true recurrence 49 days after Episode 1 discharge).
Trade-offs
Runnable example
Acute-event episode construction with a deduplication window from claims-style inputs. Required input (already cleaned, one row per qualifying claim that carries the target diagnosis in the principal position): claims : person_id, claim_id, claim_type ('IP'/'OP'), admit_date (datetime), discharge_date (datetime),...
import pandas as pd
GAP_DAYS = 30 # condition-specific blackout (AMI=30; exacerbations/sepsis commonly 14)
TRANSFER_DAYS = 1 # contiguous/overlapping IP stays within this many days are the same event (transfer)
def build_event_episodes(claims: pd.DataFrame,
gap_days: int = GAP_DAYS,
transfer_days: int = TRANSFER_DAYS) -> pd.DataFrame:
c = claims.sort_values(["person_id", "admit_date", "discharge_date"]).copy()
out = []
for pid, g in c.groupby("person_id", sort=False):
episode_id = 0
ep_admit = ep_discharge = None
n_records = 0
first_seen = True
for _, row in g.iterrows():
if ep_admit is None:
# open the first episode for this person
episode_id, n_records = 1, 1
ep_admit, ep_discharge = row["admit_date"], row["discharge_date"]
continue
days_after_discharge = (row["admit_date"] - ep_discharge).days
if days_after_discharge <= transfer_days:
# transfer / fragment of the SAME event -> extend the episode, do not count anew
ep_discharge = max(ep_discharge, row["discharge_date"])
n_records += 1
elif days_after_discharge < gap_days:
# inside the blackout window -> attributed to current episode, not a new event
n_records += 1
else:
# beyond the gap -> close current episode, open a new one
out.append((pid, episode_id, ep_admit, ep_discharge, n_records, first_seen))
first_seen = False
episode_id += 1
ep_admit, ep_discharge, n_records = row["admit_date"], row["discharge_date"], 1
if ep_admit is not None:
out.append((pid, episode_id, ep_admit, ep_discharge, n_records, first_seen))
episodes = pd.DataFrame(out, columns=["person_id", "episode_id", "index_admit",
"index_discharge", "n_records", "is_incident"])
return episodesAcute-event episode construction with a deduplication window using data.table. Input mirrors the Python version (one row per qualifying principal-position claim): claims : person_id, claim_id, claim_type ('IP'/'OP'), admit_date (Date), discharge_date (Date), dx_primary Returns one row per episode.
library(data.table)
GAP_DAYS <- 30L
TRANSFER_DAYS <- 1L
build_event_episodes <- function(claims, gap_days = GAP_DAYS, transfer_days = TRANSFER_DAYS) {
setDT(claims)
setorder(claims, person_id, admit_date, discharge_date)
assign_episodes <- function(admit, discharge) {
n <- length(admit)
epi <- integer(n); ep_discharge <- discharge[1L]; epi[1L] <- 1L
for (i in seq_len(n)[-1L]) {
gap <- as.integer(admit[i] - ep_discharge)
if (gap <= transfer_days) { # transfer / same-event fragment
epi[i] <- epi[i - 1L]
ep_discharge <- max(ep_discharge, discharge[i])
} else if (gap < gap_days) { # inside blackout -> current episode
epi[i] <- epi[i - 1L]
} else { # beyond gap -> new episode
epi[i] <- epi[i - 1L] + 1L
ep_discharge <- discharge[i]
}
}
epi
}
claims[, episode_id := assign_episodes(admit_date, discharge_date), by = person_id]
episodes <- claims[, .(index_admit = min(admit_date),
index_discharge = max(discharge_date),
n_records = .N),
by = .(person_id, episode_id)]
episodes[, is_incident := episode_id == min(episode_id), by = person_id]
episodes[]
}Acute-event episode construction with a deduplication window in SAS (sorted DATA step with RETAIN/LAG-style carry of the running episode discharge). Required input (one row per qualifying principal-position claim): work.claims : person_id, claim_id, claim_type, admit_date, discharge_date, dx_primary Produces...
%let gap = 30; /* condition-specific blackout window (days) */
%let transfer = 1; /* contiguous IP stays within this gap = same event */
proc sort data=work.claims; by person_id admit_date discharge_date; run;
data work.episodes(keep=person_id episode_id index_admit index_discharge n_records is_incident);
set work.claims;
by person_id;
retain episode_id ep_discharge index_admit index_discharge n_records;
format index_admit index_discharge date9.;
if first.person_id then do; /* open first episode for the person */
episode_id = 1; ep_discharge = discharge_date;
index_admit = admit_date; index_discharge = discharge_date; n_records = 1;
end;
else do;
gap_days = admit_date - ep_discharge;
if gap_days <= &transfer then do; /* transfer / same-event fragment */
ep_discharge = max(ep_discharge, discharge_date);
index_discharge = ep_discharge; n_records + 1;
end;
else if gap_days < &gap then do; /* inside blackout -> current episode */
n_records + 1;
end;
else do; /* beyond gap -> emit + open new */
is_incident = (episode_id = 1); /* flag the episode being emitted */
output;
episode_id + 1; ep_discharge = discharge_date;
index_admit = admit_date; index_discharge = discharge_date; n_records = 1;
end;
end;
if last.person_id then do; /* emit the still-open final episode */
is_incident = (episode_id = 1);
output;
end;
run;Citations
- [1]Kiyota Y, Schneeweiss S, Glynn RJ, Cannuscio CC, Avorn J, Solomon DH. Accuracy of Medicare claims-based diagnosis of acute myocardial infarction: estimating positive predictive value on the basis of review of hospital records. American Heart Journal. 2004;148(1):99-104.
- [2]Benchimol EI, Smeeth L, Guttmann A, et al. The REporting of studies Conducted using Observational Routinely-collected health Data (RECORD) statement. PLoS Medicine. 2015;12(10):e1001885.
- [3]Bossuyt PM, Reitsma JB, Bruns DE, et al. STARD 2015: an updated list of essential items for reporting diagnostic accuracy studies. BMJ. 2015;351:h5527.