Handling Out-of-Range Flags Without Manual Intervention

Problem Statement

A static reference-range check flags every value outside its stored bounds, but most of those flags are not clinically actionable: a chronically elevated creatinine on a dialysis patient, a neonatal bilirubin that is normal for age, or a spuriously high potassium from a hemolyzed draw all trip the same limit. When every flag lands in a human review queue, throughput collapses and genuinely critical results wait behind noise. This page builds a deterministic pipeline that auto-resolves the resolvable flags — releasing chronically abnormal-but-stable results, holding results a calibration problem makes untrustworthy, and escalating true criticals immediately — while writing an attributable audit record for every automated decision so the laboratory director can defend each release.

Prerequisites

  • Runtime: Python 3.11+ (for X | None unions and datetime.UTC).
  • Libraries: pydantic>=2.6 for typed flag, verdict, and audit models; pytest for the verification suite.
  • Instrument firmware / middleware: analyzers must export a daily quality-control summary — control means, SDs, and the evaluated Westgard state per analyte — plus reagent-lot metadata, alongside each chemistry result.
  • Regulatory baseline: the laboratory has a documented auto-verification (auto-release) procedure on file, a CLIA §493.1253 results-review requirement, and an append-only audit store as built in implementing HIPAA-compliant audit trails in LIMS.
  • Upstream contract: results arrive already parsed and normalized to canonical analyte codes and UCUM units by the HL7 v2 Segment Mapping layer, with reference intervals and delta limits owned and version-controlled by the parent Threshold Tuning & Calibration stage. This code resolves flags; it never invents thresholds.

This build sits downstream of the Clinical Result Validation & Rule Engine Architecture runtime path — it consumes the flags raised by the stateless Reference Range Check Implementation and the verdicts produced by Delta Validation & Trend Analysis, then decides disposition.

Out-of-range flag auto-resolution decision flow An out-of-range flag enters a resolver and descends a spine of four ordered gates. A critical flag short-circuits to ALERT_IMMEDIATE (page now). The demographic gate re-resolves the reference interval by sex, age and matrix; an in-range, demographic-specific value clears outright to AUTO_OVERRIDE (release). The delta gate routes an acute shift to MANUAL_QUEUE (hold). The QC and Westgard gate acts as a circuit breaker: an out-of-control control state forces HOLD_RECAL and blocks release, while an unreachable QC service fails closed to STRICT_MODE with no auto-override. The confidence gate then compares the combined score against the 0.85 auto-release floor, sending a passing score to AUTO_OVERRIDE and a failing score to MANUAL_QUEUE. Any gate that does not resolve passes the flag down to the next. OutOfRangeFlag value past a stored bound is_critical? hard critical-limit check Demographic range re-resolve by sex · age · matrix Delta suppression consume upstream delta_status QC / Westgard control state · circuit breaker Confidence ≥ 0.85 combined score vs floor not critical ↓ still flagged ↓ not cleared ↓ in control ↓ ALERT_IMMEDIATE page now · bypass the queue is_critical ✓ AUTO_OVERRIDE in range & demographic-specific cleared → release MANUAL_QUEUE ACUTE_SHIFT / no range → hold acute shift not in control HOLD_RECAL OUT_OF_CONTROL · block release STRICT_MODE UNREACHABLE · no auto-override AUTO_OVERRIDE · score ≥ floor MANUAL_QUEUE · score < floor

Step-by-Step Implementation

Step 1: Model the flag and every possible disposition as typed data

Auto-resolution decisions are audited, so they cannot be loose dictionaries. Model the inbound flag and the terminal Disposition as frozen pydantic types. Every automated decision must reduce to exactly one disposition, so encode them as an enum rather than free strings.

python
from __future__ import annotations

from decimal import Decimal
from enum import Enum

from pydantic import BaseModel, Field


class Disposition(str, Enum):
    AUTO_OVERRIDE = "auto_override"   # release without human review
    MANUAL_QUEUE = "manual_queue"     # hold for a validation technologist
    ALERT_IMMEDIATE = "alert_immediate"  # page now; bypass the queue
    HOLD_RECAL = "hold_recalibration"    # QC out of control; do not release


class PatientMeta(BaseModel):
    model_config = {"frozen": True}

    sex: str | None = None
    age_group: str | None = None      # e.g. "neonate", "adult", "geriatric"
    matrix: str = "serum"


class OutOfRangeFlag(BaseModel):
    model_config = {"frozen": True}

    result_id: str
    patient_id: str
    analyte: str                      # canonical, e.g. LOINC-resolved
    value: Decimal
    unit: str
    patient_meta: PatientMeta
    is_critical: bool = False         # value past a hard critical limit
    reagent_lot: str

Step 2: Re-resolve the reference range against patient demographics

A generic adult interval produces the largest share of false flags. Before treating a flag as real, re-resolve it against a demographic-keyed interval; many “out of range” results are in range for the patient’s age and sex. Resolve deterministically and fall back explicitly — a missing key is never silently treated as in range.

python
class Interval(BaseModel):
    model_config = {"frozen": True}
    low: Decimal
    high: Decimal


class RangeResolution(BaseModel):
    in_range: bool
    used_default: bool
    error: str | None = None


def resolve_range(
    flag: OutOfRangeFlag, intervals: dict[str, Interval]
) -> RangeResolution:
    m = flag.patient_meta
    key = f"{flag.analyte}_{m.sex}_{m.age_group}_{m.matrix}"
    bounds = intervals.get(key)
    used_default = False
    if bounds is None:
        used_default = True
        bounds = intervals.get(f"{flag.analyte}_default")
        if bounds is None:
            return RangeResolution(
                in_range=False, used_default=True,
                error=f"NO_RANGE_CONFIGURED:{flag.analyte}",
            )
    return RangeResolution(
        in_range=bounds.low <= flag.value <= bounds.high,
        used_default=used_default,
    )

A used_default=True resolution is a confidence penalty, not a pass: it means the demographic-specific interval was missing, so the decision cannot be fully trusted for auto-release.

Step 3: Fold in the delta verdict to suppress stable chronic abnormality

A value that is out of range but unchanged from the patient’s own recent baseline is usually chronic, not acute. Consume the delta_status the Delta Validation & Trend Analysis stage already computed — this code does not recompute deltas — and use STABLE as a signal that a persistently abnormal result is safe to release.

python
class DeltaStatus(str, Enum):
    STABLE = "stable"
    TRENDING = "trending"
    ACUTE_SHIFT = "acute_shift"
    NO_BASELINE = "no_baseline"


def delta_confidence(status: DeltaStatus) -> Decimal:
    """Map an upstream delta verdict to a release-confidence contribution."""
    return {
        DeltaStatus.STABLE: Decimal("0.40"),      # chronic, unchanged
        DeltaStatus.TRENDING: Decimal("0.10"),
        DeltaStatus.ACUTE_SHIFT: Decimal("0.00"),  # never auto-release
        DeltaStatus.NO_BASELINE: Decimal("0.15"),
    }[status]

Step 4: Gate on the QC / calibration state with a circuit breaker

An out-of-range flag is only meaningful if the analyzer that produced it was in control. If the daily QC series has tripped a rejection rule — 1_3s, 2_2s, R_4s, 4_1s, or 10_x — no result from that analyte may auto-release, because the calibration governing the bound is itself suspect. And if the QC service is unreachable, fail closed to STRICT_MODE rather than assuming control.

QC state Meaning Auto-resolution effect
IN_CONTROL No Westgard rejection Auto-release permitted
WARNING (1_2s) Single control > 2 SD Permitted; confidence penalty applied
OUT_OF_CONTROL Rejection rule tripped Force HOLD_RECAL; block release
UNREACHABLE QC service timeout Force STRICT_MODE; no auto-override
python
class QCState(str, Enum):
    IN_CONTROL = "in_control"
    WARNING = "warning"
    OUT_OF_CONTROL = "out_of_control"
    UNREACHABLE = "unreachable"


def qc_gate(state: QCState) -> tuple[bool, Decimal]:
    """Return (auto_release_allowed, confidence_contribution)."""
    if state in (QCState.OUT_OF_CONTROL, QCState.UNREACHABLE):
        return False, Decimal("0.00")
    penalty = Decimal("0.10") if state is QCState.WARNING else Decimal("0.00")
    return True, Decimal("0.30") - penalty

The reagent-lot metadata on each flag matters here: a lot change shifts the analytical baseline, and the population feedback that retunes bounds against observed false-positive rates is owned by the Threshold Tuning & Calibration stage — this gate only enforces the current control state.

Step 5: Score confidence and route to a single disposition

Combine the signals into one confidence score, then map it to a disposition with an explicit auto-release floor. Criticals short-circuit to paging before any scoring; an out-of-control analyzer short-circuits to a recalibration hold. Everything else releases only above the confidence floor.

python
AUTO_RELEASE_FLOOR = Decimal("0.85")


def route_flag(
    flag: OutOfRangeFlag,
    rng: RangeResolution,
    delta: DeltaStatus,
    qc: QCState,
) -> tuple[Disposition, Decimal]:
    if flag.is_critical:
        return Disposition.ALERT_IMMEDIATE, Decimal("1.00")
    if qc in (QCState.OUT_OF_CONTROL,):
        return Disposition.HOLD_RECAL, Decimal("0.00")

    # Demographic re-resolution can clear the flag outright.
    if rng.error is None and rng.in_range and not rng.used_default:
        return Disposition.AUTO_OVERRIDE, Decimal("0.95")

    allowed, qc_conf = qc_gate(qc)
    base = Decimal("0.15") if not rng.used_default else Decimal("0.00")
    confidence = base + delta_confidence(delta) + qc_conf
    if delta is DeltaStatus.ACUTE_SHIFT or rng.error is not None:
        return Disposition.MANUAL_QUEUE, confidence
    if allowed and confidence >= AUTO_RELEASE_FLOOR:
        return Disposition.AUTO_OVERRIDE, confidence
    return Disposition.MANUAL_QUEUE, confidence

The escalation of an ALERT_IMMEDIATE disposition is owned downstream by Critical Value Alert Routing, which consumes this verdict and dispatches the page; this stage only decides that a page is required.

Step 6: Emit an immutable audit record for every automated decision

An auto-released abnormal result must be as defensible as a human-verified one. Write an append-only audit record — hashing the patient identifier, capturing the full signal set and the resolving rule version — inside the same transactional boundary as the release, so no decision can exist without its provenance.

python
import hashlib
import uuid
from datetime import UTC, datetime


class ResolutionAudit(BaseModel):
    model_config = {"frozen": True}

    audit_id: str
    result_id: str
    patient_hash: str
    analyte: str
    disposition: Disposition
    confidence: Decimal
    delta_status: DeltaStatus
    qc_state: QCState
    reagent_lot: str
    rule_version: str
    decided_at: datetime


async def resolve_and_audit(
    flag: OutOfRangeFlag,
    delta: DeltaStatus,
    qc: QCState,
    intervals: dict[str, Interval],
    audit_sink,                 # append-only store; awaited write
    rule_version: str = "2026.07",
) -> ResolutionAudit:
    rng = resolve_range(flag, intervals)
    disposition, confidence = route_flag(flag, rng, delta, qc)
    record = ResolutionAudit(
        audit_id=str(uuid.uuid4()),
        result_id=flag.result_id,
        patient_hash=hashlib.sha256(flag.patient_id.encode()).hexdigest(),
        analyte=flag.analyte,
        disposition=disposition,
        confidence=confidence,
        delta_status=delta,
        qc_state=qc,
        reagent_lot=flag.reagent_lot,
        rule_version=rule_version,
        decided_at=datetime.now(UTC),
    )
    await audit_sink.append(record)
    return record

Verification & Testing

Pin the three behaviors that carry the most clinical and regulatory weight: a stable chronic abnormality auto-releases, an out-of-control analyzer never does, and a critical value pages regardless of every other signal.

python
from decimal import Decimal


def _flag(**kw):
    base = dict(
        result_id="r1", patient_id="P1", analyte="CREA",
        value=Decimal("2.4"), unit="mg/dL",
        patient_meta=PatientMeta(sex="M", age_group="adult", matrix="serum"),
        reagent_lot="LOT-A",
    )
    base.update(kw)
    return OutOfRangeFlag(**base)


def test_stable_chronic_abnormal_auto_releases():
    flag = _flag()
    intervals = {"CREA_M_adult_serum": Interval(low=Decimal("0.7"),
                                                high=Decimal("1.3"))}
    rng = resolve_range(flag, intervals)          # out of range, demographic-specific
    disp, conf = route_flag(flag, rng, DeltaStatus.STABLE, QCState.IN_CONTROL)
    assert disp is Disposition.AUTO_OVERRIDE
    assert conf >= AUTO_RELEASE_FLOOR


def test_out_of_control_qc_forces_hold():
    flag = _flag()
    intervals = {"CREA_M_adult_serum": Interval(low=Decimal("0.7"),
                                                high=Decimal("1.3"))}
    rng = resolve_range(flag, intervals)
    disp, _ = route_flag(flag, rng, DeltaStatus.STABLE, QCState.OUT_OF_CONTROL)
    assert disp is Disposition.HOLD_RECAL


def test_critical_value_always_pages():
    flag = _flag(value=Decimal("9.9"), is_critical=True)
    rng = resolve_range(flag, {})
    disp, _ = route_flag(flag, rng, DeltaStatus.ACUTE_SHIFT, QCState.OUT_OF_CONTROL)
    assert disp is Disposition.ALERT_IMMEDIATE

Expected results: all three pass. Extend the suite with a used_default=True case (must route to MANUAL_QUEUE, since demographic confidence is missing), a QCState.UNREACHABLE case (must not auto-release), and a boundary case where combined confidence equals AUTO_RELEASE_FLOOR exactly (which releases, since the comparison is >=). A ResolutionAudit round-trip through model_dump_json() and back confirms the audit payload serializes losslessly for the append-only store.

Compliance Note

Auto-resolution of out-of-range flags is an auto-verification activity governed by CLIA §493.1253(b)(2), which requires the laboratory to establish, verify, and follow documented procedures for reviewing results before release. The explicit auto-release floor, the QC gate that blocks release from an out-of-control analyzer, and the forced hold on acute shifts are the auditable evidence that clinically significant results are not silently auto-released. Because each ResolutionAudit record is attributable, timestamped in UTC, hashed, and written immutably in the release transaction, the pipeline also satisfies the electronic-record requirements of 21 CFR Part 11.10(e) for a tamper-evident review trail. The patient_hash and the history reads that feed the delta verdict stay inside the CLIA/CAP data boundaries that segment protected patient data from downstream interfaces.

Troubleshooting

Chronically abnormal results are being queued for manual review instead of auto-releasing

The demographic-specific interval is probably missing, so resolve_range returns used_default=True and the confidence base drops to zero, pushing the score below the auto-release floor. Confirm the {analyte}_{sex}_{age_group}_{matrix} key exists in the interval store for this cohort; a _default fallback keeps the result safe but intentionally will not auto-release.

Everything from one analyzer suddenly stopped auto-releasing

Check its quality-control state. A tripped Westgard rejection rule sets QCState.OUT_OF_CONTROL, which forces HOLD_RECAL for every flag on that analyte until the control series recovers. If the QC state is UNREACHABLE, the circuit breaker has fallen back to strict mode because the QC service timed out — restore the QC feed and the analyzer returns to normal auto-resolution.

A genuinely critical value was auto-released instead of paging

route_flag short-circuits to ALERT_IMMEDIATE only when is_critical is set on the flag. If a critical slipped through, the hard critical-limit check that sets is_critical did not fire upstream — verify the critical limits in the Reference Range Check Implementation and that the flag carried the boolean into this stage.

Auto-override rates jumped after a reagent lot change

A new lot shifts the analytical baseline, so bounds tuned for the prior lot now over-release. The lot is captured on every flag and audit record; map the reagent lot to the effective interval version and let the population feedback in Threshold Tuning & Calibration recalibrate the bounds against the observed false-positive rate.

We cannot reconstruct why a specific result was auto-released

The ResolutionAudit record for that result_id holds the full decision context — disposition, confidence, delta status, QC state, reagent lot, and rule_version. If a record is missing, the audit write was outside the release transaction; move audit_sink.append inside the same transactional boundary as the release so a decision can never exist without its provenance.

Part of: Threshold Tuning & Calibration