feature: Trend Detection & Alert Suppression Windows

This commit is contained in:
voltsrage
2026-06-18 21:18:13 +08:00
parent e6f7989298
commit 3c54feb38a
31 changed files with 2751 additions and 18 deletions
@@ -0,0 +1,62 @@
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})"
};
}