Diversion Alert Thresholds & Suspicious-Order Monitoring
Suspicious-order monitoring and diversion detection under 21 CFR 1301.74(b): per-entity statistical baselines, z-score and velocity alert thresholds
Diversion is rarely a single dramatic event; it is a slow drift that hides inside normal-looking traffic. A prescriber who orders 15% more oxycodone every week, a dispensing cabinet whose overrides creep up on the night shift, a supplier order that is only slightly larger than the last one — none of these trips a fixed limit, and all of them are exactly what 21 CFR § 1301.74(b) requires a registrant to detect and report. This reference specifies how to turn that regulatory obligation into a running statistical control: per-entity baselines, deviation thresholds that adapt to each drug and location, velocity limits, and an escalation path that converts an anomalous number into a documented hold and review. It operates within Controlled Substance Storage & Handling Compliance, consuming the same append-only ledger that the storage architecture already maintains and feeding its output back as compliance evidence.
The central distinction this page draws — and the one most implementations blur — is between suspicious-order monitoring at the procurement boundary and diversion detection at the dispensing boundary. They share statistical machinery but answer to different clauses, watch different entities, and escalate to different people. Conflating them produces a monitor that is loud about wholesaler orders and blind to the pharmacy technician quietly removing Schedule II stock one waste event at a time.
Regulatory Context & Compliance Boundaries
The governing text is compact but demanding. 21 CFR § 1301.74(b) requires a registrant to “design and operate a system to disclose to the registrant suspicious orders of controlled substances,” and defines suspicious orders as those of unusual size, those deviating substantially from a normal pattern, and those of unusual frequency. Those three adjectives — size, pattern, frequency — are not prose; they are the three statistical tests a monitoring system must implement. Size maps to a magnitude threshold, pattern maps to a deviation-from-baseline score, and frequency maps to a velocity or inter-arrival check.
That clause governs the procurement side. On the dispensing side, the completeness and accuracy requirements of 21 CFR § 1304.21 and the perpetual-inventory obligations of 21 CFR § 1304.11 mean every removal, waste, and administration event is already a ledger row — which is precisely the event stream a diversion detector consumes. When a threshold is breached and the loss is confirmed as theft or significant loss, the reporting duty of 21 CFR § 1301.76(b) engages and a filing flows through to DEA Form 106 Theft & Loss Reporting. The monitor does not decide that a loss occurred; it decides that a number is anomalous enough to demand a human decision, and it preserves the evidence for whichever way that decision goes.
Two boundaries must be encoded structurally. First, the monitor reads from — and writes alerts to — the compliance ledger, but it must respect the same scope wall that the rest of the platform observes: it operates on quantities, entity identifiers, timestamps, and NDCs, never on patient-identifying detail. What counts as an in-scope event, and what must be excluded as protected health information, is fixed by Audit Boundary Definition & Scope, and a diversion detector that reaches across that boundary to correlate on patient identity trades a HIPAA exposure for a marginal signal it does not need. Second, an alert is a compliance artifact in its own right: the evidence that the system did disclose an unusual order is itself the proof of a working § 1301.74(b) program, so alerts are appended to the immutable ledger, not written to a mutable queue that can be cleared without trace.
| Clause | Requirement | Monitoring control |
|---|---|---|
21 CFR § 1301.74(b) |
Detect orders of unusual size, pattern deviation, or unusual frequency | Magnitude, z-score/MAD, and velocity evaluators over a per-entity baseline |
21 CFR § 1304.11 |
Perpetual inventory kept current | Dispensing event stream is the detector’s input feed |
21 CFR § 1304.21 |
Complete, accurate record of each disposition | Every evaluated event and every alert is a hash-chained ledger row |
21 CFR § 1301.76(b) |
Report theft or significant loss on discovery | Confirmed-diversion holds route to the Form 106 generator |
45 CFR § 164.312(b) |
Audit controls and integrity | Alert records carry SHA-256 digest + prior-hash linkage |
Suspicious-Order Monitoring vs. Diversion Detection
The two disciplines are worth separating precisely because their false-positive costs differ. A suspicious-order monitor watches the inbound flow — purchase orders sent to distributors — and its entities are (registrant, supplier, drug family). A blocked or held order delays a legitimate resupply, so its threshold is tuned to catch genuine outliers against a slowly moving procurement baseline. A diversion detector watches the outbound and internal flow — dispenses, cabinet removals, overrides, waste — and its entities are finer-grained: (prescriber, drug), (dispensing location, drug), (operator, drug). Its baseline moves faster and its thresholds are tighter, because the cost of a missed signal is a controlled substance leaving the building unaccounted for.
The table below makes the split concrete. Both feed the same evaluator described later; only the entity keys, the baseline window, and the escalation target change.
| Dimension | Suspicious-order monitoring | Diversion detection |
|---|---|---|
Governing focus of § 1301.74(b) |
Orders to suppliers | Internal disposition pattern |
| Boundary | Procurement / receiving | Dispensing / waste / cabinet |
| Baseline entity | (registrant, supplier, drug) | (prescriber, drug), (location, drug), (operator, drug) |
| Primary signal | Order magnitude + frequency | Per-event and rolling-rate deviation |
| Typical action | Hold order, require justification | Hold entity, forensic review |
| Escalates to | Purchasing compliance officer | Diversion / security officer |
| Downstream filing | Distributor’s own § 1301.74 duty |
§ 1301.76 Form 106 if loss confirmed |
Statistical Thresholding Model
A fixed limit — “alert if a prescriber orders more than 500 units” — fails in both directions: it is deaf to a small-volume drug being drained and it screams at a high-volume clinic behaving normally. Thresholds must be relative to a baseline computed per entity. Three complementary tests cover the three adjectives in the statute.
Deviation from pattern (unusual size relative to normal). For each (entity, drug) pair, maintain a robust location and scale estimate over a trailing window. The classic choice is a rolling mean and standard deviation, scoring each new observation as a z-score z = (x − μ) / σ. Because a few large diversion events can inflate the very standard deviation meant to catch them, the more defensible estimator is the median and median absolute deviation (MAD), scoring with a modified z-score 0.6745 · (x − median) / MAD. MAD has a breakdown point of 50%, so a run of anomalies cannot quietly widen the band that is supposed to flag them. The full derivation, the 0.6745 consistency constant, and the small-sample and seasonality traps live in the companion recipe, Computing Diversion Z-Score Thresholds.
Unusual frequency (velocity). Size alone misses the diverter who takes small amounts often. A velocity limit tracks events per rolling interval — dispenses per hour, overrides per shift, orders per week — and flags when the rate exceeds its own baseline, independent of any single event’s magnitude. Ten normal-sized removals in an hour where the baseline is two is a frequency anomaly even though no single event is large.
Absolute magnitude (unusual size, floor). A pure z-score is scale-free, which means a single dispense of 900 units against a baseline of 3±1 produces an enormous score but so would 6 units against 3±0.3. A magnitude floor — “regardless of z-score, any single Schedule II removal above N units is at least reviewed” — prevents a noisy small-count baseline from downgrading a genuinely large event, and prevents a diverter from suppressing scores by first inflating the baseline’s spread.
The evaluator combines the three with an explicit precedence: a magnitude-floor breach is always at least a soft alert; a deviation or velocity breach escalates by severity band. Crucially, thresholds are configured per schedule — Schedule II tolerances are tighter than Schedule V — resolved from the product identifier through DEA Schedule II-V Classification Mapping so the band a drug is judged against follows its legal control level, not a hand-typed constant.
Deterministic Evaluation Workflow
The monitor runs as a deterministic function over an ordered event stream. Each event carries an entity key, a normalized NDC, a signed quantity, and a UTC timestamp; the evaluator resolves the entity’s baseline, scores the event, applies precedence, and either commits a clean record or raises a graded alert. The workflow is fixed so that a DEA inspector can replay any decision from the ledger.
State transitions per event:
INGESTED→SCORED— resolve the (entity, drug) baseline; if none exists yet, mark the eventWARMINGand update the baseline without scoring against an unstable estimate.SCORED→WITHIN_BASELINE— no magnitude, deviation, or velocity band breached; the event is committed to the ledger and the baseline is updated.SCORED→ALERTED— one or more bands breached; a graded alert is raised (SOFT,HARD,CRITICAL), the affected entity is placed on hold, and the alert record is appended to the ledger.ALERTED→UNDER_REVIEW→CLEAREDorCONFIRMED_DIVERSION— a compliance officer adjudicates; a cleared alert releases the hold but preserves the evidence, a confirmed one routes to Form 106.
Production Python: Streaming Threshold Evaluator
The evaluator below scores an event stream against per-entity robust baselines, applies the three-test precedence, chains each decision into a SHA-256 audit record, and emits PHI-free structured logs. Baselines use median/MAD so a run of diversion events cannot silently widen the band. The full statistical treatment (hysteresis, seasonality, warm-up) is deferred to the companion recipe; here the focus is the deterministic evaluate-and-audit loop.
from __future__ import annotations
import hashlib
import json
import logging
import statistics
from collections import defaultdict, deque
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Deque, Optional
logger = logging.getLogger("cs.diversion.monitor")
# Consistency constant making MAD a std-dev estimator for normal data.
_MAD_SCALE = 0.6745
class Severity(str, Enum):
NONE = "NONE"
SOFT = "SOFT"
HARD = "HARD"
CRITICAL = "CRITICAL"
@dataclass(frozen=True)
class Event:
"""One controlled-substance disposition or order event. No PHI."""
entity_id: str # prescriber / location / operator / (registrant,supplier)
ndc: str # canonical 11-digit NDC
schedule: str # "II".."V", resolved upstream
quantity: float # signed magnitude of the event
ts: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
@dataclass(frozen=True)
class Thresholds:
"""Per-schedule alerting bands. Tighter for Schedule II."""
z_hard: float # modified z-score raising a HARD alert
z_critical: float # modified z-score raising a CRITICAL alert
magnitude_floor: float # any single event >= this is at least SOFT
velocity_max: int # max events per rolling window before frequency alert
# Schedule II is watched most tightly; V is loosest. Satisfies the
# per-schedule intent of 21 CFR § 1301.74(b) (unusual size AND frequency).
SCHEDULE_BANDS: dict[str, Thresholds] = {
"II": Thresholds(z_hard=3.5, z_critical=5.0, magnitude_floor=120, velocity_max=8),
"III": Thresholds(z_hard=4.0, z_critical=6.0, magnitude_floor=250, velocity_max=12),
"IV": Thresholds(z_hard=4.5, z_critical=6.5, magnitude_floor=400, velocity_max=20),
"V": Thresholds(z_hard=5.0, z_critical=7.0, magnitude_floor=600, velocity_max=30),
}
_MIN_BASELINE = 12 # observations required before scoring (else WARMING)
_WINDOW = 60 # trailing observations retained per entity
class BaselineStore:
"""Rolling per-(entity, drug) magnitude and velocity baseline."""
def __init__(self) -> None:
self._mags: dict[tuple[str, str], Deque[float]] = defaultdict(
lambda: deque(maxlen=_WINDOW)
)
self._events: dict[tuple[str, str], Deque[str]] = defaultdict(
lambda: deque(maxlen=_WINDOW)
)
def modified_z(self, key: tuple[str, str], x: float) -> Optional[float]:
mags = self._mags[key]
if len(mags) < _MIN_BASELINE:
return None # not enough history: do not score
med = statistics.median(mags)
mad = statistics.median([abs(m - med) for m in mags]) or 1e-9
return _MAD_SCALE * (x - med) / mad
def velocity(self, key: tuple[str, str]) -> int:
return len(self._events[key])
def update(self, key: tuple[str, str], x: float, ts: str) -> None:
self._mags[key].append(x)
self._events[key].append(ts)
@dataclass(frozen=True)
class AlertRecord:
entity_id: str
ndc: str
schedule: str
quantity: float
z_score: Optional[float]
velocity: int
severity: Severity
reason: str
ts: str
previous_hash: str
def record_hash(self) -> str:
payload = json.dumps(
{k: v for k, v in self.__dict__.items() if k != "record_hash"},
sort_keys=True, default=str,
)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def evaluate(event: Event, store: BaselineStore, previous_hash: str) -> AlertRecord:
"""Score one event and return an audit record (severity NONE == clean).
Precedence (21 CFR § 1301.74(b)): a magnitude-floor or velocity breach is
always at least SOFT; deviation escalates by band. The baseline is updated
AFTER scoring so an event is never judged against itself.
"""
key = (event.entity_id, event.ndc)
bands = SCHEDULE_BANDS.get(event.schedule, SCHEDULE_BANDS["II"])
z = store.modified_z(key, abs(event.quantity))
vel = store.velocity(key) + 1 # counting this event
severity, reason = Severity.NONE, "within_baseline"
if z is None:
severity, reason = Severity.NONE, "warming_baseline"
else:
if abs(z) >= bands.z_critical:
severity, reason = Severity.CRITICAL, "deviation_critical"
elif abs(z) >= bands.z_hard:
severity, reason = Severity.HARD, "deviation_hard"
if vel > bands.velocity_max and severity in (Severity.NONE, Severity.SOFT):
severity, reason = Severity.HARD, "velocity_exceeded"
if abs(event.quantity) >= bands.magnitude_floor and severity == Severity.NONE:
severity, reason = Severity.SOFT, "magnitude_floor"
record = AlertRecord(
entity_id=event.entity_id, ndc=event.ndc, schedule=event.schedule,
quantity=event.quantity, z_score=None if z is None else round(z, 3),
velocity=vel, severity=severity, reason=reason, ts=event.ts,
previous_hash=previous_hash,
)
store.update(key, abs(event.quantity), event.ts)
# Structured, PHI-free audit line. Entity is a role/location id, never a patient.
logger.info(
"diversion_evaluated",
extra={"entity": event.entity_id, "ndc": event.ndc,
"schedule": event.schedule, "severity": severity.value,
"reason": reason, "z": record.z_score, "velocity": vel,
"record_hash": record.record_hash()},
)
return record
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
store = BaselineStore()
prev = "0" * 64
# Warm a routine Schedule II baseline around ~5 units, then inject a spike.
stream = [Event("presc-1042", "00093-0058-01", "II", q)
for q in [5, 4, 6, 5, 5, 4, 6, 5, 5, 6, 4, 5, 5]] + \
[Event("presc-1042", "00093-0058-01", "II", 140)] # anomaly
for ev in stream:
rec = evaluate(ev, store, prev)
prev = rec.record_hash()
The AlertRecord is frozen and hash-chained exactly like the ledger entries the storage architecture already produces, so an alert is not a side note — it is an auditable row proving the § 1301.74(b) system ran and what it concluded. The baseline update happens after scoring, which is the small detail that prevents an event from diluting the very statistics used to judge it.
Compliance Mapping & Audit Boundaries
The monitor produces two kinds of evidence an inspector will ask for: proof that a detection system exists and operates, and the specific decisions it reached. Both are satisfied by writing every evaluation — clean or alerting — to the append-only ledger rather than to a mutable alert table.
| Obligation | System requirement | Implementation artifact |
|---|---|---|
21 CFR § 1301.74(b) unusual size |
Magnitude relative to per-entity baseline | Modified z-score + magnitude_floor band |
21 CFR § 1301.74(b) unusual frequency |
Rate anomaly independent of size | velocity count vs. velocity_max |
21 CFR § 1301.74(b) pattern deviation |
Robust deviation resistant to poisoning | Median/MAD estimator, breakdown point 50% |
21 CFR § 1304.21 completeness |
Every disposition recorded | Each Event evaluation is a ledger row |
21 CFR § 1301.76(b) reporting |
Escalate confirmed loss | CONFIRMED_DIVERSION → Form 106 route |
45 CFR § 164.312(b) integrity |
Tamper-evident alert trail | record_hash() chained on previous_hash |
Access to the alert ledger is restricted to diversion and compliance roles under role-based access control, and the boundary of what the monitor may read is inherited wholesale from Audit Boundary Definition & Scope: entity identifiers are operational role or location keys, never patient identifiers, so the detector never becomes an unauthorized PHI aggregation. A cleared alert is never deleted — clearing appends an adjudication record referencing the original, preserving the complete decision history.
Error Handling & Offline Resilience
A monitor that goes silent during an outage is worse than no monitor, because it creates an evidentiary gap exactly when it matters. Failure modes are handled deterministically:
- Cold start / sparse entity. An entity with fewer than
_MIN_BASELINEobservations is markedWARMING: its events are still committed and still accumulate the baseline, but they are not scored against an unstable estimate. Scoring a spike against three prior points invents both false positives and false negatives. - Baseline poisoning. A patient diverter may attempt to raise their own baseline before diverting in bulk. Median/MAD blunts this — half the window must be corrupted before the estimate moves — and the
magnitude_floorprovides an absolute backstop that no baseline manipulation can lower. - Offline windows. When the event feed is buffered during a POS or cabinet disconnect, events replay in timestamp order on reconnect using the deferred-validation pattern from Fallback Routing for Offline Sync. The evaluator is a pure function of (event, baseline state), so replayed evaluation is deterministic and idempotency-keyed against double-scoring.
- Alert-storm suppression. A genuine large-scale diversion can trip hundreds of events; the graded severity plus per-entity hold collapses the storm into one held entity under review rather than a flood of tickets, while still logging every underlying evaluation.
Downstream Integration
An alert is an upstream event for several subsystems. A CONFIRMED_DIVERSION outcome is the trigger for DEA Form 106 Theft & Loss Reporting, which renders its figures from the immutable ledger snapshot so the reported loss cannot drift between discovery and filing. On the procurement side, a held suspicious order feeds back into the receiving gate maintained by DEA Form 222 Digital Validation, so an anomalous order cannot be quietly received while it is under review. The variance signals that the EDI 852 & 846 Parsing Pipelines already compute at ingestion are a second input stream the same evaluator can consume, letting supplier-reported activity and internal dispensing be scored against one baseline model.
Frequently Asked Questions
What exactly makes an order “suspicious” under 21 CFR 1301.74(b)?
The rule names three independent triggers: unusual size, a substantial deviation from a normal ordering pattern, and unusual frequency. A compliant monitor implements all three — a magnitude test, a deviation-from-baseline score, and a velocity check — rather than a single fixed quantity limit, because any one test alone misses two of the three ways the regulation says diversion presents.
Why use median and MAD instead of mean and standard deviation?
Because diversion events are exactly the outliers that inflate a standard deviation. A handful of large removals widen the mean-based band until later removals look normal, so the estimator defeats itself. The median and median absolute deviation have a 50% breakdown point: nearly half the window must be corrupted before the baseline shifts, which is the resistance a poisoning-aware detector needs.
How does a threshold breach become a DEA filing?
A breach raises a graded alert and places the entity on hold, but the system does not itself declare a loss. A compliance officer adjudicates; only a CONFIRMED_DIVERSION outcome routes to the Form 106 generator, which reports theft or significant loss on discovery per 21 CFR § 1301.76(b). A cleared alert releases the hold while retaining the evidence that the system detected and reviewed the anomaly.
Does the monitor need access to patient records?
No, and it must not have it. The evaluator operates on entity identifiers (prescriber, location, operator, supplier), NDCs, quantities, and timestamps. Reaching across the scope wall to correlate on patient identity adds negligible detection signal and creates a HIPAA exposure, so the boundary defined for the audit ledger is enforced on the monitor’s inputs as well.
Related
- Up: Controlled Substance Storage & Handling Compliance — the parent architecture whose ledger this monitor reads and writes.
- Computing Diversion Z-Score Thresholds — the robust-baseline recipe with hysteresis and small-sample handling.
- DEA Form 106 Theft & Loss Reporting — where a confirmed-diversion hold escalates for filing.
- Audit Boundary Definition & Scope — the scope wall that fixes what the monitor may read.
- DEA Schedule II-V Classification Mapping — per-schedule band resolution from the product identifier.