feature: Explainable Alerts
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
public static class GcsContributorBuilder
|
||||
{
|
||||
public static List<ScoreContributor> Build(int eye, int verbal, int motor) => new()
|
||||
{
|
||||
new() { Parameter = "Eye", Points = eye, RawValue = eye.ToString(), NormalRange = "4" },
|
||||
new() { Parameter = "Verbal", Points = verbal, RawValue = verbal.ToString(), NormalRange = "5" },
|
||||
new() { Parameter = "Motor", Points = motor, RawValue = motor.ToString(), NormalRange = "6" },
|
||||
};
|
||||
}
|
||||
@@ -75,18 +75,20 @@ public class GcsDetector
|
||||
encounterId, patientId, (int)eye, (int)verbal, (int)motor,
|
||||
total, classification, calculatedAt, ct);
|
||||
|
||||
var contributors = GcsContributorBuilder.Build((int)eye, (int)verbal, (int)motor);
|
||||
|
||||
var alertCreated = false;
|
||||
if (total <= 8)
|
||||
{
|
||||
alertCreated = await TryCreateAlertAsync(
|
||||
encounterId, patientId, AlertType.GcsCritical, AlertSeverity.Critical,
|
||||
eye, verbal, motor, total, classification, ct);
|
||||
eye, verbal, motor, total, classification, contributors, ct);
|
||||
}
|
||||
else if (total <= 12)
|
||||
{
|
||||
alertCreated = await TryCreateAlertAsync(
|
||||
encounterId, patientId, AlertType.GcsWarning, AlertSeverity.Warning,
|
||||
eye, verbal, motor, total, classification, ct);
|
||||
eye, verbal, motor, total, classification, contributors, ct);
|
||||
}
|
||||
|
||||
await PublishScoredEventAsync(
|
||||
@@ -105,7 +107,8 @@ public class GcsDetector
|
||||
"GCS score {Total} ({Classification}) for encounter {Id} — E={Eye} V={Verbal} M={Motor}",
|
||||
total, classification, encounterId, eye, verbal, motor);
|
||||
|
||||
return new GcsResult(GcsOutcome.ScoreComputed, total, classification, alertCreated, 3);
|
||||
return new GcsResult(
|
||||
GcsOutcome.ScoreComputed, total, classification, alertCreated, 3, contributors);
|
||||
}
|
||||
|
||||
private async Task PersistScoreAsync(
|
||||
@@ -140,6 +143,7 @@ public class GcsDetector
|
||||
AlertType alertType, AlertSeverity severity,
|
||||
decimal eye, decimal verbal, decimal motor,
|
||||
int total, string classification,
|
||||
List<ScoreContributor> contributors,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (alertType == AlertType.GcsWarning)
|
||||
@@ -164,20 +168,15 @@ public class GcsDetector
|
||||
var details =
|
||||
$"GCS total {total} ({classification}): E={eye}, V={verbal}, M={motor}.";
|
||||
|
||||
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(
|
||||
"GCS", 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;
|
||||
@@ -187,7 +186,7 @@ public class GcsDetector
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Topic = "alert.generated",
|
||||
Payload = JsonSerializer.Serialize(new
|
||||
Payload = ClinicalAlertFactory.SerializeOutboxPayload(new
|
||||
{
|
||||
alertId,
|
||||
encounterId,
|
||||
@@ -198,6 +197,7 @@ public class GcsDetector
|
||||
triggeredAt,
|
||||
gcsTotal = total,
|
||||
gcsClassification = classification,
|
||||
explanation,
|
||||
partitionKey = encounterId.ToString()
|
||||
}),
|
||||
PartitionKey = encounterId.ToString(),
|
||||
|
||||
Reference in New Issue
Block a user