using System.Text.Json; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using StackExchange.Redis; [Collection("Integration")] public class ClinicalRefactorEndToEndTests : IAsyncLifetime { private readonly ApiFixture _fixture; private readonly HttpClient _client; public ClinicalRefactorEndToEndTests(ApiFixture fixture) { _fixture = fixture; _client = fixture.CreateClient(); } public async Task InitializeAsync() { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await DbResetHelper.ResetAsync(db); var redis = scope.ServiceProvider.GetRequiredService(); await DataSeeder.SeedThresholdsOnlyAsync(db, redis); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public async Task SofaProgressionScenario_EndToEnd() { var scenario = ScenarioReplayHelper.Load("sepsis-sofa-progression-01.json"); var (_, encounterId) = await ScenarioReplayHelper.ReplayObservationsAsync( _client, scenario, _fixture.Services); await ScenarioReplayHelper.WaitForAlertTypeAsync( _fixture.Services, encounterId, AlertType.QsofaScreen, TimeSpan.FromSeconds(60)); await ScenarioReplayHelper.WaitForAlertTypeAsync( _fixture.Services, encounterId, AlertType.SofaSepsis, TimeSpan.FromSeconds(60)); SepsisBundle? bundle = null; var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(10); while (DateTime.UtcNow < deadline) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); bundle = await db.SepsisBundles.FirstOrDefaultAsync(b => b.EncounterId == encounterId); if (bundle is not null) break; await Task.Delay(500); } bundle.Should().NotBeNull("expected sepsis bundle for encounter after SOFA_SEPSIS alert"); bundle!.TriggeringAlertType.Should().Be("SOFA_SEPSIS"); } [Fact] public async Task GcsDeclineScenario_EndToEnd() { var scenario = ScenarioReplayHelper.Load("neurological-decline-gcs-01.json"); var (_, encounterId) = await ScenarioReplayHelper.ReplayObservationsAsync( _client, scenario, _fixture.Services); await ScenarioReplayHelper.WaitForAlertTypeAsync( _fixture.Services, encounterId, AlertType.GcsWarning, TimeSpan.FromSeconds(60)); await ScenarioReplayHelper.WaitForAlertTypeAsync( _fixture.Services, encounterId, AlertType.GcsCritical, TimeSpan.FromSeconds(60)); await ScenarioReplayHelper.AssertNoAlertTypeAsync( _fixture.Services, encounterId, AlertType.SofaSepsis, TimeSpan.FromSeconds(5)); await ScenarioReplayHelper.AssertNoAlertTypeAsync( _fixture.Services, encounterId, AlertTypeExtensions.FromDbString("SEPSIS_WARNING"), TimeSpan.FromSeconds(1)); } [Fact] public async Task PartialSofaScenario_StalenessFlags() { var scenario = ScenarioReplayHelper.Load("sofa-partial-spo2-fallback-01.json"); var (_, encounterId) = await ScenarioReplayHelper.ReplayObservationsAsync( _client, scenario, _fixture.Services); var jsonOpts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var sofa = await ScenarioReplayHelper.WaitForSofaScoreAsync( _fixture.Services, encounterId, TimeSpan.FromSeconds(60), s => { if (string.IsNullOrEmpty(s.StalenessFlags)) return false; var f = JsonSerializer.Deserialize(s.StalenessFlags, jsonOpts); return f?.UsedSpO2Fallback == true; }); var flags = JsonSerializer.Deserialize(sofa.StalenessFlags!, jsonOpts); flags!.UsedSpO2Fallback.Should().BeTrue("expected SpO2/FiO2 proxy for respiratory SOFA"); } [Theory] [InlineData("stable-baseline-01.json")] [InlineData("cardiac-arrest-post-mi-01.json")] [InlineData("post-op-hemorrhage-01.json")] [InlineData("respiratory-failure-asthma-01.json")] [InlineData("dka-electrolyte-01.json")] [InlineData("medication-false-alarm-01.json")] [InlineData("hypothermia-elderly-01.json")] [InlineData("uti-sepsis-elderly-01.json")] public void ExistingScenarios_ValidateWithoutErrors(string fileName) { var scenario = ScenarioReplayHelper.Load(fileName); var errors = ScenarioValidator.Validate(scenario); errors.Should().BeEmpty(string.Join("; ", errors)); } [Fact] public async Task UtiSepsisScenario_NowUsesSofa() { var scenario = ScenarioReplayHelper.Load("uti-sepsis-elderly-01.json"); var (_, encounterId) = await ScenarioReplayHelper.ReplayObservationsAsync( _client, scenario, _fixture.Services); await ScenarioReplayHelper.WaitForAlertTypeAsync( _fixture.Services, encounterId, AlertType.SofaSepsis, TimeSpan.FromSeconds(60)); await ScenarioReplayHelper.AssertNoAlertTypeAsync( _fixture.Services, encounterId, AlertTypeExtensions.FromDbString("SEPSIS_WARNING"), TimeSpan.FromSeconds(5)); } }