Files
2026-06-25 00:25:31 +08:00

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
};
}