969 lines
38 KiB
C#
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 |