feature:
Full SOFA Score: Data Layer + Scoring Engine Glasgow Coma Scale: Data Layer + Scoring Engine
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
public record GcsResult(
|
||||
GcsOutcome Outcome,
|
||||
int? TotalScore = null,
|
||||
string? Classification = null,
|
||||
bool AlertCreated = false,
|
||||
int PresentComponents = 0)
|
||||
{
|
||||
public static readonly GcsResult NotGcsCode = new(GcsOutcome.NotGcsCode);
|
||||
|
||||
public static GcsResult IncompleteComponents(int presentCount) =>
|
||||
new(GcsOutcome.IncompleteComponents, PresentComponents: presentCount);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
public record GcsScoreResponse(
|
||||
int EyeScore,
|
||||
int VerbalScore,
|
||||
int MotorScore,
|
||||
int TotalScore,
|
||||
string Classification,
|
||||
DateTimeOffset CalculatedAt);
|
||||
@@ -0,0 +1 @@
|
||||
public record SofaCachedValue(decimal Value, DateTimeOffset RecordedAt);
|
||||
@@ -0,0 +1,3 @@
|
||||
public record SofaResult(
|
||||
int Total, int Respiratory, int Coagulation, int Liver,
|
||||
int Cardiovascular, int Cns, int Renal);
|
||||
@@ -0,0 +1,7 @@
|
||||
public record SofaScoreResponse(
|
||||
int TotalScore,
|
||||
int RespiratoryScore, int CoagulationScore, int LiverScore,
|
||||
int CardiovascularScore, int CnsScore, int RenalScore,
|
||||
bool IsBaseline, int? DeltaFromBaseline,
|
||||
SofaStalenessInfo? Staleness,
|
||||
DateTimeOffset CalculatedAt);
|
||||
@@ -0,0 +1,10 @@
|
||||
public record SofaScoringResult(
|
||||
SofaOutcome Outcome,
|
||||
SofaResult? Score = null,
|
||||
bool IsBaseline = false,
|
||||
int? DeltaFromBaseline = null,
|
||||
bool AlertCreated = false)
|
||||
{
|
||||
public static readonly SofaScoringResult NotSofaTrigger = new(SofaOutcome.NotSofaTrigger);
|
||||
public static readonly SofaScoringResult EncounterNotFound = new(SofaOutcome.EncounterNotFound);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public record SofaStalenessFlags(
|
||||
IReadOnlyList<string> StaleComponents,
|
||||
IReadOnlyList<string> MissingComponents,
|
||||
bool UsedSpO2Fallback);
|
||||
@@ -0,0 +1,4 @@
|
||||
public record SofaStalenessInfo(
|
||||
IReadOnlyList<string> StaleComponents,
|
||||
IReadOnlyList<string> MissingComponents,
|
||||
bool UsedSpO2Fallback);
|
||||
@@ -0,0 +1,90 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
|
||||
public class SofaVasopressorResolver
|
||||
{
|
||||
private static readonly HashSet<string> VasopressorDrugs = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"DOPAMINE", "DOBUTAMINE", "EPINEPHRINE", "NOREPINEPHRINE",
|
||||
"VASOPRESSIN", "PHENYLEPHRINE"
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private readonly AppDbContext _db;
|
||||
private readonly IConnectionMultiplexer _redis;
|
||||
private readonly SofaOptions _options;
|
||||
|
||||
public SofaVasopressorResolver(
|
||||
AppDbContext db,
|
||||
IConnectionMultiplexer redis,
|
||||
IOptions<SofaOptions> options)
|
||||
{
|
||||
_db = db;
|
||||
_redis = redis;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public static string VasopressorCacheKey(Guid encounterId) =>
|
||||
$"sofa:{encounterId}:vasopressor";
|
||||
|
||||
public async Task CacheFromAdministrationAsync(MedicationAdministration med, CancellationToken ct)
|
||||
{
|
||||
if (!VasopressorDrugs.Contains(med.DrugName)) return;
|
||||
|
||||
var info = new VasopressorInfo(med.DrugName, NormalizeDose(med.DrugName, med.Dose, med.DoseUnit));
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
info.DrugName,
|
||||
info.DoseUgKgMin,
|
||||
med.AdministeredAt
|
||||
}, JsonOptions);
|
||||
|
||||
await _redis.GetDatabase().StringSetAsync(
|
||||
VasopressorCacheKey(med.EncounterId),
|
||||
json,
|
||||
TimeSpan.FromHours(_options.VasopressorWindowHours));
|
||||
}
|
||||
|
||||
public async Task<VasopressorInfo?> GetActiveVasopressorAsync(
|
||||
Guid encounterId, CancellationToken ct)
|
||||
{
|
||||
var cached = await _redis.GetDatabase()
|
||||
.StringGetAsync(VasopressorCacheKey(encounterId));
|
||||
if (cached.HasValue)
|
||||
{
|
||||
using var doc = JsonDocument.Parse((string)cached!);
|
||||
var root = doc.RootElement;
|
||||
return new VasopressorInfo(
|
||||
root.GetProperty("drugName").GetString()!,
|
||||
root.GetProperty("doseUgKgMin").GetDecimal());
|
||||
}
|
||||
|
||||
var since = DateTimeOffset.UtcNow.AddHours(-_options.VasopressorWindowHours);
|
||||
var med = await _db.MedicationAdministrations
|
||||
.AsNoTracking()
|
||||
.Where(m => m.EncounterId == encounterId
|
||||
&& VasopressorDrugs.Contains(m.DrugName)
|
||||
&& m.AdministeredAt >= since)
|
||||
.OrderByDescending(m => m.AdministeredAt)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (med is null) return null;
|
||||
return new VasopressorInfo(med.DrugName, NormalizeDose(med.DrugName, med.Dose, med.DoseUnit));
|
||||
}
|
||||
|
||||
private static decimal NormalizeDose(string drug, decimal dose, string unit) =>
|
||||
unit.ToLowerInvariant() switch
|
||||
{
|
||||
"mcg/kg/min" or "µg/kg/min" => dose,
|
||||
"mcg/min" or "µg/min" => dose / 70m,
|
||||
"mg/hr" => dose * 1000m / 60m / 70m,
|
||||
_ => dose
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
public record VasopressorInfo(string DrugName, decimal DoseUgKgMin);
|
||||
Reference in New Issue
Block a user