# Guide 23: Sepsis Bundle Automation ## What is a Sepsis Bundle? **Sepsis** is a life-threatening condition where the body's response to an infection damages its own organs. It's one of the leading causes of death in hospitals, and early treatment dramatically improves survival. The **Surviving Sepsis Campaign** defines a set of mandatory interventions (a "bundle") that must be completed within 1 hour of sepsis recognition: | Element | What It Is | Why It's Urgent | |---------|-----------|----------------| | Blood cultures | Draw blood samples before antibiotics | Identifies the infecting organism so treatment can be targeted | | Serum lactate | Blood test for lactate level | High lactate indicates tissue damage from inadequate blood flow | | Broad-spectrum antibiotics | Administer antibiotics immediately | Every hour of delay increases mortality by ~8% | | IV fluid resuscitation | Administer 30 mL/kg crystalloid fluids | Restores blood volume and organ perfusion | **What is a "bundle" in software terms?** It's a checklist of 4 orders that the system creates automatically when sepsis is detected. Each element is tracked as PENDING → COMPLETED, and the bundle as a whole is tracked as IN_PROGRESS → COMPLIANT or NON_COMPLIANT based on whether all 4 elements are completed within the 1-hour deadline. --- ## Why Automate Sepsis Bundles? Without automation, a nurse sees a sepsis alert, mentally recalls the 4-element bundle, manually creates each order, and tracks compliance on paper. In a busy ICU with multiple deteriorating patients, elements get missed or delayed. Automation ensures: 1. **Instant order creation**: All 4 orders are created the moment sepsis is detected — no manual recall needed 2. **Deadline tracking**: The 1-hour clock starts automatically 3. **Compliance monitoring**: A background service checks every 5 minutes for overdue bundles 4. **Audit trail**: Every bundle is recorded with its triggering alert, deadline, and outcome --- ## Architecture Overview ``` SOFA score computed (delta >= 2 from baseline) │ ▼ SofaDetector creates SOFA_SEPSIS alert │ ▼ SepsisAlertHandler.OnSepsisAlertCreatedAsync() │ ▼ SepsisBundleService.TryCreateBundleAsync() │ ├── Creates SepsisBundle (IN_PROGRESS, deadline = now + 1 hour) ├── Creates 4 SepsisBundleElements (PENDING) ├── Creates 4 Orders (orderedBy: "sepsis-bundle-engine") └── All in one PostgreSQL transaction (atomic) ... 1 hour passes ... SepsisBundleMonitorService (every 5 minutes) │ ├── Finds IN_PROGRESS bundles past deadline └── Marks as NON_COMPLIANT if elements remain PENDING ``` --- ## Trigger: SOFA Delta >= 2 Sepsis bundles are only triggered by `SOFA_SEPSIS` alerts — not by qSOFA screens, NEWS2 scores, or any other alert type: ```csharp public class SepsisAlertHandler { public async Task OnSepsisAlertCreatedAsync( Guid encounterId, Guid alertId, AlertType alertType, CancellationToken ct) { if (alertType != AlertType.SofaSepsis) return; // Only SOFA_SEPSIS triggers a bundle var bundle = await _bundleService.TryCreateBundleAsync( encounterId, alertId, alertType, ct); if (bundle is not null) _logger.LogInformation( "Sepsis bundle {BundleId} created for encounter {EncounterId}", bundle.Id, encounterId); } } ``` **Why only SOFA_SEPSIS?** The Sepsis-3 definition requires evidence of organ dysfunction (SOFA delta >= 2 from baseline). A qSOFA screen (>= 2 criteria) is a bedside screen that recommends ordering SOFA labs — it doesn't confirm sepsis. Creating bundles on qSOFA would produce false positives. The clinical flow is: qSOFA screen → order labs → SOFA computed → if delta >= 2 → sepsis bundle. --- ## Bundle Creation: Atomic Transaction The bundle, its 4 elements, and the 4 corresponding orders are all created in a single PostgreSQL transaction: ```csharp public async Task TryCreateBundleAsync( Guid encounterId, Guid alertId, AlertType alertType, CancellationToken ct) { // Idempotency: only one in-progress bundle per encounter var existing = await _db.SepsisBundles .AnyAsync(b => b.EncounterId == encounterId && b.ComplianceStatus == SepsisBundleComplianceStatus.InProgress, ct); if (existing) return null; await using var tx = await _db.Database.BeginTransactionAsync(ct); var recognizedAt = DateTimeOffset.UtcNow; var bundle = new SepsisBundle { Id = Guid.NewGuid(), EncounterId = encounterId, TriggeringAlertId = alertId, TriggeringAlertType = alertType.ToDbString(), RecognizedAt = recognizedAt, DeadlineAt = recognizedAt.AddHours(1), // 1-hour compliance window ComplianceStatus = SepsisBundleComplianceStatus.InProgress, }; _db.SepsisBundles.Add(bundle); // Create the 4 bundle elements with linked orders var elements = new[] { ("BLOOD_CULTURE", "Draw blood cultures (2 sets, aerobic + anaerobic)"), ("SERUM_LACTATE", "Obtain serum lactate level"), ("ANTIBIOTICS", "Administer broad-spectrum antibiotics"), ("IV_FLUIDS", "Begin IV crystalloid fluid resuscitation (30 mL/kg)"), }; foreach (var (code, description) in elements) { var order = new Order { Id = Guid.NewGuid(), EncounterId = encounterId, OrderType = MapOrderType(code), Description = description, Status = OrderStatus.Pending, OrderedBy = "sepsis-bundle-engine", OrderedAt = recognizedAt, }; _db.Orders.Add(order); _db.SepsisBundleElements.Add(new SepsisBundleElement { Id = Guid.NewGuid(), BundleId = bundle.Id, ElementCode = code, OrderId = order.Id, Status = SepsisBundleElementStatus.Pending, }); } // Outbox events for downstream notification _db.OutboxEvents.Add(/* sepsis.bundle.created event */); await _db.SaveChangesAsync(ct); await tx.CommitAsync(ct); _metrics.SepsisBundleComplianceTotal.WithLabels("CREATED").Inc(); return bundle; } ``` **Why atomic?** If the bundle is created but one of the orders fails, you'd have a partially-created bundle with missing elements — clinicians would see a checklist with items missing. The transaction ensures all-or-nothing: either all 4 elements and their orders exist, or none do. **Why `orderedBy: "sepsis-bundle-engine"`?** This identifies auto-created orders vs manually-created ones. Clinicians see that the order was system-generated and can distinguish it from orders they placed themselves. --- ## Bundle Element Lifecycle Each bundle element starts as PENDING and moves to COMPLETED when the linked order is resulted: ``` PENDING ──(order resulted)──► COMPLETED ``` When a clinician marks an order as "resulted" (e.g., blood cultures drawn, antibiotics administered), the corresponding bundle element is updated. When all 4 elements are COMPLETED before the deadline, the bundle transitions: ``` IN_PROGRESS ──(all 4 elements completed within 1 hour)──► COMPLIANT ``` --- ## Compliance Monitoring: SepsisBundleMonitorService A background service runs every 5 minutes and checks for overdue bundles: ```csharp public class SepsisBundleMonitorService : BackgroundService { private static readonly TimeSpan ScanInterval = TimeSpan.FromMinutes(5); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { await ScanOverdueBundlesAsync(stoppingToken); } catch (Exception ex) when (ex is not OperationCanceledException) { _logger.LogError(ex, "Sepsis bundle monitor error — will retry"); } await Task.Delay(ScanInterval, stoppingToken); } } internal async Task ScanOverdueBundlesAsync(CancellationToken ct) { var overdue = await db.SepsisBundles .Where(b => b.ComplianceStatus == SepsisBundleComplianceStatus.InProgress && b.DeadlineAt < DateTimeOffset.UtcNow) .ToListAsync(ct); foreach (var bundle in overdue) { bundle.ComplianceStatus = SepsisBundleComplianceStatus.NonCompliant; _metrics.SepsisBundleComplianceTotal .WithLabels("NON_COMPLIANT").Inc(); var incompleteCount = await db.SepsisBundleElements .CountAsync(e => e.BundleId == bundle.Id && e.Status != SepsisBundleElementStatus.Completed, ct); _logger.LogWarning( "Sepsis bundle {BundleId} for encounter {EncounterId} marked NON_COMPLIANT — " + "deadline {Deadline} passed with {Incomplete} incomplete elements", bundle.Id, bundle.EncounterId, bundle.DeadlineAt, incompleteCount); } if (overdue.Count > 0) await db.SaveChangesAsync(ct); } } ``` The scan finds all bundles that are still IN_PROGRESS but past their deadline, marks them NON_COMPLIANT, and logs which elements were incomplete. The `sepsis_bundle_compliance_total` Prometheus counter tracks compliance outcomes on the Grafana dashboard. --- ## Idempotency: One Bundle Per Encounter The `TryCreateBundleAsync` method checks for existing in-progress bundles before creating a new one: ```csharp var existing = await _db.SepsisBundles .AnyAsync(b => b.EncounterId == encounterId && b.ComplianceStatus == SepsisBundleComplianceStatus.InProgress, ct); if (existing) return null; ``` This prevents multiple bundles from being created if the SOFA score triggers multiple `SOFA_SEPSIS` alerts (e.g., if the score worsens further). Only one bundle can be in progress per encounter at a time. --- ## The Complete Sepsis Detection Timeline ``` t=0:00 qSOFA screen: resp_rate=24, systolic_bp=95 (2/3 criteria) → QSOFA_SCREEN warning alert → Recommendation: "Order SOFA labs" t=0:30 Labs drawn: platelets, bilirubin, creatinine t=1:00 Lab results arrive + vitals recorded → SOFA baseline established (total = 3) t=2:00 Patient deteriorates — new labs + vitals → SOFA current = 6, delta = 3 from baseline → SOFA_SEPSIS critical alert created → SepsisAlertHandler triggers bundle creation t=2:00 Sepsis bundle created (deadline = t=3:00): ✓ Blood cultures order (PENDING) ✓ Serum lactate order (PENDING) ✓ Antibiotics order (PENDING) ✓ IV fluids order (PENDING) t=2:10 Nurse draws blood cultures → order resulted → element COMPLETED (1/4) t=2:15 Lactate result arrives → element COMPLETED (2/4) t=2:20 Antibiotics administered → element COMPLETED (3/4) t=2:35 IV fluids initiated → element COMPLETED (4/4) → Bundle status: COMPLIANT (within 1-hour deadline) -- OR -- t=3:00 Deadline passes with 2/4 elements still PENDING → SepsisBundleMonitorService marks: NON_COMPLIANT → Prometheus counter: sepsis_bundle_compliance_total{status="NON_COMPLIANT"} ``` --- ## Database Schema ``` sepsis_bundles ├── id (UUID) ├── encounter_id (FK) ├── triggering_alert_id (FK) ├── triggering_alert_type ("SOFA_SEPSIS") ├── recognized_at (timestamp) ├── deadline_at (recognized_at + 1 hour) ├── compliance_status ("IN_PROGRESS" | "COMPLIANT" | "NON_COMPLIANT") └── completed_at (nullable) sepsis_bundle_elements ├── id (UUID) ├── bundle_id (FK → sepsis_bundles) ├── element_code ("BLOOD_CULTURE" | "SERUM_LACTATE" | "ANTIBIOTICS" | "IV_FLUIDS") ├── order_id (FK → orders) └── status ("PENDING" | "COMPLETED") ``` --- ## Key Takeaways - **Only SOFA_SEPSIS triggers bundles** — qSOFA is a screen, not a confirmation. Bundles require evidence of organ dysfunction (SOFA delta >= 2). - **Atomic creation ensures completeness** — all 4 elements and orders are created in one transaction; no partially-created bundles - **1-hour compliance window is automatically enforced** — the deadline is set at creation time and checked every 5 minutes - **One bundle per encounter at a time** — prevents duplicate bundles from repeated SOFA alerts during deterioration - **Auto-created orders are labeled** — `orderedBy: "sepsis-bundle-engine"` distinguishes automated from manual orders - **Compliance is tracked as a Prometheus metric** — trends in COMPLIANT vs NON_COMPLIANT rates are visible on the dashboard for quality improvement