using System.Net; using System.Net.Http.Json; using System.Text.Json; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; [Collection("Integration")] public class AlertQualityScenarioAttributionTests : IAsyncLifetime { private readonly ApiFixture _fixture; private readonly HttpClient _client; public AlertQualityScenarioAttributionTests(ApiFixture fixture) { _fixture = fixture; _client = fixture.CreateClient(); _client.AsNurse(); } public async Task InitializeAsync() { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); await DbResetHelper.ResetAsync(db); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public async Task AlertQuality_FiltersByScenarioId() { Guid matchFeedbackId; Guid otherFeedbackId; using (var scope = _fixture.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); var patientA = NewPatient("MRN-SCEN-A"); var patientB = NewPatient("MRN-SCEN-B"); var encounterA = NewEncounter(patientA.Id); var encounterB = NewEncounter(patientB.Id); var alertA = NewAlert(encounterA.Id, patientA.Id, AlertType.News2Warning); var alertB = NewAlert(encounterB.Id, patientB.Id, AlertType.News2Emergency); db.Patients.AddRange(patientA, patientB); db.Encounters.AddRange(encounterA, encounterB); db.ClinicalAlerts.AddRange(alertA, alertB); db.SimulationRuns.Add(new SimulationRun { Id = Guid.NewGuid(), ScenarioId = "uti-sepsis-elderly-01", ScenarioName = "UTI Sepsis", Speed = 60, Status = SimulationRunStatus.Completed, PatientId = patientA.Id, EncounterId = encounterA.Id, SessionId = "session-b-alert-quality", StartedByUserId = "tester", StartedAt = DateTimeOffset.UtcNow.AddHours(-1), CompletedAt = DateTimeOffset.UtcNow, }); db.SimulationRuns.Add(new SimulationRun { Id = Guid.NewGuid(), ScenarioId = "medication-false-alarm-01", ScenarioName = "Med False Alarm", Speed = 60, Status = SimulationRunStatus.Completed, PatientId = patientB.Id, EncounterId = encounterB.Id, SessionId = "session-b-alert-quality", StartedByUserId = "tester", StartedAt = DateTimeOffset.UtcNow.AddHours(-1), CompletedAt = DateTimeOffset.UtcNow, }); var fbA = new AlertFeedback { Id = Guid.NewGuid(), AlertId = alertA.Id, UserId = Guid.NewGuid(), FeedbackType = AlertFeedbackType.WouldAct, CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-10), }; var fbB = new AlertFeedback { Id = Guid.NewGuid(), AlertId = alertB.Id, UserId = Guid.NewGuid(), FeedbackType = AlertFeedbackType.FalsePositive, CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-5), }; db.AlertFeedbacks.AddRange(fbA, fbB); await db.SaveChangesAsync(); matchFeedbackId = fbA.Id; otherFeedbackId = fbB.Id; } var all = await _client.GetAsync("/api/v1/alerts/quality-metrics/feedback"); all.StatusCode.Should().Be(HttpStatusCode.OK); var allBody = await all.Content.ReadFromJsonAsync(); var allItems = allBody!.RootElement.GetProperty("data").GetProperty("items"); allItems.GetArrayLength().Should().Be(2); var match = allItems.EnumerateArray() .Single(e => e.GetProperty("id").GetGuid() == matchFeedbackId); match.GetProperty("scenarioId").GetString().Should().Be("uti-sepsis-elderly-01"); match.GetProperty("sessionId").GetString().Should().Be("session-b-alert-quality"); var filtered = await _client.GetAsync( "/api/v1/alerts/quality-metrics/feedback?scenarioId=uti-sepsis-elderly-01"); filtered.StatusCode.Should().Be(HttpStatusCode.OK); var filteredBody = await filtered.Content.ReadFromJsonAsync(); var filteredItems = filteredBody!.RootElement.GetProperty("data").GetProperty("items"); filteredItems.GetArrayLength().Should().Be(1); filteredItems[0].GetProperty("id").GetGuid().Should().Be(matchFeedbackId); filteredItems[0].GetProperty("scenarioId").GetString().Should().Be("uti-sepsis-elderly-01"); // Other scenario's feedback must not leak into the filter. filteredItems.EnumerateArray() .Any(e => e.GetProperty("id").GetGuid() == otherFeedbackId) .Should().BeFalse(); } private static Patient NewPatient(string mrn) => new() { Id = Guid.NewGuid(), Mrn = mrn, FirstName = "Sim", LastName = "Patient", DateOfBirth = new DateOnly(1980, 1, 1), Gender = "F", CreatedAt = DateTimeOffset.UtcNow, IsSimulated = true, }; private static Encounter NewEncounter(Guid patientId) => new() { Id = Guid.NewGuid(), PatientId = patientId, EncounterType = EncounterType.Inpatient, Status = EncounterStatus.Active, Department = Department.Icu, AttendingPhysician = "Dr. Test", AdmittedAt = DateTimeOffset.UtcNow, CreatedAt = DateTimeOffset.UtcNow, }; private static ClinicalAlert NewAlert(Guid encounterId, Guid patientId, AlertType type) => new() { Id = Guid.NewGuid(), EncounterId = encounterId, PatientId = patientId, AlertType = type, Severity = AlertSeverity.Warning, Details = "Test alert", Status = AlertStatus.Acknowledged, TriggeredAt = DateTimeOffset.UtcNow.AddMinutes(-30), AcknowledgedAt = DateTimeOffset.UtcNow.AddMinutes(-20), AcknowledgedBy = "nurse", }; }