72 lines
2.6 KiB
C#
72 lines
2.6 KiB
C#
using StackExchange.Redis;
|
|
|
|
public static class TrendCalculator
|
|
{
|
|
// NEWS2-relevant codes that support trend detection.
|
|
public static readonly IReadOnlyList<string> TrendCodes = new[]
|
|
{
|
|
"HEART_RATE", "RESP_RATE", "SYSTOLIC_BP", "TEMP_C", "SPO2"
|
|
};
|
|
|
|
public static bool IsTrendCode(string observationCode) =>
|
|
TrendCodes.Contains(observationCode);
|
|
|
|
public static string HistoryKey(Guid encounterId, string code) =>
|
|
$"trend:{encounterId}:{code}";
|
|
|
|
public static RedisKey[] AllHistoryKeys(Guid encounterId) =>
|
|
TrendCodes.Select(c => (RedisKey)HistoryKey(encounterId, c)).ToArray();
|
|
|
|
/// <summary>
|
|
/// Computes rate of change (units per minute) between the oldest and newest
|
|
/// entries within the window. Returns null if fewer than 2 entries or window exceeded.
|
|
/// </summary>
|
|
public static decimal? ComputeRatePerMinute(
|
|
IReadOnlyList<TrendHistoryEntry> entries,
|
|
int windowMinutes)
|
|
{
|
|
if (entries.Count < 2) return null;
|
|
|
|
var newest = entries[^1];
|
|
var oldest = entries[0];
|
|
|
|
var deltaMinutes = (newest.RecordedAt - oldest.RecordedAt).TotalMinutes;
|
|
if (deltaMinutes <= 0 || deltaMinutes > windowMinutes) return null;
|
|
|
|
return (newest.Value - oldest.Value) / (decimal)deltaMinutes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the rate exceeds the configured threshold.
|
|
/// For SPO2 and SYSTOLIC_BP, checks negative rate (decline) as clinically significant.
|
|
/// </summary>
|
|
public static bool ExceedsThreshold(
|
|
string observationCode,
|
|
decimal ratePerMinute,
|
|
decimal thresholdPerMinute) =>
|
|
observationCode switch
|
|
{
|
|
"SPO2" or "SYSTOLIC_BP" => ratePerMinute <= -thresholdPerMinute,
|
|
_ => ratePerMinute >= thresholdPerMinute
|
|
};
|
|
|
|
public static string DescribeTrend(
|
|
string observationCode, decimal ratePerMinute, decimal currentValue) =>
|
|
observationCode switch
|
|
{
|
|
"SPO2" or "SYSTOLIC_BP" =>
|
|
$"Rapid decline: {observationCode} falling at {Math.Abs(ratePerMinute):F2}/min (current {currentValue})",
|
|
_ =>
|
|
$"Rapid rise: {observationCode} rising at {ratePerMinute:F2}/min (current {currentValue})"
|
|
};
|
|
|
|
public static string DisplayName(string observationCode) => observationCode switch
|
|
{
|
|
"HEART_RATE" => "Heart Rate",
|
|
"RESP_RATE" => "Respiratory Rate",
|
|
"SYSTOLIC_BP" => "Systolic BP",
|
|
"TEMP_C" => "Temperature",
|
|
"SPO2" => "SpO2",
|
|
_ => observationCode
|
|
};
|
|
} |