using System.Text.Json; using Microsoft.EntityFrameworkCore; public class SepsisBundleService : ISepsisBundleService { private readonly AppDbContext _db; private readonly IOrderService _orderService; private readonly ClinicalMetrics _metrics; public SepsisBundleService(AppDbContext db, IOrderService orderService, ClinicalMetrics metrics) { _db = db; _orderService = orderService; _metrics = metrics; } public async Task TryCreateBundleAsync( Guid encounterId, Guid triggeringAlertId, AlertType alertType, CancellationToken ct = default) { var exists = await _db.SepsisBundles .AnyAsync(b => b.EncounterId == encounterId && b.ComplianceStatus == SepsisBundleComplianceStatus.InProgress, ct); if (exists) return null; var recognizedAt = DateTimeOffset.UtcNow; var bundle = new SepsisBundle { Id = Guid.NewGuid(), EncounterId = encounterId, TriggeringAlertId = triggeringAlertId, TriggeringAlertType = alertType.ToDbString(), RecognizedAt = recognizedAt, DeadlineAt = recognizedAt.AddHours(1), ComplianceStatus = SepsisBundleComplianceStatus.InProgress }; var elementDefs = new (SepsisBundleElementType Type, OrderType OrderType, string Description)[] { (SepsisBundleElementType.BloodCultures, OrderType.Lab, "SEP-1: Blood cultures"), (SepsisBundleElementType.SerumLactate, OrderType.Lab, "SEP-1: Serum lactate"), (SepsisBundleElementType.BroadSpectrumAntibiotics, OrderType.Medication, "SEP-1: Broad-spectrum antibiotics"), (SepsisBundleElementType.IvFluidResuscitation, OrderType.Procedure, "SEP-1: IV fluid bolus"), }; foreach (var (elementType, orderType, description) in elementDefs) { var order = await _orderService.CreateAsync(encounterId, new CreateOrderRequest( orderType, description, "sepsis-bundle-engine")); bundle.Elements.Add(new SepsisBundleElement { Id = Guid.NewGuid(), ElementType = elementType, Status = SepsisBundleElementStatus.Pending, OrderId = order.Id }); } _db.SepsisBundles.Add(bundle); _db.OutboxEvents.Add(new OutboxEvent { Id = Guid.NewGuid(), Topic = "sepsis.bundle.created", Payload = JsonSerializer.Serialize(new { bundleId = bundle.Id, encounterId, triggeringAlertId, triggeringAlertType = alertType.ToDbString(), recognizedAt, deadlineAt = bundle.DeadlineAt, partitionKey = encounterId.ToString() }), PartitionKey = encounterId.ToString(), CreatedAt = DateTimeOffset.UtcNow }); await _db.SaveChangesAsync(ct); return bundle; } public async Task GetCurrentByEncounterAsync(Guid encounterId) { return await _db.SepsisBundles .AsNoTracking() .Include(b => b.Elements) .ThenInclude(e => e.Order) .Where(b => b.EncounterId == encounterId) .OrderByDescending(b => b.RecognizedAt) .FirstOrDefaultAsync(); } public async Task GetByIdAsync(Guid id) { var bundle = await _db.SepsisBundles .AsNoTracking() .Include(b => b.Elements) .ThenInclude(e => e.Order) .FirstOrDefaultAsync(b => b.Id == id); if (bundle is null) throw new NotFoundException("Sepsis bundle not found.", "BUNDLE_NOT_FOUND"); return bundle; } public async Task OnOrderResultedAsync(Guid orderId, CancellationToken ct = default) { var element = await _db.SepsisBundleElements .Include(e => e.Bundle) .FirstOrDefaultAsync(e => e.OrderId == orderId, ct); if (element is null || element.Status == SepsisBundleElementStatus.Completed) return; element.Status = SepsisBundleElementStatus.Completed; element.CompletedAt = DateTimeOffset.UtcNow; var bundle = element.Bundle; var allComplete = await _db.SepsisBundleElements .Where(e => e.BundleId == bundle.Id && e.Id != element.Id) .AllAsync(e => e.Status == SepsisBundleElementStatus.Completed, ct); if (allComplete) { bundle.CompletedAt = DateTimeOffset.UtcNow; bundle.ComplianceStatus = bundle.CompletedAt <= bundle.DeadlineAt ? SepsisBundleComplianceStatus.Compliant : SepsisBundleComplianceStatus.NonCompliant; _metrics.SepsisBundleComplianceTotal .WithLabels(bundle.ComplianceStatus.ToDbString()).Inc(); } _db.OutboxEvents.Add(new OutboxEvent { Id = Guid.NewGuid(), Topic = "sepsis.bundle.updated", Payload = JsonSerializer.Serialize(new { bundleId = bundle.Id, encounterId = bundle.EncounterId, elementId = element.Id, elementType = element.ElementType.ToDbString(), elementStatus = element.Status.ToDbString(), complianceStatus = bundle.ComplianceStatus.ToDbString(), completedAt = element.CompletedAt, partitionKey = bundle.EncounterId.ToString() }), PartitionKey = bundle.EncounterId.ToString(), CreatedAt = DateTimeOffset.UtcNow }); await _db.SaveChangesAsync(ct); } }