Infused Biologic Administration Capture
An exposure-definition method that ascertains infused (physician-administered, buy-and-bill) biologic use from medical-claim HCPCS J-codes/Q-codes and administration CPT codes on the administration date, then derives exposure intervals from the label dosing schedule rather than from a pharmacy days_supply.
On this page
Some medications are injected or infused by a nurse or doctor in a clinic rather than picked up at a pharmacy. To track when a patient received one of these infusions in insurance records, researchers look for a billing code on the medical claim called a J-code, which appears on the date the infusion was given. Because there is no days_supply field on a medical claim the way there is on a pharmacy fill, analysts instead use the drug's official dosing schedule to estimate how long each infusion should cover the patient until the next one is due. A gap in coverage is declared only when the patient goes too long without returning for their next scheduled infusion.
Infused biologic administration capture
is the operational problem of measuring exposure to physician-administered biologics — infliximab, rituximab, vedolizumab, abatacept IV, tocilizumab IV, ocrelizumab, natalizumab — in real-world data. These drugs are almost never dispensed through the pharmacy benefit. Under the U.S. "buy-and-bill" model they are acquired by the provider, administered in an infusion suite, hospital outpatient department (HOPD), or home, and billed to the medical benefit (Medicare Part B, or the commercial medical claim) as a HCPCS Level II J-code for the drug (e.g., J1745 infliximab, J3262 tocilizumab IV, J0129 abatacept IV, J3380 vedolizumab) plus an administration CPT code (96365/96366 IV infusion, 96413/96415 chemotherapy-style infusion, 96401 SC).
The unit of capture is therefore a medical-claim line on the administration date, not a pharmacy fill — which makes most oral-drug exposure machinery (days_supply stitching, refill-gap rules, drug-era logic keyed on dispensing) the wrong default.
Core conceptual distinction
An infused biologic exposure has no `days_supply`; the duration of one administration's coverage is fixed by the product label's dosing interval, not by a pharmacist-entered field. Infliximab loads at weeks 0, 2, 6 then maintains q8w; rituximab gives 1000 mg ×2 separated by 14 days then re-treats q24w; vedolizumab loads 0/2/6 then q8w. The correct exposure interval is built as `next_expected_date = administration_date + label_interval_days`, with a grace period for real-world timing variability, and a gap is declared when the observed inter-administration interval exceeds roughly 1.5× the label interval — that is the operational definition of discontinuation for an infused agent. This is the conceptual opposite of `persistence-time-to-discontinuation` for oral drugs, which keys off the end of `days_supply`.
The dose actually delivered comes from the units billed on the J-code (each J-code defines a unit of milligrams, e.g., J1745 = 10 mg infliximab), which must be read together with the JW/JZ discarded-drug modifiers and weight-based dosing, because naively summing units across lines double-counts wastage and mis-scales weight-based regimens.
Pros, cons, and trade-offs
- vs a pharmacy-fill / Part D-only exposure definition: Capturing J-codes on the medical claim is the only way to see infused biologics at all — a Part D / NDC-only algorithm has ~0% sensitivity for buy-and-bill agents and will silently drop the entire infused arm, producing a cohort that looks like a self-injectable-only population. Cost: medical claims lack `days_supply`, require label-driven interval logic, and force you to reconcile drug J-codes with separate administration CPT codes. Prefer J-code capture for any analysis of IV/infused biologics; reserve NDC/Part D logic for self-injected or white-bagged product that flows through the pharmacy benefit.
- vs an "ever exposed" J-code flag (presence only): A binary flag is robust and easy, but it discards dose, schedule, persistence, and time-varying exposure. Building intervals from the label schedule supports `time-updated-exposures-cumulative-dose-rwe`, on-treatment risk windows, and per-protocol estimands. Cost: interval construction is sensitive to grace-period and gap-multiplier choices, which must be pre-specified and varied in sensitivity analyses.
- vs treating each infusion as an independent point exposure: Point exposures are simplest but cannot represent continuous on-treatment time between scheduled infusions, so they understate exposed person-time and mishandle the loading phase. Prefer interval construction when the estimand is a rate or a hazard over on-treatment time.
When NOT to use — and when it is actively misleading or dangerous
- Self-injectable-only comparisons (e.g., adalimumab pen vs etanercept SYRINGE) belong to NDC/Part D fill logic; forcing J-code logic there finds nothing and the cohort collapses.
- Pure inpatient bundled administrations. Inpatient biologic doses are usually rolled into a DRG and not separately billed as a J-code, so an outpatient J-code algorithm misses inpatient bridging doses — see `inpatient-bridging-exposure-rwe`. Concluding "treatment gap" across a hospitalization when the drug was given inpatient is a false discontinuation and is actively misleading for persistence and immortal-time analyses.
- Biosimilar-era exposure without code reconciliation. Infliximab biosimilars carry distinct Q-codes (Q5103 infliximab-dyyb, Q5104 infliximab-abda, Q5121 infliximab-axxq). A code list built before a biosimilar launch will register apparent discontinuation at the originator→biosimilar switch date. Decide by design whether to pool originator+biosimilars into one molecule or analyze them as distinct exposures, and document the switch handling.
- Naive unit summation for dose. Summing J-code units without removing JW (discarded) lines and without weight scaling inflates cumulative-dose estimates; for a renal/oncology outcome modified by dose this can manufacture a spurious dose-response.
Data-source operational depth
- Claims (FFS Medicare or commercial): Exposure = medical-claim line with a drug J-code/Q-code on `admin_date`, ideally co-occurring with an administration CPT and a plausible place-of-service (POS 11 office, 19/22 HOPD, 12 home). Require continuous medical enrollment (Part B, not just Part D) across baseline and follow-up so absence of a J-code is a real no-treatment period, not unobserved benefit. Failure modes: (1) MA-only person-time lacks complete FFS encounter/claim submission — "no infusion" can be missingness; restrict to FFS Parts A/B (and D for any white-bagged product) or to commercial plans with complete medical capture. (2) JW/JZ discarded-drug modifiers create extra lines that double-count dose if not netted out. (3) Claims adjudication lag and reversals make the most recent quarters look like false discontinuation — impose a data-maturity buffer. (4) Differential competing risks (death before a scheduled infusion) in elderly claims can masquerade as discontinuation unless death/disenrollment are handled as censoring/competing events.
- EHR (MAR / order data): The administration record (MAR `administered` event), not the order, is the exposure; orders without a matching administration over-capture intended-but-not-given doses. EHR adds weight, BSA, lab severity, and the actual milligrams hung — superior dose fidelity — but external-care leakage (an infusion given at an outside center) is invisible, biasing persistence downward for mobile patients. Link to claims to recover out-of-system infusions.
- Registry: Strong for indication, disease activity, and adjudicated outcomes; typically weak/incomplete for every administration date. Link to claims for the full infusion history and to a death index for censoring.
- Linked claims–EHR–registry: The ideal substrate (claims completeness + EHR dose fidelity + registry severity) but introduces linkage selection and date-discrepancy problems (order date vs MAR date vs claim service date vs claim paid date) that must be reconciled to a single `admin_date` before interval construction.
Worked claims example
Question: 12-month persistence on infliximab (originator + biosimilars) among adults with Crohn's disease initiating IV induction in a commercial + Medicare FFS database.
- Build the drug code list: J1745 (originator) plus Q5103/Q5104/Q5121 (biosimilars), each defined in 10-mg units; pool them into a single `infliximab` molecule.
- Keep medical claim lines with those codes co-occurring with an administration CPT (96365/96413) and drop lines carrying the JW modifier before counting units.
- Require ≥365 days continuous medical enrollment (FFS Parts A/B or commercial medical) before and after the first administration; exclude MA-only person-time so an absent J-code means no infusion, not missing data.
- Index = first qualifying administration date.
- Reconstruct the schedule: loading at weeks 0, 2, 6, then maintenance q8w; for each administration set `next_expected_date = admin_date + (14 or 56 days)` per loading/maintenance phase, add a 28-day grace period, and declare discontinuation when the next administration is absent beyond `1.5 ×` the expected maintenance interval (i.e., > 56 + 28 = 84 days with no infusion).
- Before calling a gap a discontinuation, check for an inpatient bridging dose (hospitalization spanning the expected window with no outpatient J-code) and for an originator↔biosimilar switch on the gap date.
- Censor at disenrollment, death, end of data, and the data-maturity buffer; estimate persistence with a Kaplan–Meier curve and a Fine–Gray model treating death as a competing risk for the time-to-discontinuation outcome.
Decision diagram
flowchart TD
Bene[Patient with the indication<br/>continuous MEDICAL enrollment, FFS not MA-only] --> Codes[Medical claim lines:<br/>drug J-code/Q-code + administration CPT]
Codes --> Net[Drop JW discarded lines<br/>collapse same-day lines -> one administration]
Net --> Adm[Administration on admin_date<br/>dose_mg = units x mg/unit]
Adm --> Sched[Classify loading vs maintenance<br/>by infusion sequence]
Sched --> Intv[Exposure interval = admin_date + label_interval + grace]
Intv --> Gap{Next infusion within<br/>1.5x maintenance interval?}
Gap -- Yes --> Cont[On-treatment continues]
Gap -- No --> Check{Inpatient bridging dose<br/>or biosimilar switch on gap date?}
Check -- Yes --> Cont
Check -- No --> Disc[Discontinuation]gantt title Infliximab infusion exposure intervals for one initiator (claims) dateFormat YYYY-MM-DD axisFormat %b %Y section Loading (wk 0,2,6) Infusion 1 (interval = 14d + grace) :done, l1, 2024-01-01, 14d Infusion 2 (interval = 14d + grace) :done, l2, 2024-01-15, 14d Infusion 3 (interval -> q8w) :done, l3, 2024-02-26, 56d section Maintenance (q8w) Infusion 4 (interval = 56d + grace) :active, m4, 2024-04-22, 56d Infusion 5 (interval = 56d + grace) :active, m5, 2024-06-17, 56d section Gap No infusion > 1.5x q8w -> discontinuation :crit, g1, 2024-08-12, 30d
Worked example
Scenario
A 45-year-old with Crohn's disease starts infliximab on January 6, 2025. The drug label calls for a loading phase of three infusions at weeks 0, 2, and 6, then maintenance infusions every 8 weeks (56 days). An analyst has 360 days of continuous medical-benefit enrollment (January 6 through December 31, 2025) and wants to know how many days this patient was on-treatment and whether they discontinued. There is no pharmacy fill record anywhere because infliximab is infused in a clinic and billed to the medical benefit as J-code J1745.
Dataset
Five medical claim lines from the infusion suite. Each row is one J-code administration. There is no days_supply column because infused drugs have none.
| person_id | admin_date | hcpcs | cpt_admin | units | jw_modifier |
|---|---|---|---|---|---|
| 1001 | 2025-01-06 | J1745 | 96365 | 100 | False |
| 1001 | 2025-01-20 | J1745 | 96365 | 100 | False |
| 1001 | 2025-02-17 | J1745 | 96365 | 100 | False |
| 1001 | 2025-04-14 | J1745 | 96365 | 100 | False |
| 1001 | 2025-06-09 | J1745 | 96365 | 100 | False |
Steps
Result
5 infusions captured via J-code J1745; on-treatment (covered) for 226 of 360 window days (63%); discontinuation declared September 1, 2025 after no sixth infusion arrived within the 84-day grace threshold.
Trade-offs
Runnable example
Build label-schedule exposure intervals for an infused biologic from claims-style inputs. Required inputs (cleaned, de-duplicated, one row per administration line): med : medical drug-administration claims -> person_id, admin_date (datetime), hcpcs (J/Q code str), cpt_admin (administration CPT str), units (int),...
import pandas as pd
import numpy as np
# Pool originator + biosimilars into one molecule (example: IV infliximab).
CODE_LIST = {"J1745", "Q5103", "Q5104", "Q5121"}
UNIT_MG = {"J1745": 10, "Q5103": 10, "Q5104": 10, "Q5121": 10} # mg per billed unit
ADMIN_CPT = {"96365", "96366", "96413", "96415"} # IV infusion administration
LOAD_GAP_DAYS = 14 # loading phase target interval (weeks 0,2,6)
MAINT_GAP_DAYS = 56 # maintenance target interval (q8w)
GRACE_DAYS = 28 # real-world timing slack
GAP_MULTIPLIER = 1.5 # discontinuation when next infusion absent beyond MULTIPLIER x maintenance interval
def build_infusion_intervals(med: pd.DataFrame, enroll: pd.DataFrame) -> pd.DataFrame:
# 1) Keep on-molecule administrations with a real administration CPT; drop JW (discarded) lines for dose/event logic.
m = med[med["hcpcs"].isin(CODE_LIST) & med["cpt_admin"].isin(ADMIN_CPT) & (~med["jw_modifier"])].copy()
# 2) Collapse multiple same-day lines (e.g., two vials) into one administration; sum mg actually delivered.
m["dose_mg"] = m["units"] * m["hcpcs"].map(UNIT_MG)
adm = (m.groupby(["person_id", "admin_date"], as_index=False)
.agg(dose_mg=("dose_mg", "sum")))
# 3) Require continuous medical enrollment (no MA-only gaps) covering each administration date.
e = enroll[~enroll["ma_only"]]
adm = adm.merge(e, on="person_id")
adm = adm[(adm["enroll_start"] <= adm["admin_date"]) & (adm["enroll_end"] >= adm["admin_date"])]
adm = adm.drop(columns=["enroll_start", "enroll_end", "ma_only"]).drop_duplicates(["person_id", "admin_date"])
# 4) Order administrations and classify loading vs maintenance by infusion sequence (first 3 = loading 0/2/6 wk).
adm = adm.sort_values(["person_id", "admin_date"])
adm["seq"] = adm.groupby("person_id").cumcount()
adm["target_gap"] = np.where(adm["seq"] < 3, LOAD_GAP_DAYS, MAINT_GAP_DAYS)
# 5) Exposure interval = [admin_date, admin_date + target_gap + grace]; gap to the next infusion drives discontinuation.
adm["interval_end"] = adm["admin_date"] + pd.to_timedelta(adm["target_gap"] + GRACE_DAYS, unit="D")
adm["next_admin"] = adm.groupby("person_id")["admin_date"].shift(-1)
adm["gap_to_next"] = (adm["next_admin"] - adm["admin_date"]).dt.days
thresh = MAINT_GAP_DAYS * GAP_MULTIPLIER
adm["discontinued"] = adm["next_admin"].isna() | (adm["gap_to_next"] > thresh)
return adm[["person_id", "admin_date", "seq", "dose_mg", "interval_end", "next_admin", "discontinued"]]Label-schedule exposure intervals with data.table. Inputs mirror the Python version: med : person_id, admin_date (Date), hcpcs, cpt_admin, units (int), jw_modifier (logical), place_of_service enroll : person_id, enroll_start, enroll_end, ma_only (logical) # MA-only spans lack complete FFS claims
library(data.table)
code_list <- c("J1745", "Q5103", "Q5104", "Q5121") # originator + biosimilars pooled
unit_mg <- c(J1745 = 10, Q5103 = 10, Q5104 = 10, Q5121 = 10) # mg per billed unit
admin_cpt <- c("96365", "96366", "96413", "96415")
load_gap <- 14L; maint_gap <- 56L; grace <- 28L; gap_mult <- 1.5
build_infusion_intervals <- function(med, enroll) {
setDT(med); setDT(enroll)
# 1) On-molecule administrations with a real administration CPT; drop JW (discarded) lines.
m <- med[hcpcs %chin% code_list & cpt_admin %chin% admin_cpt & !jw_modifier]
m[, dose_mg := units * unit_mg[hcpcs]]
# 2) Collapse same-day lines into one administration; sum mg delivered.
adm <- m[, .(dose_mg = sum(dose_mg)), by = .(person_id, admin_date)]
# 3) Require continuous medical enrollment (no MA-only) covering each administration date.
e <- enroll[ma_only == FALSE]
adm <- e[adm, on = "person_id", allow.cartesian = TRUE
][enroll_start <= admin_date & enroll_end >= admin_date]
adm <- unique(adm[, .(person_id, admin_date, dose_mg)])
# 4) Sequence administrations; first 3 = loading (0/2/6 wk), rest = maintenance (q8w).
setorder(adm, person_id, admin_date)
adm[, seq := seq_len(.N) - 1L, by = person_id]
adm[, target_gap := fifelse(seq < 3L, load_gap, maint_gap)]
# 5) Exposure interval and discontinuation flag from the gap to the next infusion.
adm[, interval_end := admin_date + target_gap + grace]
adm[, next_admin := shift(admin_date, type = "lead"), by = person_id]
adm[, gap_to_next := as.integer(next_admin - admin_date)]
adm[, discontinued := is.na(next_admin) | gap_to_next > maint_gap * gap_mult]
adm[, .(person_id, admin_date, seq, dose_mg, interval_end, next_admin, discontinued)]
}Label-schedule exposure-interval construction in SAS (PROC SQL + DATA step). Required input datasets (post data-management, one row per administration line): work.med : person_id, admin_date, hcpcs, cpt_admin, units, jw_modifier (0/1), place_of_service work.enroll : person_id, enroll_start, enroll_end, ma_only (0/1)...
%let load_gap = 14; /* loading interval (wk 0,2,6) */
%let maint_gap = 56; /* maintenance interval (q8w) */
%let grace = 28; /* real-world timing slack */
%let gap_mult = 1.5; /* discontinuation threshold multiplier */
/* 1) On-molecule administrations with a real administration CPT; drop JW (discarded) lines; map units -> mg. */
proc sql;
create table adm0 as
select person_id,
admin_date,
units * 10 as dose_mg /* each unit = 10 mg for J1745/Q5103/Q5104/Q5121 */
from work.med
where hcpcs in ('J1745','Q5103','Q5104','Q5121') /* originator + biosimilars pooled */
and cpt_admin in ('96365','96366','96413','96415') /* IV administration */
and jw_modifier = 0;
quit;
/* 2) Collapse multiple same-day lines into one administration; sum mg delivered. */
proc sql;
create table adm1 as
select person_id, admin_date, sum(dose_mg) as dose_mg
from adm0
group by person_id, admin_date;
quit;
/* 3) Keep only administrations covered by continuous medical (non-MA-only) enrollment. */
proc sql;
create table adm2 as
select distinct a.person_id, a.admin_date, a.dose_mg
from adm1 a
where exists (
select 1 from work.enroll e
where e.person_id = a.person_id
and e.ma_only = 0
and e.enroll_start <= a.admin_date
and e.enroll_end >= a.admin_date
);
quit;
/* 4-5) Sequence infusions, classify loading vs maintenance, then look ahead to the next infusion (read backward so the
previous row holds the "next" administration) and flag discontinuation when that gap exceeds 1.5x q8w. */
proc sort data=adm2; by person_id admin_date; run;
data seqd;
set adm2; by person_id admin_date;
retain seq;
if first.person_id then seq = 0; else seq + 1;
if seq < 3 then target_gap = &load_gap; else target_gap = &maint_gap;
interval_end = admin_date + target_gap + &grace;
format interval_end admin_date date9.;
run;
proc sort data=seqd; by person_id descending admin_date; run;
data work.intervals;
set seqd; by person_id descending admin_date;
next_admin = lag(admin_date); /* lag over descending order = the next (later) infusion */
if first.person_id then next_admin = .; /* last infusion in the person has no successor */
gap_to_next = next_admin - admin_date;
discontinued = (next_admin = .) or (gap_to_next > &maint_gap * &gap_mult);
format next_admin date9.;
keep person_id admin_date seq dose_mg interval_end next_admin gap_to_next discontinued;
run;Citations
- [1]Curtis JR, Baddley JW, Yang S, et al. Derivation and preliminary validation of an administrative claims-based algorithm for the effectiveness of medications for rheumatoid arthritis. Arthritis Research & Therapy. 2011;13(5):R155.
- [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.