166 lines
6.3 KiB
C#
166 lines
6.3 KiB
C#
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<AppDbContext>();
|
|
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<AppDbContext>();
|
|
|
|
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<JsonDocument>();
|
|
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<JsonDocument>();
|
|
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",
|
|
};
|
|
}
|