# 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<SepsisBundle?> 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
