feature: Explainable Alerts
This commit is contained in:
@@ -147,6 +147,23 @@ public class SofaDetector
|
||||
var result = SofaCalculator.ComputeTotal(
|
||||
respiratory, coagulation, liver, cardiovascular, cns, renal);
|
||||
|
||||
var rawValues = new Dictionary<string, string?>
|
||||
{
|
||||
["RESPIRATORY"] = pao2 is not null && fio2 is not null
|
||||
? $"PaO2/FiO2={pao2}/{fio2}%"
|
||||
: usedSpO2Fallback && spo2 is not null && fio2 is not null
|
||||
? $"SpO2/FiO2={spo2}/{fio2}%"
|
||||
: null,
|
||||
["COAGULATION"] = GetValue("PLATELET_K_UL")?.ToString(),
|
||||
["LIVER"] = GetValue("BILIRUBIN_MG_DL")?.ToString(),
|
||||
["CARDIOVASCULAR"] = vasopressor is not null
|
||||
? $"{vasopressor.DrugName} {vasopressor.DoseUgKgMin} µg/kg/min"
|
||||
: map?.ToString(),
|
||||
["CNS"] = gcsTotal?.ToString(),
|
||||
["RENAL"] = creatinine?.ToString() ?? urineMlDay?.ToString()
|
||||
};
|
||||
var contributors = SofaContributorBuilder.Build(result, rawValues);
|
||||
|
||||
var stalenessFlags = JsonSerializer.Serialize(new SofaStalenessFlags(
|
||||
staleComponents.Distinct().ToList(),
|
||||
missingComponents.Distinct().ToList(),
|
||||
@@ -179,13 +196,13 @@ public class SofaDetector
|
||||
{
|
||||
alertCreated = await TryCreateAlertAsync(
|
||||
encounterId, patientId, AlertType.SofaSepsis, AlertSeverity.Critical,
|
||||
result, isBaseline ? null : delta, stalenessFlags, ct);
|
||||
result, isBaseline ? null : delta, stalenessFlags, contributors, ct);
|
||||
}
|
||||
else if (delta == 1)
|
||||
{
|
||||
alertCreated = await TryCreateAlertAsync(
|
||||
encounterId, patientId, AlertType.SofaWarning, AlertSeverity.Warning,
|
||||
result, delta, stalenessFlags, ct);
|
||||
result, delta, stalenessFlags, contributors, ct);
|
||||
}
|
||||
|
||||
_metrics.SofaScoresTotal
|
||||
@@ -197,7 +214,7 @@ public class SofaDetector
|
||||
result.Total, encounterId, isBaseline, delta);
|
||||
|
||||
return new SofaScoringResult(
|
||||
SofaOutcome.ScoreComputed, result, isBaseline, delta, alertCreated);
|
||||
SofaOutcome.ScoreComputed, result, isBaseline, delta, alertCreated, contributors);
|
||||
}
|
||||
|
||||
private async Task<int?> LoadGcsTotalAsync(Guid encounterId)
|
||||
@@ -268,6 +285,7 @@ public class SofaDetector
|
||||
Guid encounterId, Guid patientId,
|
||||
AlertType alertType, AlertSeverity severity,
|
||||
SofaResult result, int? delta, string stalenessFlags,
|
||||
List<ScoreContributor> contributors,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (alertType == AlertType.SofaWarning)
|
||||
@@ -292,20 +310,15 @@ public class SofaDetector
|
||||
$"Liver={result.Liver}, CV={result.Cardiovascular}, CNS={result.Cns}, Renal={result.Renal}. " +
|
||||
$"Staleness: {stalenessFlags}";
|
||||
|
||||
var affected = await db.Database.ExecuteSqlInterpolatedAsync($"""
|
||||
INSERT INTO clinical_alerts
|
||||
(id, encounter_id, patient_id, alert_type, severity, details, status, triggered_at)
|
||||
SELECT {alertId}, {encounterId}, {patientId},
|
||||
{alertType.ToDbString()}, {severity.ToDbString()}, {details}, '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);
|
||||
var explanation = AlertExplanationBuilder.Build(
|
||||
"SOFA", result.Total, contributors);
|
||||
|
||||
if (affected == 0)
|
||||
var inserted = await ClinicalAlertFactory.TryInsertAsync(
|
||||
db, alertId, encounterId, patientId,
|
||||
alertType, severity, details, explanation,
|
||||
observationCode: null, triggeredAt, ct);
|
||||
|
||||
if (!inserted)
|
||||
{
|
||||
await tx.RollbackAsync(ct);
|
||||
return false;
|
||||
@@ -315,7 +328,7 @@ public class SofaDetector
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Topic = "alert.generated",
|
||||
Payload = JsonSerializer.Serialize(new
|
||||
Payload = ClinicalAlertFactory.SerializeOutboxPayload(new
|
||||
{
|
||||
alertId,
|
||||
encounterId,
|
||||
@@ -326,6 +339,7 @@ public class SofaDetector
|
||||
triggeredAt,
|
||||
sofaTotal = result.Total,
|
||||
sofaDelta = delta,
|
||||
explanation,
|
||||
partitionKey = encounterId.ToString()
|
||||
}),
|
||||
PartitionKey = encounterId.ToString(),
|
||||
|
||||
Reference in New Issue
Block a user