using System.Timers; using adas_core.Domain.Enums; using adas_core.Domain.Models; using adas_core.Domain.Models.GroupedObservations; using adas_core.Domain.Utils; using MongoDB.Bson; using Serilog; using static adas_core.Domain.Models.GroupedObservation; using Timer = System.Timers.Timer; namespace adas_core.Application.Subscriptions; /** * This class is used to group the subscribers by the same group */ public class WsSubscriberGrouped { private readonly EventHandler _sendEvent; public WsSubscriberGrouped(ObjectId patientId, string wsId, string? timeZoneId, GroupedField gf, GroupedObservation lastGroupedObservationObs, EventHandler sendEvent) { _sendEvent = sendEvent; if (WsSubscriber.ToList().FirstOrDefault(s => s == wsId) == null) WsSubscriber.Add(wsId); TimeZoneId = timeZoneId ?? "Romance Standard Time"; PatientId = patientId; Names = CollectionsUtils.IfEmptyOrNull(gf.Names, [gf.Name ?? string.Empty]); Max = gf.Max; Since = gf.Since; StartTimeShift = gf.StartTimeShift; Regularity = gf.Regularity; Result = gf.Result; Group.Add(wsId, gf.Group ?? string.Empty); UpdateLastGo(lastGroupedObservationObs); HashCode = CryptoAdas.CreateMd5GroupedObs(gf, patientId); Timer = new Timer { Interval = SetUpTimerInterval() }; Timer.Elapsed += Timer_Elapsed; if (Regularity != GroupedObservationEnum.Regularity.Times) Timer.Start(); } public List WsSubscriber { get; set; } = []; public ObjectId PatientId { get; set; } public List Names { get; set; } public int Max { get; set; } public GroupedObservationEnum.Regularity? Regularity { get; set; } public List Result { get; set; } public GroupedObservationEnum.Since Since { get; set; } public List StartTimeShift { get; set; } public string HashCode { get; set; } public string TimeZoneId { get; set; } public List LastGroupedObservationObs { get; set; } = []; public Dictionary Group { get; set; } = new(); public Timer Timer { get; set; } public void UpdateLastGo(GroupedObservation lastGroupedObservationObs) { LastGroupedObservationObs.Clear(); foreach (var name in Names) { var lastObs = lastGroupedObservationObs.Observations.LastOrDefault(c => c.Name == name); if (lastObs != null) { LastGroupedObservationObs.Add(lastObs); } else { lastObs = lastGroupedObservationObs.Observations.LastOrDefault(); if (lastObs != null) LastGroupedObservationObs.Add(lastObs); } } } private void Timer_Elapsed(object? sender, ElapsedEventArgs e) { var currentDateTime = DateTime.Now; Timer.Stop(); try { bool hasCurrentObs; switch (Regularity) { case GroupedObservationEnum.Regularity.Second: // Filtra observaciones con el mismo segundo actual hasCurrentObs = LastGroupedObservationObs.Any(obs => obs.Time.Second == currentDateTime.Second && obs.Time.Minute == currentDateTime.Minute && obs.Time.Hour == currentDateTime.Hour && obs.Time.Date == currentDateTime.Date); break; case GroupedObservationEnum.Regularity.Minute: // Filtra observaciones con el mismo minuto actual hasCurrentObs = LastGroupedObservationObs.Any(obs => obs.Time.Minute == currentDateTime.Minute && obs.Time.Hour == currentDateTime.Hour && obs.Time.Date == currentDateTime.Date); break; case GroupedObservationEnum.Regularity.Day: // Filtra observaciones con la misma fecha actual (día completo) hasCurrentObs = LastGroupedObservationObs.Any(obs => obs.Time.Date == currentDateTime.Date); break; default: // Filtra observaciones con la misma hora actual hasCurrentObs = LastGroupedObservationObs.Any(obs => obs.Time.Hour == currentDateTime.Hour && obs.Time.Date == currentDateTime.Date); break; } // Si no hay al menos una observación que coincide, invoca el evento if (!hasCurrentObs) _sendEvent(this, HashCode); } catch (Exception ex) { Log.Warning( $"Exception on WsSubsciptor for patient: {PatientId} message: {ex.Message} exception: {ex}"); } TimerReestart(); } private void TimerReestart() { Timer.Interval = SetUpTimerInterval(); Timer.Start(); } private int SetUpTimerInterval() { var currentDateTime = DateTime.Now; int graceTime; DateTime nextIntervalTime; switch (Regularity) { case GroupedObservationEnum.Regularity.Second: nextIntervalTime = new DateTime( currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, currentDateTime.Hour, currentDateTime.Minute, currentDateTime.Second ).AddSeconds(1); graceTime = 500; break; case GroupedObservationEnum.Regularity.Minute: nextIntervalTime = new DateTime( currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, currentDateTime.Hour, currentDateTime.Minute, 0 ).AddMinutes(1); graceTime = 30000; break; case GroupedObservationEnum.Regularity.Day: nextIntervalTime = new DateTime( currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, 0, 0, 0 ).AddDays(1); graceTime = 60000; break; default: nextIntervalTime = new DateTime( currentDateTime.Year, currentDateTime.Month, currentDateTime.Day, currentDateTime.Hour, 0, 0 ).AddHours(1); graceTime = 30000; break; } var timeSpanToNextInterval = nextIntervalTime - currentDateTime; Log.Information( "Obs {Obs}, currentDateTime: {CurrentDateTime}, nextIntervalTime: {NextIntervalTime}, timeSpanToNextInterval: {TimeSpanToNextInterval}, timeSpanToNextInterval.TotalMilliseconds+graceTime: {Tt}", Group, currentDateTime, nextIntervalTime, timeSpanToNextInterval, (int)timeSpanToNextInterval.TotalMilliseconds + graceTime); return (int)timeSpanToNextInterval.TotalMilliseconds + graceTime; } } public static class WsSubscriberExtension { public static bool Compare(this WsSubscriberGrouped source, GroupedField r, ObjectId patientId) { return r.Regularity == source.Regularity && r.Since == source.Since && CompareStringShiftList(r.StartTimeShift, source.StartTimeShift) && r.Max == source.Max && r.Result.SequenceEqual(source.Result) && patientId.ToString() == source.PatientId.ToString() && CompareNamesStringList(r.Names, [r.Name ?? string.Empty], source.Names); } private static bool CompareStringShiftList(List? shift, List? sourceShift) { if (shift != null && sourceShift != null) return sourceShift.SequenceEqual(shift); if (shift == null && sourceShift == null) return true; return false; } private static bool CompareNamesStringList(List names, List name, List sourceNames) { if (!CollectionsUtils.IsEmptyOrNull(names) && sourceNames.SequenceEqual(names)) return true; return !CollectionsUtils.IsEmptyOrNull(name) && sourceNames.SequenceEqual(name); } /// /// Check if the incoming observation affect to the group and is new info or is irrelevant /// /// public static bool IsNewObservationRelevantForGroup(this WsSubscriberGrouped group, PatientObservation obs) { double.TryParse(obs.Value.ToString(), out var pobsValue); switch (group.Regularity) { case GroupedObservationEnum.Regularity.Shift: case GroupedObservationEnum.Regularity.Hour: var obsInThisHour = group.LastGroupedObservationObs.Where(c => new DateTime(c.Time.Year, c.Time.Month, c.Time.Day, c.Time.Hour, 0, 0) == new DateTime(obs.Time.Year, obs.Time.Month, obs.Time.Day, obs.Time.Hour, 0, 0) && !c.IsFilled); //Obs is in grouped time, don't generate group for observation outside of range //Change to don't cut is obs time is a future var obsInTime = DateTime.UtcNow.AddHours(group.Max) > obs.Time && DateTime.UtcNow.AddHours(group.Max * -1) < obs.Time; //future data is relevant because maybe nothing is inserted in the same hour after it and we cant lose it /* if (DateTime.UtcNow.AddHours(group.Max) > obs.time) { } */ if (!obsInTime) return false; var groupedObservationObsEnumerable = obsInThisHour.ToList(); if (!groupedObservationObsEnumerable.Any() && obsInTime) return true; //If obs in this hour check if is relevant by Result if (groupedObservationObsEnumerable.Any()) foreach (var result in group.Result) switch (result) { case GroupedObservationEnum.Result.Max: var value = groupedObservationObsEnumerable.Max(s => s.Max?.Value); var parsed = double.TryParse(value?.ToString(), out var valueParsed); //actual value is higher, is relevant if (parsed && pobsValue > valueParsed) return true; break; case GroupedObservationEnum.Result.Min: var minValue = groupedObservationObsEnumerable.Min(s => s.Min?.Value); var isParsed = double.TryParse(minValue?.ToString(), out var minValueParsed); //actual value is higher, is relevant if (isParsed && pobsValue < minValueParsed) return true; break; case GroupedObservationEnum.Result.Last: var lastObsInTime = groupedObservationObsEnumerable.OrderBy(t => t.Last?.Time).First(); //is the new latest in the hour, relevant if (lastObsInTime.Time < obs.Time) return true; break; case GroupedObservationEnum.Result.LastFilled: var lastFilledObsInTime = groupedObservationObsEnumerable .OrderBy(t => t.LastFilled?.Time).First(); //is the new latest in the hour, relevant if (lastFilledObsInTime.Time < obs.Time) return true; break; //always is relevant because SUM everything in the hour //TODO halfHour should check if any obs already in the halfHour case GroupedObservationEnum.Result.Sum: case GroupedObservationEnum.Result.Average: case GroupedObservationEnum.Result.HalfHour: return true; } return false; default: return true; } } }