namespace Evergreen.Health.Application.Health.Sleep; /// 本版没有异常判定算法;未来规则只产生事件,不直接操作音箱或家具。 public sealed record SleepAnomaly(string Code, string Description, DateTimeOffset ObservedAt); public interface ISleepAnomalyRule { string Key { get; } SleepAnomaly? Evaluate(IReadOnlyList observations); } /// 每个舒睡会话独立持有;只有语音实际播报后才记录提醒时间。 public sealed class AnomalyReminderGate { private readonly TimeSpan _interval; private readonly Dictionary _delivered = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); public AnomalyReminderGate(SleepWorkflowOptions options) => _interval = options.SameAnomalyReminderInterval; public bool CanRemind(string code, DateTimeOffset now) { lock (_lock) return !_delivered.TryGetValue(code, out var last) || now - last >= _interval; } public void MarkDelivered(string code, DateTimeOffset now) { lock (_lock) _delivered[code] = now; } }