using System.Text.Json; using Microsoft.EntityFrameworkCore; public static class ClinicalAlertFactory { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public static string SerializeOutboxPayload(object payload) => JsonSerializer.Serialize(payload, JsonOptions); /// /// Idempotent INSERT. Returns true when a new row was created. /// When is null, dedupes on encounter + alert type only /// (NEWS2, SOFA, GCS). When set, dedupes on encounter + alert type + observation code (trend). /// public static async Task TryInsertAsync( AppDbContext db, Guid alertId, Guid encounterId, Guid patientId, AlertType alertType, AlertSeverity severity, string details, AlertExplanation? explanation, string? observationCode, DateTimeOffset triggeredAt, CancellationToken ct) { var explanationJson = explanation is null ? null : JsonSerializer.Serialize(explanation, JsonOptions); var affected = observationCode is null ? await db.Database.ExecuteSqlInterpolatedAsync($""" INSERT INTO clinical_alerts (id, encounter_id, patient_id, alert_type, severity, details, explanation, status, triggered_at) SELECT {alertId}, {encounterId}, {patientId}, {alertType.ToDbString()}, {severity.ToDbString()}, {details}, {explanationJson}::jsonb, 'OPEN', {triggeredAt} WHERE NOT EXISTS ( SELECT 1 FROM clinical_alerts WHERE encounter_id = {encounterId} AND alert_type = {alertType.ToDbString()} AND status IN ('OPEN', 'ESCALATED') ) """, ct) : await db.Database.ExecuteSqlInterpolatedAsync($""" INSERT INTO clinical_alerts (id, encounter_id, patient_id, alert_type, severity, details, explanation, observation_code, status, triggered_at) SELECT {alertId}, {encounterId}, {patientId}, {alertType.ToDbString()}, {severity.ToDbString()}, {details}, {explanationJson}::jsonb, {observationCode}, 'OPEN', {triggeredAt} WHERE NOT EXISTS ( SELECT 1 FROM clinical_alerts WHERE encounter_id = {encounterId} AND alert_type = {alertType.ToDbString()} AND observation_code = {observationCode} AND status IN ('OPEN', 'ESCALATED') ) """, ct); return affected > 0; } }