feature: Sepsis Engine Refactor: Remove SIRS, Rewire qSOFA + Bundle

Frontend: GCS Entry, SOFA Display, Sepsis UI Refactor
This commit is contained in:
voltsrage
2026-06-21 03:56:27 +08:00
parent 93ea473d2b
commit bf46e6554a
48 changed files with 2686 additions and 714 deletions
@@ -0,0 +1,191 @@
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[Collection("Integration")]
public class SepsisRefactorTests : IAsyncLifetime
{
private readonly ApiFixture _fixture;
private Guid _encounterId;
private Guid _patientId;
public SepsisRefactorTests(ApiFixture fixture) => _fixture = fixture;
public async Task InitializeAsync()
{
using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await DbResetHelper.ResetAsync(db);
var patient = new Patient
{
Id = Guid.NewGuid(), Mrn = "MRN-REFACTOR-001", FirstName = "Refactor", LastName = "Test",
DateOfBirth = new DateOnly(1960, 1, 1), Gender = "M",
CreatedAt = DateTimeOffset.UtcNow
};
var encounter = new Encounter
{
Id = Guid.NewGuid(), PatientId = patient.Id, EncounterType = EncounterType.Inpatient,
Status = EncounterStatus.Active, Department = Department.Icu,
AttendingPhysician = "Dr. Refactor", AdmittedAt = DateTimeOffset.UtcNow,
CreatedAt = DateTimeOffset.UtcNow
};
db.Patients.Add(patient);
db.Encounters.Add(encounter);
await db.SaveChangesAsync();
_patientId = patient.Id;
_encounterId = encounter.Id;
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task SirsObservations_NoSepsisWarningAlert()
{
using var scope = _fixture.Services.CreateScope();
var qsofa = scope.ServiceProvider.GetRequiredService<QsofaDetector>();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await qsofa.ProcessObservationAsync(_encounterId, _patientId, "TEMP_C", 39m);
await qsofa.ProcessObservationAsync(_encounterId, _patientId, "HEART_RATE", 110m);
await qsofa.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 22m);
await qsofa.ProcessObservationAsync(_encounterId, _patientId, "WBC_K_UL", 15m);
(await db.ClinicalAlerts.CountAsync(a => a.AlertType == AlertType.SepsisWarning))
.Should().Be(0);
}
[Fact]
public async Task SofaDelta2_TriggersSepsisBundleCreation()
{
using var scope = _fixture.Services.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<SepsisAlertHandler>();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var alertId = Guid.NewGuid();
db.ClinicalAlerts.Add(new ClinicalAlert
{
Id = alertId, EncounterId = _encounterId, PatientId = _patientId,
AlertType = AlertType.SofaSepsis, Severity = AlertSeverity.Critical,
Details = "SOFA delta +2", Status = AlertStatus.Open,
TriggeredAt = DateTimeOffset.UtcNow
});
await db.SaveChangesAsync();
await handler.OnSepsisAlertCreatedAsync(
_encounterId, alertId, AlertType.SofaSepsis, CancellationToken.None);
(await db.SepsisBundles.CountAsync()).Should().Be(1);
}
[Fact]
public async Task QsofaScreen_DoesNotTriggerBundle()
{
using var scope = _fixture.Services.CreateScope();
var detector = scope.ServiceProvider.GetRequiredService<QsofaDetector>();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 24m);
await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 95m);
(await db.SepsisBundles.CountAsync()).Should().Be(0);
}
[Fact]
public async Task QsofaScreen_IsWarningNotCritical()
{
using var scope = _fixture.Services.CreateScope();
var detector = scope.ServiceProvider.GetRequiredService<QsofaDetector>();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 24m);
await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 95m);
var alert = await db.ClinicalAlerts.SingleAsync();
alert.AlertType.Should().Be(AlertType.QsofaScreen);
alert.Severity.Should().Be(AlertSeverity.Warning);
}
[Fact]
public void QsofaScreen_IsSuppressible()
{
AlertType.QsofaScreen.IsSuppressible().Should().BeTrue();
}
[Fact]
public async Task LegacySepsisWarning_StillQueryable()
{
using var scope = _fixture.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var alertService = scope.ServiceProvider.GetRequiredService<IAlertService>();
db.ClinicalAlerts.Add(new ClinicalAlert
{
Id = Guid.NewGuid(), EncounterId = _encounterId, PatientId = _patientId,
AlertType = AlertType.SepsisWarning, Severity = AlertSeverity.Critical,
Details = "Legacy SIRS alert", Status = AlertStatus.Resolved,
TriggeredAt = DateTimeOffset.UtcNow.AddDays(-1)
});
await db.SaveChangesAsync();
var page = await alertService.ListByEncounterAsync(_encounterId, null, 1, 10);
page.Items.Should().ContainSingle(a => a.AlertType == AlertType.SepsisWarning);
}
[Fact]
public void CannotCreateNewSepsisWarning()
{
var act = () => AlertCreationGuard.EnsureAllowed(AlertType.SepsisWarning);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*deprecated*");
}
[Fact]
public async Task SepsisBundleCompliance_StillTracked()
{
using var scope = _fixture.Services.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<SepsisAlertHandler>();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var alertId = Guid.NewGuid();
db.ClinicalAlerts.Add(new ClinicalAlert
{
Id = alertId, EncounterId = _encounterId, PatientId = _patientId,
AlertType = AlertType.SofaSepsis, Severity = AlertSeverity.Critical,
Details = "SOFA delta +2", Status = AlertStatus.Open,
TriggeredAt = DateTimeOffset.UtcNow
});
await db.SaveChangesAsync();
await handler.OnSepsisAlertCreatedAsync(
_encounterId, alertId, AlertType.SofaSepsis, CancellationToken.None);
var bundle = await db.SepsisBundles.SingleAsync();
bundle.DeadlineAt = DateTimeOffset.UtcNow.AddHours(-1);
await db.SaveChangesAsync();
var monitor = new SepsisBundleMonitorService(
_fixture.Services,
_fixture.Services.GetRequiredService<ClinicalMetrics>(),
_fixture.Services.GetRequiredService<ILogger<SepsisBundleMonitorService>>());
await monitor.ScanOverdueBundlesAsync(CancellationToken.None);
var updated = await db.SepsisBundles.AsNoTracking().SingleAsync();
updated.ComplianceStatus.Should().Be(SepsisBundleComplianceStatus.NonCompliant);
}
[Fact]
public async Task QsofaScreen_IncludesLabRecommendation()
{
using var scope = _fixture.Services.CreateScope();
var detector = scope.ServiceProvider.GetRequiredService<QsofaDetector>();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await detector.ProcessObservationAsync(_encounterId, _patientId, "RESP_RATE", 24m);
await detector.ProcessObservationAsync(_encounterId, _patientId, "SYSTOLIC_BP", 95m);
var alert = await db.ClinicalAlerts.SingleAsync();
alert.Details.Should().Contain("order SOFA labs");
}
}