Files
vigilcare-clinical/VigilCareClinicalAPI/Services/SepsisBundleService.cs
T

161 lines
5.8 KiB
C#

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<SepsisBundle?> 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<SepsisBundle?> 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<SepsisBundle> 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);
}
}