Files
adas-core/adas-core.Application/Services/AlarmService.cs
T

969 lines
38 KiB
C#

using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Serilog;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services;
public class AlarmService : IAlarmService
{
private readonly IAlarmRepository _alarmRepository;
private readonly IOptions<ApiSettings> _apiSettings;
private readonly ILocalAuditService _auditService;
private readonly Lazy<ICalculatedObservationsService> _calculatedObservationsService;
private readonly IClientMessageService _clientMessageService;
private readonly IConfigObservationService _configObservationService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Lazy<ILightBeaconService> _lightBeaconService;
private readonly ILogger<AlarmService> _logger;
private readonly Lazy<IObservationService> _observationService;
private readonly IPatientService _patientService;
private readonly IPointOfCareService _pocService;
private readonly Lazy<IRecordingService> _recordingService;
private readonly List<PatientObservation> _relayAlarmList = [];
private readonly Lazy<IRelayService> _relayService;
private readonly SemaphoreSlim
_semaphore = new(1, 1); // Semáforo para evitar la ejecución simultánea del temporizador
private readonly ISubscribersService _subscribersService;
private readonly IUnitService _unitService;
//private readonly string _url;
private List<PatientObservation> _beaconAlarmList = [];
private TimeSpan _interval;
public AlarmService(IAlarmRepository alarmRepository,
ILogger<AlarmService> logger,
IPatientService patientService,
IConfigObservationService configObservationService,
Lazy<IObservationService> observationService,
IClientMessageService clientMessageService,
ISubscribersService subscribersService,
Lazy<ICalculatedObservationsService> calculatedObservationsService,
Lazy<ILightBeaconService> lightBeaconService,
Lazy<IRecordingService> recordingService,
Lazy<IRelayService> relayService,
IOptions<ApiSettings> apiSettings,
IUnitService unitService,
IPointOfCareService pocService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
bool startTimer = true
)
{
_alarmRepository = alarmRepository;
_logger = logger;
_patientService = patientService;
_configObservationService = configObservationService;
_observationService = observationService;
_clientMessageService = clientMessageService;
_subscribersService = subscribersService;
_calculatedObservationsService = calculatedObservationsService;
_lightBeaconService = lightBeaconService;
_recordingService = recordingService;
_relayService = relayService;
_apiSettings = apiSettings;
_unitService = unitService;
_pocService = pocService;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
if (apiSettings == null) throw new Exception("ApiSettings must be defined");
if (startTimer) StartTimer();
}
public async Task SaveRequest(ApiRequest apiRequest)
{
if (
string.IsNullOrEmpty(apiRequest.PatientNumber) &&
string.IsNullOrEmpty(apiRequest.Location?.UnitName)
)
{
_logger.LogDebug("Patient and PointOfCare are nulls");
throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
}
var patient = await _patientService.FindPatientByApiRequest(apiRequest);
if (patient == null)
{
// NO PATIENTS OR LOCATIONS WERE FOUND
_logger.LogWarning(
"Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
apiRequest.PatientNumber, apiRequest.Location);
return;
}
var unitConfig = await _unitService.FindById(patient.UnitId);
if (!Hl7Utils.ManageAutoAdt(unitConfig, null, _logger, "ORU Alarm")) return;
switch (apiRequest.Type)
{
/*
* ORU_R40 - Unsolicited transmission of an alert observation message
*/
case "ORU_R40": // UNSOLICITED ALERT OBSERVATION
// OBSERVATIONS
if (apiRequest.Observation != null && apiRequest.Observations?.FirstOrDefault() == null)
apiRequest.Observations = [apiRequest.Observation];
//var obrcode = apiRequest.ObservationData?.Code ?? "";
if (!apiRequest.Alarms.IsNullOrEmpty())
await ProcessAlarmObservations(apiRequest.Alarms ?? [], apiRequest.Observations ?? [],
patient, apiRequest.ObservationData?.Time ?? apiRequest.MessageTime,
apiRequest.ObservationData);
break;
default:
_logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations",
apiRequest.Type);
throw new ApiRequestException("ApiRequest type " + apiRequest.Type +
" is not valid for Observations");
}
}
public async Task SaveRequestAsync(ApiRequest apiRequest)
{
await SaveRequest(apiRequest);
}
public async Task<PatientObservationAlarm?> MapObservation(PatientObservationAlarm obs, bool onlyByName = false)
{
try
{
_logger.LogTrace("Mapping config Observation obs: {obs} onlyByName: {onlyByName}", obs, onlyByName);
var obs2 = await _configObservationService.Map(obs, onlyByName);
if (obs2 == null)
{
_logger.LogTrace("Mapping obs2 {obs}: Ignored", obs);
return null;
}
_logger.LogTrace("Mapping _configObservationService.Map obs2: {obs2}", obs2);
var obs3 = await _calculatedObservationsService.Value.Map(obs2, onlyByName);
if (obs3 == null)
{
_logger.LogTrace("Mapping obs3 {obs2}: Ignored", obs2);
return null;
}
return obs3;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Mapping Observation, Ignoring Observation: {obs} Exception:{ex}", obs,
ex.Message);
return null;
}
}
public async Task<List<PatientObservationAlarm>> FindLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null)
{
var result =
await _alarmRepository.AggregatedPatientLastObservationsByField(patientId, filterObservations);
return result;
}
public async Task<List<PatientObservationAlarm>> FindLastValuesNotExpired(ObjectId patientId,
List<Field> filterObservations, List<ConfigObservation> configAlarm)
{
var result =
await _alarmRepository.AggregatedPatientNotExpiredObservationsByField(patientId, filterObservations,
configAlarm);
return result;
}
public async Task<PatientObservationAlarm?> MapObservationsByName(PatientObservationAlarm obs)
{
return await _configObservationService.Map(obs, true);
}
public async Task ProcessAlarmObservations(List<PatientObservationAlarm> alarmObservations,
List<PatientObservation> observations, Patient patient,
DateTime messageTime, ObservationData? observationData = null)
{
_logger.LogDebug(
"Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING {observationsCount} OBSERVATIONS",
patient.Id, patient.UnitId, patient.PointOfCareId, messageTime, alarmObservations.Count);
ParentDataClass? parentData = null;
if (observationData != null)
parentData = new ParentDataClass
{
Code = observationData.Code,
CodingSystem = observationData.CodingSystem,
Name = observationData.Text
};
var listToInsert = new List<PatientObservationAlarm>();
foreach (var obs in alarmObservations)
{
obs.ParentData = parentData;
obs.MessageTime = messageTime;
obs.PatientId = patient.Id;
obs.Patient = patient;
obs.Id = ObjectId.GenerateNewId();
var intObsTime = new DateTimeOffset(obs.Time).ToUnixTimeSeconds();
if (obs.Time == DateTime.MinValue || intObsTime <= 10)
obs.Time = DateTime.UtcNow;
var intMessageTime = new DateTimeOffset(obs.MessageTime).ToUnixTimeSeconds();
if (obs.MessageTime == DateTime.MinValue || intMessageTime <= 10)
obs.MessageTime = DateTime.UtcNow;
if (obs.Value.ToString() == "System.Object")
{
obs.Value = obs.Event?.ToString()??string.Empty;
}
_logger.LogDebug(
"Patient: {patientId} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING ALARM OBSERAVTION {obs}",
patient.Id, patient.PointOfCare, patient.Bed, messageTime, obs);
listToInsert.Add(obs);
}
//listToInsert.ForEach(async obs => await InsertObservation(obs));
foreach (var alarmToInsert in listToInsert)
{
var alarmData =
observations.FirstOrDefault(obs => obs.Value.ToString() == alarmToInsert.Value.ToString());
if (alarmData != null)
{
alarmToInsert.Code = alarmData.Code;
alarmToInsert.Name = alarmData.Code;
alarmToInsert.CodingSystem = alarmData.CodingSystem;
}
else
{
alarmToInsert.CodingSystem = parentData?.CodingSystem?? "MDIL-ALARM";
alarmToInsert.Code = alarmToInsert.Priority.ToString();
alarmToInsert.Name = alarmToInsert.Priority switch
{
AlarmEnum.ObservationAlarmPriority.Ph => "RedAlarm_Ph",
AlarmEnum.ObservationAlarmPriority.Pm => "YellowAlarm_Pm",
AlarmEnum.ObservationAlarmPriority.Pl => "BlueAlarm_Pl",
_ => "",
};
}
if (!alarmToInsert.Sources.IsNullOrEmpty())
{
var apiRequestObs = new ApiRequest
{
Type = "ORU_R01",
MessageTime = messageTime,
ObservationData = observationData
};
var obsToInsert = new List<PatientObservation>();
alarmToInsert.Sources?.ForEach(async void (c) =>
{
try
{
var obs = new PatientObservation
{
Code = c.Code,
Name = c.OriginalName,
CodingSystem = c.CodeSystem,
Units = c.Units,
Value = c.Value?.ToString() ?? "No value",
Time = alarmToInsert.Time,
Result = c.Result
};
var obs2 = await _calculatedObservationsService.Value
.MapSourceAlarm(obs, alarmToInsert);
obsToInsert.Add(obs2);
}
catch (Exception e)
{
_logger.LogError(
"Error processing source observation {source} for alarm {alarm}. Exception: {ex}",
c, alarmToInsert, e);
}
});
apiRequestObs.Observations = obsToInsert;
apiRequestObs.Location = patient.Location;
apiRequestObs.Patient = patient.Person;
apiRequestObs.PatientNumber = patient.PatientNumber;
await _observationService.Value.SaveRequestAsync(apiRequestObs);
}
await InsertObservation(alarmToInsert);
}
}
private async Task InsertObservation(PatientObservationAlarm obs, bool persistObs = true, bool mapObs = true)
{
try
{
var obs2 = obs;
//only will be false if the obs comes from the inner refactor job
if (mapObs) obs2 = await MapObservation(obs2, onlyByName: true);
if (obs2 == null)
{
_logger.LogDebug("Mapped observation returns null. Ignored {obs}", obs);
}
else
{
if (obs2.Persist.HasValue && !obs2.Persist.Value) persistObs = false;
if (persistObs)
{
_logger.LogDebug("Mapped {obs2}", obs2);
await _alarmRepository.InsertOneAsync(obs2);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2);
}
_logger.LogDebug("Inserted {obs2}", obs2);
await SendObsBroadcast(obs2);
}
}
catch (Exception ex)
{
_logger.LogError("Error Inserting observation {obs}. Excepcion; {ex} ", obs, ex);
}
}
private async Task SendObsBroadcast(BasePatientObservation obs)
{
if (obs.Name == null) return;
const OperationType type = OperationType.Alarm;
var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId);
if (patient == null)
{
_logger.LogDebug("Not patient on bd to sendOnBroadcastObs: {obspatientid}", obs.PatientId);
return;
}
_logger.LogTrace(
"sending obs name: {obsname} to patient id: {patientid}, PointOfCare: {patientpointOfCare} {patientbed}",
obs.Name, patient.Id, patient.UnitId, patient.Bed);
var subscribers = _subscribersService.GetSubscribers().Where(s =>
!s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
c == patient.PointOfCareId
)).ToList();
foreach (var subscriber in subscribers)
{
_logger.LogTrace("sending obs name: {obsname} to subscriber id: {subscriberId}", obs.Name,
subscriber.Id);
await _clientMessageService.SendAsync(subscriber.Id, type, obs);
}
}
#region activación de alarmas con balizas, relé y grabaciones
public async Task CheckObservationAlarm(PatientObservation obs)
{
//ConfigObservations
var configs = await _configObservationService.Get(new PatientObservation
{
Name = obs.Name,
PatientId = obs.PatientId
}
);
if (configs?.CreateObservation == null)
return;
var obsValue = obs.Value.ToString() ?? string.Empty;
var observationsToCreate = configs.CreateObservation
.Where(c => obsValue.ToUpper().Contains(c.RequiredValue?.ToString()?.ToUpper() ?? string.Empty))
.ToList();
foreach (var obsConfig in observationsToCreate)
{
var create = false;
if (obsConfig.Preconditions == null)
create = true;
else
foreach (var preCondition in obsConfig.Preconditions)
{
if (preCondition.Name == null)
continue;
var obsWithConditions =
await _observationService.Value.FindLastObservations(obs.PatientId, 1,
[preCondition.Name]);
if (!obsWithConditions.Any())
continue;
var foundObs = obsWithConditions.FirstOrDefault();
//Descartamos la observación si ha expirado
if (foundObs == null || (obsConfig.Expires.HasValue &&
foundObs.Time.AddSeconds(obsConfig.Expires.Value) < DateTime.UtcNow))
continue;
var foundObsStr = foundObs.Value.ToString();
var requiredValueStr = preCondition.RequiredValue?.ToString();
if (string.IsNullOrEmpty(requiredValueStr) ||
(foundObsStr != null && foundObsStr.Contains(requiredValueStr)))
{
create = true;
break;
}
}
if (create)
{
var newObservation = CreateNewObservation(obs, obsConfig, StatusEnum.Type.Alert);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, newObservation);
newObservation = await CheckAlarmConfig(newObservation);
var obsName = newObservation.Name ?? string.Empty;
_ = SendAlarm(newObservation, obsName, null, AlarmEnum.Severity.None, AlarmEnum.Type.Auto);
_ = _observationService.Value.InsertObservation(newObservation);
}
}
}
private static PatientObservation CreateNewObservation(PatientObservation obs, ConfigObservation config,
StatusEnum.Type type)
{
return new PatientObservation
{
CodingSystem = config.CodingSystem,
Code = config.Code,
Name = config.Name,
Value = obs.Value,
PatientId = obs.PatientId,
Time = obs.Time,
Alarm = config.Alarm,
Status = type
};
}
private async Task<PatientObservation> CheckAlarmConfig(PatientObservation pobs)
{
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = pobs.Name,
PatientId = pobs.PatientId
});
pobs.Alarm = configObs?.Alarm ?? null;
return pobs;
}
/// <summary>
/// Sends a new alarm
/// </summary>
/// <param name="obs">Observation to generate the alarm</param>
/// <param name="name">Name of the alarm</param>
/// <param name="code">Code of the alarm for the recording</param>
/// <param name="severity">Severity of the alarm for the recording</param>
/// <param name="type"></param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <returns>New alarm created</returns>
public async Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code, AlarmEnum.Severity severity,
AlarmEnum.Type type)
{
try
{
var poc = await _pocService.FindPoCByPatientId(obs.PatientId);
switch (obs.Time.Kind)
{
// Convert obs.Time to UTC if it's not already
case DateTimeKind.Local:
obs.Time = obs.Time.ToUniversalTime();
break;
case DateTimeKind.Unspecified:
_logger.LogWarning("obs.Time has unspecified kind. Assuming it to be UTC.");
obs.Time = DateTime.SpecifyKind(obs.Time, DateTimeKind.Utc);
break;
case DateTimeKind.Utc:
break;
default:
throw new ArgumentOutOfRangeException();
}
//ConfigObservations
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = obs.Name,
PatientId = obs.PatientId
}
);
if (configObs == null)
return;
if (configObs is { Alarm.Enabled: true })
{
obs.Alarm = configObs.Alarm;
var now = DateTime.UtcNow;
if (obs.Expired || (obs.Expires.HasValue && obs.Time.AddSeconds(obs.Expires.Value) < now))
return;
//En la prioridad de las alarmas 1 máxima prioridad
if (configObs.Alarm.Beacon is { Enabled: true })
try
{
if (obs.Time.AddSeconds(configObs.Alarm.Beacon.EndAfter) >= now)
{
var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId);
if (patient != null)
{
_logger.LogDebug("PatientId: {nObsPatientid}. Send Beacon alarmName {beaconColor}",
obs.PatientId, configObs.Alarm.Beacon.BeaconColor);
if (!_beaconAlarmList.Any(o =>
o.PatientId == obs.PatientId && o.Alarm?.Priority < obs.Alarm.Priority))
{
_ = SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient);
lock (_beaconAlarmList)
{
_beaconAlarmList.Add(obs);
}
}
}
}
else
{
_logger.LogDebug(
"PatientId: {nObsPatientid}.Beacon is Expired. EndAfter {endAfter} Time: {obsTime}",
obs.PatientId, configObs.Alarm.Beacon.EndAfter, obs.Time);
}
}
catch (Exception ex)
{
_logger.LogDebug("Exception sending beacon code for patient {patientId}. Exception: {ex}",
obs.PatientId, ex);
throw;
}
if (configObs.Alarm.Recording is { Enabled: true })
try
{
if (obs.Time.AddSeconds(configObs.Alarm.Recording.EndAfter) >= now)
{
//if(severity == AlarmSeverity.NONE)
severity = configObs.Alarm.Recording.Severity;
var strValue = obs.Value.ToString();
if (strValue == null)
{
_logger.LogError("Observation value to string is null observation:{nObs}", obs);
return;
}
_logger.LogDebug("PatientId: {nObsPatientid}. Send Recording {nObsName}", obs.PatientId,
obs.Name);
if (code == null && Enum.TryParse<AlarmEnum.Name>(configObs.Alarm.Name, out var result))
code = result;
_ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, code, severity,
strValue, configObs.Alarm.Recording.EndAfter, type);
}
else
{
_logger.LogDebug(
"PatientId: {nObsPatientid}.Recording is Expired. EndAfter {endAfter} Time: {obsTime}",
obs.PatientId, configObs.Alarm.Recording.EndAfter, obs.Time);
}
}
catch (Exception ex)
{
_logger.LogDebug(
"Exception sending Alarm Recording for patient {patientId}. Exception: {ex}",
obs.PatientId, ex);
throw;
}
if (configObs.Alarm.OpenDoor is { Enabled: true })
try
{
if (obs.Time.AddSeconds(configObs.Alarm.OpenDoor.EndAfter) >= now)
{
_logger.LogDebug("PatientId: {nObsPatientid}. Open door observation {obsName}",
obs.PatientId, obs.Name);
lock (_relayAlarmList)
{
if (!_relayAlarmList.Any(o =>
o.PatientId == obs.PatientId && o.Alarm?.Priority < obs.Alarm.Priority))
{
PointOfCareConfiguration? poCSettings = null;
if (poc is { Configuration.RelayIdList: not null })
poCSettings = poc.Configuration;
var status = _relayService.Value.GetRelayByTypeInList(poCSettings?.RelayIdList,
RelayEnum.Type.Door).FirstOrDefault()?.ManualRelayStatus;
if (status is RelayEnum.Status.Off)
_ = RelayPowerOn(obs.PatientId, RelayEnum.Type.Door);
lock (_relayAlarmList)
{
_relayAlarmList.Add(obs);
}
}
}
}
else
{
_logger.LogDebug(
"PatientId: {nObsPatientid}.Open door is Expired. EndAfter {endAfter} Time: {obsTime}",
obs.PatientId, configObs.Alarm.OpenDoor.EndAfter, obs.Time);
}
}
catch (Exception ex)
{
_logger.LogDebug("Exception opening door for patient {patientId}. Exception: {ex}",
obs.PatientId, ex);
throw;
}
}
}
catch (Exception ex)
{
Log.Error("Exception sending alarm: {exMessage}", ex.Message);
}
}
public async Task CalculateAlarmTest(BasePatientObservationValue source, string name)
{
if (source is not PatientObservation obs) return;
//ConfigObservations
var configObs = await _configObservationService.Get(source, true);
if (configObs is { Alarm.Enabled: true })
{
obs.Alarm = configObs.Alarm;
string? alarmSeverityStr = null;
if (configObs.Alarm.Beacon is { Enabled: true })
{
var patient = await _patientService.FindById(obs.PatientId);
if (patient == null)
return;
_logger.LogDebug("PatientId: {nObsPatientid}. Send Beacon alarmName {beaconColor}",
obs.PatientId, configObs.Alarm.Beacon.BeaconColor);
_ = SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient);
}
if (configObs.Alarm.Recording is { Enabled: true })
{
_logger.LogDebug("PatientId: {nObsPatientid}. Send Recording {nObsName}", obs.PatientId,
obs.Name);
if (!string.IsNullOrEmpty(alarmSeverityStr) &&
Enum.TryParse(alarmSeverityStr, out AlarmEnum.Severity alarmSeverity))
_ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, AlarmEnum.Name.Test,
alarmSeverity, "test description", configObs.Alarm.Recording.EndAfter);
else
_ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, AlarmEnum.Name.Test,
AlarmEnum.Severity.Yellow, "test description", configObs.Alarm.Recording.EndAfter);
}
if (configObs.Alarm.OpenDoor is { Enabled: true })
{
_logger.LogDebug("PatientId: {nObsPatientid}. Open door", obs.PatientId);
_ = RelayPowerOn(obs.PatientId, RelayEnum.Type.Door);
}
}
}
private Task SendBeaconCode(AlarmEnum.BeaconColor color, Patient patient)
{
if (!patient.PointOfCareId.HasValue)
{
_logger.LogError("Try to sen beacon code, but no PointOfCareId id is present in the Patient {Patient}",
patient.ToString());
return Task.CompletedTask;
}
switch (color)
{
case AlarmEnum.BeaconColor.Blue:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Blue);
break;
case AlarmEnum.BeaconColor.Yellow:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Yellow);
break;
case AlarmEnum.BeaconColor.Red:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Red);
break;
case AlarmEnum.BeaconColor.None:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Off);
break;
}
return Task.CompletedTask;
}
/// <summary>
/// </summary>
/// <param name="patientId"></param>
/// <param name="eventTime">Hora de la observación</param>
/// <param name="recording"></param>
/// <param name="alarmName"></param>
/// <param name="alarmDescription"></param>
/// <param name="endAfter"></param>
/// <param name="severity"></param>
/// <param name="type"></param>
private async Task StartRecording(ObjectId patientId, DateTime eventTime, AlarmItem? recording,
AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string? alarmDescription, int? endAfter,
AlarmEnum.Type type = AlarmEnum.Type.Manual)
{
try
{
var patient = await _patientService.FindById(patientId);
if (patient is not { PointOfCareId: not null }) return;
var poc = await _pocService.FindByIdAllConfig(patient.PointOfCareId.Value);
if (poc == null)
return;
//30 minutos antes y después de la fecha de la observación
var startTime = recording != null ? eventTime.AddSeconds(-recording.StartBefore) : eventTime;
var endDate = endAfter.HasValue ? eventTime.AddSeconds(endAfter.Value) : (DateTime?)null;
await _recordingService.Value.SendRecordingDataToQueue(patient, poc, startTime, endDate,
eventTime, alarmName, severity, alarmDescription, true, type);
}
catch (Exception ex)
{
_logger.LogError(
"Error Starting recording from patientId: {patientId}. eventTime: {eventTime}. AlarmItem: {recording}. alarmSeverity: {alarmSeverity} Error: {ex}",
patientId, eventTime, recording, severity, ex);
}
}
private async Task RelayPowerOn(ObjectId patientId, RelayEnum.Type type)
{
try
{
var patient = await _patientService.FindById(patientId);
if (patient is not { PointOfCareId: not null })
{
Log.Error("can not power on relay because patient: {patientId} not found", patientId);
return;
}
var poc = await _pocService.FindById(patient.PointOfCareId.Value);
if (poc?.Configuration == null) return;
var relayConfig = _relayService.Value.GetRelayByTypeInList(poc.Configuration.RelayIdList, type).FirstOrDefault();
if (relayConfig != null) await _relayService.Value.PowerOn(relayConfig);
}
catch (Exception ex)
{
_logger.LogError("Error Relay Power On from patientId: {patientId}. Error: {ex}", patientId, ex);
}
}
private async void StartTimer()
{
try
{
var pocList = await _pocService.GetAllLocationInfo();
_interval = TimeSpan.FromSeconds(_apiSettings.Value.ExpireAlertIntervalSeconds);
_ = new Timer(async void (_) =>
{
try
{
await _semaphore.WaitAsync(); // Esperar a adquirir el semáforo antes de ejecutar el temporizador
await CheckExpiredAlarms(pocList);
}
catch (Exception e)
{
_logger.LogError("Error in timer execution: {message}", e.Message);
//throw new Exception("Error in timer execution", e);
}
finally
{
_semaphore.Release(); // Liberar el semáforo después de ejecutar el temporizador
}
}, null, TimeSpan.Zero, _interval);
}
catch (Exception e)
{
_logger.LogError("Error starting timer: {message}", e.Message);
//throw new Exception("Error starting timer", e);
}
}
private async Task CheckExpiredAlarms(List<PointOfCare> pocList)
{
_logger.LogTrace("Checking Expired Alarms Started");
// Iniciar ambas tareas de forma asincrónica
var checkBeaconsTask = CheckExpiredBeaconsAsync(pocList);
var checkRelayTask = CheckExpiredRelayAsync();
// Esperar a que ambas tareas completen
await Task.WhenAll(checkBeaconsTask, checkRelayTask);
_logger.LogTrace("Checking Expired Alarms Finished");
}
private readonly SemaphoreSlim _beaconListSemaphore = new(1, 1);
private async Task CheckExpiredBeaconsAsync(List<PointOfCare> pocList)
{
await _beaconListSemaphore.WaitAsync();
try
{
if (!_beaconAlarmList.Any())
//var pocList = await _pocService.GetAllLocationInfo();
if (pocList.Any())
{
var tasks = pocList.Select(async poc =>
{
await _lightBeaconService.Value.SendColor(poc, LightBeaconColor.Off);
});
await Task.WhenAll(tasks);
return;
}
}
finally
{
_beaconListSemaphore.Release();
}
var now = DateTime.UtcNow;
List<PatientObservation> updatedList = [];
List<Task> ledTasks = [];
await _beaconListSemaphore.WaitAsync();
try
{
foreach (var obsGroup in _beaconAlarmList.GroupBy(o => o.PatientId))
{
var nonExpiredObs = obsGroup.Where(obs =>
obs.Alarm is { Beacon: not null } &&
(obs.Time.Kind == DateTimeKind.Utc ? obs.Time : obs.Time.ToUniversalTime())
.AddSeconds(obs.Alarm.Beacon.EndAfter) >= now
).ToList();
if (!nonExpiredObs.Any())
{
var patient = obsGroup.FirstOrDefault()?.Patient;
if (patient is { PointOfCareId: not null })
ledTasks.Add(_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value,
LightBeaconColor.Off));
}
else
{
updatedList.AddRange(nonExpiredObs);
}
}
_beaconAlarmList = updatedList;
}
finally
{
_beaconListSemaphore.Release();
}
await Task.WhenAll(ledTasks);
}
private async Task CheckExpiredRelayAsync()
{
var now = DateTime.UtcNow;
IEnumerable<IGrouping<ObjectId, PatientObservation>> groupedByPatientId;
lock (_relayAlarmList)
{
groupedByPatientId = _relayAlarmList.GroupBy(o => o.PatientId);
}
foreach (var obsGroup in groupedByPatientId)
{
var patient = await _patientService.FindById(obsGroup.Key);
if (patient is not { PointOfCareId: null })
continue;
var nonExpiredObs = obsGroup.Where(obs =>
obs.Alarm is { OpenDoor: not null } &&
(obs.Time.Kind == DateTimeKind.Utc ? obs.Time : obs.Time.ToUniversalTime()).AddSeconds(
obs.Alarm.OpenDoor.EndAfter) >= now
).ToList();
// Apagar el LED si no hay observaciones no expiradas
if (!nonExpiredObs.Any())
{
//buscamos en PoCSettings si está activado de forma manual
var pocSettings = await _pocService.FindById(patient.PointOfCareId!.Value);
var relays = _relayService.Value.GetRelayInList(pocSettings?.Configuration?.RelayIdList);
foreach (var relay in relays)
{
// Verificamos si NO tiene un estado manual activo (On)
// Si el estado es null o es diferente de On, lo apagamos
if (relay.ManualRelayStatus != null && relay.ManualRelayStatus != RelayEnum.Status.On)
{
await _relayService.Value.PowerOff(relay);
}
}
}
// Reemplazar la lista original con las observaciones no expiradas
lock (_relayAlarmList)
{
_relayAlarmList.RemoveAll(obs => obs.PatientId == obsGroup.Key);
_relayAlarmList.AddRange(nonExpiredObs);
}
}
}
}
#endregion