Files
voltsrage 93ea473d2b feature:
Full SOFA Score: Data Layer + Scoring Engine

Glasgow Coma Scale: Data Layer + Scoring Engine
2026-06-21 01:09:50 +08:00

160 lines
5.5 KiB
C#

public static class SofaCalculator
{
public static readonly IReadOnlyList<string> SofaObservationCodes = new[]
{
"PAO2_MMHG", "FIO2_PCT", "PLATELET_K_UL", "BILIRUBIN_MG_DL",
"CREATININE_MG_DL", "URINE_OUTPUT_ML_H",
"SYSTOLIC_BP", "DIASTOLIC_BP", "SPO2", "SUPPLEMENTAL_O2"
};
public static readonly IReadOnlySet<string> SofaCodeSet =
new HashSet<string>(SofaObservationCodes);
public static bool IsSofaCode(string observationCode) =>
SofaCodeSet.Contains(observationCode);
// GCS components and gcs.scored also trigger SOFA re-score (CNS organ system)
public static bool TriggersRescore(string observationCode) =>
IsSofaCode(observationCode) || GcsCalculator.IsGcsCode(observationCode);
public static string CacheKey(Guid encounterId, string code) =>
$"sofa:{encounterId}:{code}";
// --- 1. Respiratory: PaO2/FiO2 ratio ---
public static int ScoreRespiratory(decimal? pao2, decimal? fio2, bool onMechanicalVent)
{
if (pao2 is null || fio2 is null || fio2 == 0) return 0;
var ratio = pao2.Value / (fio2.Value / 100m);
return ratio switch
{
>= 400 when onMechanicalVent => 0,
>= 400 => 0,
>= 300 => 1,
>= 200 => 2,
>= 100 when onMechanicalVent => 3,
>= 100 => 2,
_ when onMechanicalVent => 4,
_ => 3
};
}
// SpO2/FiO2 proxy when PaO2 unavailable (Rice et al. 2007)
public static int ScoreRespiratoryFromSpo2(decimal? spo2, decimal? fio2, bool onMechanicalVent)
{
if (spo2 is null || fio2 is null || fio2 == 0) return 0;
var sfRatio = spo2.Value / (fio2.Value / 100m);
return sfRatio switch
{
>= 315 => 0,
>= 235 => 1,
>= 150 => 2,
>= 67 when onMechanicalVent => 3,
>= 67 => 2,
_ when onMechanicalVent => 4,
_ => 3
};
}
// --- 2. Coagulation: Platelet count (k/µL) ---
public static int ScoreCoagulation(decimal? platelets)
{
if (platelets is null) return 0;
return platelets.Value switch
{
>= 150 => 0,
>= 100 => 1,
>= 50 => 2,
>= 20 => 3,
_ => 4
};
}
// --- 3. Liver: Bilirubin (mg/dL) ---
public static int ScoreLiver(decimal? bilirubin)
{
if (bilirubin is null) return 0;
return bilirubin.Value switch
{
< 1.2m => 0,
< 2.0m => 1,
< 6.0m => 2,
< 12.0m => 3,
_ => 4
};
}
// --- 4. Cardiovascular: MAP and vasopressor dose ---
public static int ScoreCardiovascular(decimal? map, VasopressorInfo? vasopressor)
{
if (vasopressor is not null)
{
return vasopressor.DrugName.ToUpperInvariant() switch
{
"DOPAMINE" when vasopressor.DoseUgKgMin > 15m => 4,
"EPINEPHRINE" when vasopressor.DoseUgKgMin > 0.1m => 4,
"NOREPINEPHRINE" when vasopressor.DoseUgKgMin > 0.1m => 4,
"DOPAMINE" when vasopressor.DoseUgKgMin > 5m => 3,
"EPINEPHRINE" => 3,
"NOREPINEPHRINE" => 3,
"DOPAMINE" => 2,
"DOBUTAMINE" => 2,
_ => 1
};
}
if (map is null) return 0;
return map.Value < 70m ? 1 : 0;
}
// --- 5. CNS: Glasgow Coma Scale (from Phase 25) ---
public static int ScoreCns(int? gcsTotal) =>
GcsCalculator.ToSofaCnsScore(gcsTotal ?? 15);
// --- 6. Renal: Creatinine (mg/dL) or urine output (mL/day) ---
public static int ScoreRenal(decimal? creatinine, decimal? urineOutputMlPerDay)
{
var creatScore = creatinine switch
{
null => 0,
< 1.2m => 0,
< 2.0m => 1,
< 3.5m => 2,
< 5.0m => 3,
_ => 4
};
var urineScore = urineOutputMlPerDay switch
{
null => 0,
< 200m => 4,
< 500m => 3,
_ => 0
};
return Math.Max(creatScore, urineScore);
}
public static SofaResult ComputeTotal(
int respiratory, int coagulation, int liver,
int cardiovascular, int cns, int renal) =>
new(
Total: respiratory + coagulation + liver + cardiovascular + cns + renal,
Respiratory: respiratory,
Coagulation: coagulation,
Liver: liver,
Cardiovascular: cardiovascular,
Cns: cns,
Renal: renal);
// Count organ systems with component data for baseline eligibility
public static int CountPopulatedOrganSystems(
bool hasRespiratory,
bool hasCoagulation,
bool hasLiver,
bool hasCardiovascular,
bool hasCns,
bool hasRenal) =>
new[] { hasRespiratory, hasCoagulation, hasLiver,
hasCardiovascular, hasCns, hasRenal }
.Count(hasData => hasData);
}