using StackExchange.Redis; public static class News2Calculator { // The 7 NEWS2 parameter codes. Order is stable for MGET. public static readonly IReadOnlyList ParameterCodes = new[] { "RESP_RATE", "SPO2", "SYSTOLIC_BP", "HEART_RATE", "AVPU", "TEMP_C", "SUPPLEMENTAL_O2" }; public static RedisKey[] AllParameterKeys(Guid encounterId) => ParameterCodes .Select(code => (RedisKey)$"news2:{encounterId}:{code}") .ToArray(); public static string ParameterKey(Guid encounterId, string code) => $"news2:{encounterId}:{code}"; public static bool IsNews2Code(string observationCode) => ParameterCodes.Contains(observationCode); // --- Individual parameter scoring --- // Each method returns 0-3 per the official NEWS2 scoring table. public static int ScoreRespRate(decimal value) => value switch { <= 8 => 3, <= 11 => 1, <= 20 => 0, <= 24 => 2, _ => 3 // >= 25 }; // Scale 1 (standard). Scale 2 (hypercapnic respiratory failure) is not implemented. public static int ScoreSpo2(decimal value) => value switch { <= 91 => 3, <= 93 => 2, <= 95 => 1, _ => 0 // >= 96 }; public static int ScoreSystolicBp(decimal value) => value switch { <= 90 => 3, <= 100 => 2, <= 110 => 1, <= 219 => 0, _ => 3 // >= 220 }; public static int ScoreHeartRate(decimal value) => value switch { <= 40 => 3, <= 50 => 1, <= 90 => 0, <= 110 => 1, <= 130 => 2, _ => 3 // >= 131 }; // AVPU: Alert=0, Voice/Pain/Unresponsive=3 (any non-Alert scores 3) public static int ScoreConsciousness(decimal value) => value switch { 0 => 0, // Alert _ => 3 // Voice (1), Pain (2), Unresponsive (3) }; public static int ScoreTemperature(decimal value) => value switch { <= 35.0m => 3, <= 36.0m => 1, <= 38.0m => 0, <= 39.0m => 1, _ => 2 // >= 39.1 }; // 0 = room air, 1 = on supplemental oxygen public static int ScoreSupplementalO2(decimal value) => value >= 1 ? 2 : 0; // Dispatch to the correct scoring function by observation code. public static int ScoreParameter(string observationCode, decimal value) => observationCode switch { "RESP_RATE" => ScoreRespRate(value), "SPO2" => ScoreSpo2(value), "SYSTOLIC_BP" => ScoreSystolicBp(value), "HEART_RATE" => ScoreHeartRate(value), "AVPU" => ScoreConsciousness(value), "TEMP_C" => ScoreTemperature(value), "SUPPLEMENTAL_O2" => ScoreSupplementalO2(value), _ => throw new ArgumentOutOfRangeException(nameof(observationCode)) }; // Determine risk level from total score and single-param-3 flag. public static string DetermineRiskLevel(int totalScore, bool hasSingleParamThree) => totalScore switch { >= 7 => "HIGH", >= 5 => "MEDIUM", _ when hasSingleParamThree => "LOW_MEDIUM", _ => "LOW" }; }