fix: Trend alert matching uses fragile LIKE pattern
This commit is contained in:
@@ -51,6 +51,7 @@ public class ClinicalAlertConfiguration : IEntityTypeConfiguration<ClinicalAlert
|
|||||||
v => AlertSeverityExtensions.FromDbString(v))
|
v => AlertSeverityExtensions.FromDbString(v))
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
builder.Property(a => a.Details).HasColumnName("details").IsRequired();
|
builder.Property(a => a.Details).HasColumnName("details").IsRequired();
|
||||||
|
builder.Property(a => a.ObservationCode).HasColumnName("observation_code").HasMaxLength(50);
|
||||||
builder.Property(a => a.Status)
|
builder.Property(a => a.Status)
|
||||||
.HasColumnName("status")
|
.HasColumnName("status")
|
||||||
.HasMaxLength(20)
|
.HasMaxLength(20)
|
||||||
@@ -73,5 +74,7 @@ public class ClinicalAlertConfiguration : IEntityTypeConfiguration<ClinicalAlert
|
|||||||
builder.HasIndex(a => new { a.PatientId, a.TriggeredAt });
|
builder.HasIndex(a => new { a.PatientId, a.TriggeredAt });
|
||||||
builder.HasIndex(a => new { a.Severity, a.TriggeredAt })
|
builder.HasIndex(a => new { a.Severity, a.TriggeredAt })
|
||||||
.HasFilter("status = 'OPEN'");
|
.HasFilter("status = 'OPEN'");
|
||||||
|
builder.HasIndex(a => new { a.EncounterId, a.AlertType, a.ObservationCode })
|
||||||
|
.HasFilter("status IN ('OPEN', 'ESCALATED')");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class ClinicalAlert
|
|||||||
public AlertType AlertType { get; set; }
|
public AlertType AlertType { get; set; }
|
||||||
public AlertSeverity Severity { get; set; }
|
public AlertSeverity Severity { get; set; }
|
||||||
public string Details { get; set; } = null!;
|
public string Details { get; set; } = null!;
|
||||||
|
public string? ObservationCode { get; set; }
|
||||||
public AlertStatus Status { get; set; } = AlertStatus.Open;
|
public AlertStatus Status { get; set; } = AlertStatus.Open;
|
||||||
public DateTimeOffset? AcknowledgedAt { get; set; }
|
public DateTimeOffset? AcknowledgedAt { get; set; }
|
||||||
public string? AcknowledgedBy { get; set; }
|
public string? AcknowledgedBy { get; set; }
|
||||||
|
|||||||
+1269
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VigilCareClinicalAPI.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddAlertObservationCode : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "observation_code",
|
||||||
|
table: "clinical_alerts",
|
||||||
|
type: "character varying(50)",
|
||||||
|
maxLength: 50,
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
UPDATE clinical_alerts
|
||||||
|
SET observation_code = CASE
|
||||||
|
WHEN details LIKE '%HEART_RATE%' THEN 'HEART_RATE'
|
||||||
|
WHEN details LIKE '%RESP_RATE%' THEN 'RESP_RATE'
|
||||||
|
WHEN details LIKE '%SYSTOLIC_BP%' THEN 'SYSTOLIC_BP'
|
||||||
|
WHEN details LIKE '%TEMP_C%' THEN 'TEMP_C'
|
||||||
|
WHEN details LIKE '%SPO2%' THEN 'SPO2'
|
||||||
|
END
|
||||||
|
WHERE alert_type = 'RAPID_DETERIORATION'
|
||||||
|
AND observation_code IS NULL;
|
||||||
|
");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_clinical_alerts_encounter_id_alert_type_observation_code",
|
||||||
|
table: "clinical_alerts",
|
||||||
|
columns: new[] { "encounter_id", "alert_type", "observation_code" },
|
||||||
|
filter: "status IN ('OPEN', 'ESCALATED')");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_clinical_alerts_encounter_id_alert_type_observation_code",
|
||||||
|
table: "clinical_alerts");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "observation_code",
|
||||||
|
table: "clinical_alerts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,6 +113,11 @@ namespace VigilCareClinicalAPI.Migrations
|
|||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("encounter_id");
|
.HasColumnName("encounter_id");
|
||||||
|
|
||||||
|
b.Property<string>("ObservationCode")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)")
|
||||||
|
.HasColumnName("observation_code");
|
||||||
|
|
||||||
b.Property<Guid?>("ObservationId")
|
b.Property<Guid?>("ObservationId")
|
||||||
.HasColumnType("uuid")
|
.HasColumnType("uuid")
|
||||||
.HasColumnName("observation_id");
|
.HasColumnName("observation_id");
|
||||||
@@ -154,6 +159,9 @@ namespace VigilCareClinicalAPI.Migrations
|
|||||||
b.HasIndex("Severity", "TriggeredAt")
|
b.HasIndex("Severity", "TriggeredAt")
|
||||||
.HasFilter("status = 'OPEN'");
|
.HasFilter("status = 'OPEN'");
|
||||||
|
|
||||||
|
b.HasIndex("EncounterId", "AlertType", "ObservationCode")
|
||||||
|
.HasFilter("status IN ('OPEN', 'ESCALATED')");
|
||||||
|
|
||||||
b.ToTable("clinical_alerts", null, t =>
|
b.ToTable("clinical_alerts", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("chk_clinical_alerts_alert_type", "alert_type IN ('SEPSIS_WARNING', 'CRITICAL_HEART_RATE', 'CRITICAL_TEMP_C', 'CRITICAL_POTASSIUM_MEQ_L', 'CRITICAL_SPO2', 'CRITICAL_RESP_RATE', 'CRITICAL_WBC_K_UL', 'CRITICAL_SYSTOLIC_BP', 'CRITICAL_DIASTOLIC_BP', 'CRITICAL_LACTATE_MMOL_L', 'CRITICAL_AVPU', 'CRITICAL_GLUCOSE_MG_DL', 'CRITICAL_PAO2_MMHG', 'CRITICAL_PLATELET_K_UL', 'CRITICAL_BILIRUBIN_MG_DL', 'CRITICAL_CREATININE_MG_DL', 'WARNING_HEART_RATE', 'WARNING_TEMP_C', 'WARNING_POTASSIUM_MEQ_L', 'WARNING_SPO2', 'WARNING_RESP_RATE', 'WARNING_WBC_K_UL', 'WARNING_SYSTOLIC_BP', 'WARNING_DIASTOLIC_BP', 'WARNING_LACTATE_MMOL_L', 'WARNING_GLUCOSE_MG_DL', 'WARNING_PAO2_MMHG', 'WARNING_PLATELET_K_UL', 'WARNING_BILIRUBIN_MG_DL', 'WARNING_CREATININE_MG_DL', 'NEWS2_WARNING', 'NEWS2_EMERGENCY', 'RAPID_DETERIORATION', 'QSOFA_WARNING', 'QSOFA_SCREEN', 'GCS_CRITICAL', 'GCS_WARNING', 'SOFA_SEPSIS', 'SOFA_WARNING')");
|
t.HasCheckConstraint("chk_clinical_alerts_alert_type", "alert_type IN ('SEPSIS_WARNING', 'CRITICAL_HEART_RATE', 'CRITICAL_TEMP_C', 'CRITICAL_POTASSIUM_MEQ_L', 'CRITICAL_SPO2', 'CRITICAL_RESP_RATE', 'CRITICAL_WBC_K_UL', 'CRITICAL_SYSTOLIC_BP', 'CRITICAL_DIASTOLIC_BP', 'CRITICAL_LACTATE_MMOL_L', 'CRITICAL_AVPU', 'CRITICAL_GLUCOSE_MG_DL', 'CRITICAL_PAO2_MMHG', 'CRITICAL_PLATELET_K_UL', 'CRITICAL_BILIRUBIN_MG_DL', 'CRITICAL_CREATININE_MG_DL', 'WARNING_HEART_RATE', 'WARNING_TEMP_C', 'WARNING_POTASSIUM_MEQ_L', 'WARNING_SPO2', 'WARNING_RESP_RATE', 'WARNING_WBC_K_UL', 'WARNING_SYSTOLIC_BP', 'WARNING_DIASTOLIC_BP', 'WARNING_LACTATE_MMOL_L', 'WARNING_GLUCOSE_MG_DL', 'WARNING_PAO2_MMHG', 'WARNING_PLATELET_K_UL', 'WARNING_BILIRUBIN_MG_DL', 'WARNING_CREATININE_MG_DL', 'NEWS2_WARNING', 'NEWS2_EMERGENCY', 'RAPID_DETERIORATION', 'QSOFA_WARNING', 'QSOFA_SCREEN', 'GCS_CRITICAL', 'GCS_WARNING', 'SOFA_SEPSIS', 'SOFA_WARNING')");
|
||||||
|
|||||||
@@ -101,14 +101,14 @@ public class TrendDetector
|
|||||||
|
|
||||||
var affected = await db.Database.ExecuteSqlInterpolatedAsync($"""
|
var affected = await db.Database.ExecuteSqlInterpolatedAsync($"""
|
||||||
INSERT INTO clinical_alerts
|
INSERT INTO clinical_alerts
|
||||||
(id, encounter_id, patient_id, alert_type, severity, details, status, triggered_at)
|
(id, encounter_id, patient_id, alert_type, severity, details, observation_code, status, triggered_at)
|
||||||
SELECT {alertId}, {encounterId}, {patientId},
|
SELECT {alertId}, {encounterId}, {patientId},
|
||||||
'RAPID_DETERIORATION', 'WARNING', {fullDetails}, 'OPEN', {triggeredAt}
|
'RAPID_DETERIORATION', 'WARNING', {fullDetails}, {observationCode}, 'OPEN', {triggeredAt}
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
SELECT 1 FROM clinical_alerts
|
SELECT 1 FROM clinical_alerts
|
||||||
WHERE encounter_id = {encounterId}
|
WHERE encounter_id = {encounterId}
|
||||||
AND alert_type = 'RAPID_DETERIORATION'
|
AND alert_type = 'RAPID_DETERIORATION'
|
||||||
AND details LIKE {$"%{observationCode}%"}
|
AND observation_code = {observationCode}
|
||||||
AND status IN ('OPEN', 'ESCALATED')
|
AND status IN ('OPEN', 'ESCALATED')
|
||||||
)
|
)
|
||||||
""", ct);
|
""", ct);
|
||||||
|
|||||||
Reference in New Issue
Block a user