1568 lines
58 KiB
C#
1568 lines
58 KiB
C#
using adas_core.Application.Exceptions;
|
|
using adas_core.Application.Repositories.Interfaces;
|
|
using adas_core.Application.Services.Interfaces;
|
|
using adas_core.Application.Subscriptions;
|
|
using adas_core.Domain.Enums;
|
|
using adas_core.Domain.Models;
|
|
using adas_core.Domain.Models.AppSettings;
|
|
using adas_core.Domain.Models.Filter;
|
|
using adas_core.Domain.Models.GroupedObservations;
|
|
using adas_core.Domain.Models.MongoModels;
|
|
using adas_core.Domain.Models.Responses;
|
|
using adas_core.Domain.Utils;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using Serilog;
|
|
using Patient = adas_core.Domain.Models.MongoModels.Patient;
|
|
|
|
namespace adas_core.Application.Services;
|
|
|
|
public class ObservationService : IObservationService
|
|
{
|
|
private readonly ICacheService _cacheService;
|
|
private readonly CacheSettings? _cacheSettings;
|
|
|
|
private readonly IAlarmService _alarmService;
|
|
private readonly List<string> _allergies;
|
|
private readonly ILocalAuditService _auditService;
|
|
private readonly Lazy<ICalculatedObservationsService> _calculatedObservationsService;
|
|
private readonly IClientMessageService _clientMessageService;
|
|
private readonly IConfigObservationService _configObservationService;
|
|
private readonly IConfigUnitsService _configUnitsService;
|
|
private readonly List<string> _diagnosisCode;
|
|
private readonly IDiagnosisService _diagnosisService;
|
|
private readonly List<string> _drainageCode;
|
|
private readonly IGroupedObservationService _groupedObservationService;
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
private readonly List<string> _intravenousLines;
|
|
private readonly List<string> _isolationCode;
|
|
private readonly ILightBeaconService _lightBeaconService;
|
|
private readonly ILogger<ObservationService> _logger;
|
|
private readonly IObservationArchiveRepository _observationArchiveRepository;
|
|
private readonly IObservationRepository _observationRepository;
|
|
private readonly IPatientService _patientService;
|
|
|
|
//Suprimo el warning porque detecta que no se usa en el código, pero sí se usa en los test
|
|
//#pragma warning disable IDE0051 // Quitar miembros privados no utilizados
|
|
//#pragma warning disable CS0169 // El campo '_observationService.createPatientWithORU' nunca se usa
|
|
/*private readonly bool createPatientWithORU;
|
|
private readonly bool createPatientWithoutLocation;
|
|
|
|
private readonly bool createPatientWithLocation;
|
|
|
|
//private readonly bool updatePatientLocationWithORU;
|
|
private readonly bool updatePatientDataWithORU;
|
|
private readonly bool ArchivePatientWithOru;*/
|
|
//#pragma warning restore CS0169 // El campo '_observationService.createPatientWithORU' nunca se usa
|
|
//#pragma warning restore IDE0051 // Quitar miembros privados no utilizados
|
|
|
|
|
|
private readonly bool _persistObservationCodes;
|
|
private readonly IPointOfCareService _pointOfCareService;
|
|
private readonly List<string> _positionCode;
|
|
private readonly IRecordingService _recordingService;
|
|
private readonly IRelayService _relayService;
|
|
private readonly ISubscriberGroupedService _subscriberGroupedService;
|
|
private readonly ISubscribersService _subscribersService;
|
|
|
|
|
|
public ObservationService(
|
|
IPatientService patientService,
|
|
IConfigObservationService configObservationService,
|
|
IObservationRepository observationRepository,
|
|
IObservationArchiveRepository observationArchiveRepository,
|
|
IConfigUnitsService configUnitsService,
|
|
IDiagnosisService diagnosisService,
|
|
IOptions<ApiSettings> apiSettings,
|
|
IOptions<CacheSettings> cacheSettings,
|
|
ILightBeaconService lightBeaconService,
|
|
IRelayService relayService,
|
|
IRecordingService recordingService,
|
|
ILogger<ObservationService> logger,
|
|
IGroupedObservationService groupedObservationService,
|
|
IAlarmService alarmService,
|
|
IClientMessageService clientMessageService,
|
|
ISubscribersService subscribersService,
|
|
ISubscriberGroupedService subscriberGroupedService,
|
|
Lazy<ICalculatedObservationsService> calculatedObservationsService,
|
|
IHttpContextAccessor httpContextAccessor,
|
|
ILocalAuditService auditService,
|
|
IPointOfCareService pointOfCareService,
|
|
ICacheService cacheService
|
|
)
|
|
|
|
{
|
|
_patientService = patientService;
|
|
_configObservationService = configObservationService;
|
|
_observationRepository = observationRepository;
|
|
_observationArchiveRepository = observationArchiveRepository;
|
|
_configUnitsService = configUnitsService;
|
|
_diagnosisService = diagnosisService;
|
|
_lightBeaconService = lightBeaconService;
|
|
_relayService = relayService;
|
|
_recordingService = recordingService;
|
|
_httpContextAccessor = httpContextAccessor;
|
|
_auditService = auditService;
|
|
_groupedObservationService = groupedObservationService;
|
|
_cacheService = cacheService;
|
|
|
|
|
|
_logger = logger;
|
|
|
|
if (apiSettings == null) throw new Exception("ApiSettings must be defined");
|
|
|
|
|
|
_persistObservationCodes = apiSettings.Value.PersistObservationCodes;
|
|
_intravenousLines = apiSettings.Value.IntravenousLinesCode ?? [];
|
|
_allergies = apiSettings.Value.AllergiesCode;
|
|
_drainageCode = apiSettings.Value.DrainageCode;
|
|
_isolationCode = apiSettings.Value.IsolationCode;
|
|
_positionCode = apiSettings.Value.PositionCode;
|
|
_diagnosisCode = apiSettings.Value.DiagnosisCode;
|
|
_alarmService = alarmService;
|
|
_clientMessageService = clientMessageService;
|
|
_subscribersService = subscribersService;
|
|
_subscriberGroupedService = subscriberGroupedService;
|
|
_calculatedObservationsService = calculatedObservationsService;
|
|
_pointOfCareService = pointOfCareService;
|
|
_cacheSettings = cacheSettings.Value;
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> FindLastObservations(ObjectId patientId, int num = 2,
|
|
List<string>? filterObservations = null)
|
|
{
|
|
var result =
|
|
await _observationRepository.AggregatedPatientLastObservations(patientId, num, filterObservations);
|
|
return result;
|
|
}
|
|
|
|
private async Task<List<PatientObservation>> AggregatedLastObsCached(
|
|
ObjectId patientId,
|
|
List<Field>? filterObservations,
|
|
CancellationToken ct = default)
|
|
{
|
|
// Normalizar campos
|
|
var fieldNames = (filterObservations ?? new())
|
|
.Select(f => f.Name ?? string.Empty)
|
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
|
.ToList();
|
|
|
|
// Clave + TTL según PatientObservation / backend configurado
|
|
var (key, ttl) = CacheKeys.LatestObservationsKeyWithTtl(
|
|
_cacheSettings,
|
|
patientId,
|
|
fieldNames
|
|
);
|
|
|
|
// GET → MISS → LOCK → AGGREGATE → SET
|
|
var result = await _cacheService.GetOrSetObjectAsync(
|
|
key,
|
|
async () =>
|
|
{
|
|
var raw = await _observationRepository
|
|
.AggregatedPatientLastObservationsByField(patientId, filterObservations);
|
|
|
|
return raw;
|
|
},
|
|
ttl, ct);
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> FindLastObservationsByField(
|
|
ObjectId patientId,
|
|
List<Field>? filterObservations = null,
|
|
bool mapped = true,
|
|
CancellationToken ct = default)
|
|
{
|
|
//lista RAW desde la caché
|
|
var raw = await AggregatedLastObsCached(patientId, filterObservations, ct);
|
|
|
|
if (!mapped)
|
|
return raw;
|
|
|
|
// Mapeo
|
|
var mappedList = new List<PatientObservation>();
|
|
foreach (var o in raw)
|
|
{
|
|
var mo = await MapObservationsByName(o);
|
|
if (mo != null)
|
|
mappedList.Add(mo);
|
|
}
|
|
|
|
return mappedList;
|
|
}
|
|
|
|
public async Task<PatientObservation?> MapObservationsByName(PatientObservation obs)
|
|
{
|
|
return await _configObservationService.Map(obs, true);
|
|
}
|
|
|
|
public async Task<PatientObservation?> MapObservation(PatientObservation 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;
|
|
}
|
|
|
|
var obs3 = await _configUnitsService.Map(obs2);
|
|
|
|
var obs4 = await _calculatedObservationsService.Value.Map(obs3, onlyByName);
|
|
|
|
_logger.LogTrace("Mapping calculatedObservations.Map obs4: {obs4}", obs4);
|
|
|
|
if (obs4 == null)
|
|
{
|
|
_logger.LogTrace("Mapping obs4 {obs3}: Ignored", obs3);
|
|
return null;
|
|
}
|
|
|
|
if (obs4.CheckObservations)
|
|
_ = _alarmService.CheckObservationAlarm(obs4);
|
|
|
|
|
|
return obs4;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error Mapping Observation, Ignoring Observation: {obs} Exception:{ex}", obs,
|
|
ex.Message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a new observation if needed
|
|
/// </summary>
|
|
/// <param name="obs">Observation</param>
|
|
/// <param name="persistObs"></param>
|
|
/// <param name="mapObs">True if it needs to be inserted</param>
|
|
public async Task InsertObservation(PatientObservation 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);
|
|
|
|
if (obs2 == null)
|
|
{
|
|
_logger.LogDebug("Mapped observation returns null. Ignored {obs}", obs);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("Mapped {obs2}", obs2);
|
|
|
|
if (!_persistObservationCodes)
|
|
{
|
|
obs2.Code = null;
|
|
obs2.CodingSystem = null;
|
|
obs2.ParentData = null;
|
|
}
|
|
|
|
_ = SendObsBroadcast(obs2);
|
|
|
|
if (obs2.Persist.HasValue)
|
|
persistObs = obs2.Persist.Value;
|
|
|
|
_logger.LogDebug("Insert {obs}, persist is {persistObs}", obs2, persistObs);
|
|
|
|
if (persistObs)
|
|
{
|
|
await _observationRepository.InsertOneAsync(obs2);
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2);
|
|
_logger.LogDebug("Inserted {obs2}", obs2);
|
|
|
|
_ = Task.Run(() =>
|
|
CheckForGroupedObs(obs2)
|
|
);
|
|
}
|
|
|
|
_ = DoRetentionActions(obs2);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error Inserting observation {obs}. Excepcion; {ex} ", obs, ex);
|
|
}
|
|
}
|
|
|
|
public async Task InsertNurseObservation(PatientObservation obs)
|
|
{
|
|
try
|
|
{
|
|
var obs2 = obs;
|
|
|
|
obs2 = await MapObservation(obs2, true);
|
|
if (obs2 == null || obs2.Name == null)
|
|
{
|
|
_logger.LogError("Error mapping observation {obs}", obs.ToString());
|
|
return;
|
|
}
|
|
|
|
await _observationRepository.InsertOneAsync(obs2);
|
|
|
|
var (key, ttl) = CacheKeys.LatestObservationsKeyWithTtl(
|
|
_cacheSettings,
|
|
obs2.PatientId,
|
|
[obs2.Name]
|
|
);
|
|
|
|
// GET → MISS → LOCK → AGGREGATE → SET
|
|
var result = _cacheService.GetOrSetObjectAsync(
|
|
key,
|
|
async () =>
|
|
{
|
|
return obs2;
|
|
},
|
|
ttl, default);
|
|
|
|
_ = SendObsBroadcast(obs2);
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error Inserting nurse observation {obs}. Excepcion; {ex} ", obs, ex);
|
|
}
|
|
}
|
|
|
|
public async Task<bool> InsertIfChanged(string name, PatientObservation observation, bool persistObs = true,
|
|
bool mapObs = true)
|
|
{
|
|
var changedList = await FindLastObservations(observation.PatientId, 1, [name]);
|
|
var changed = changedList.All(o => observation.Value != o.Value);
|
|
|
|
if (!changed) return false;
|
|
|
|
await InsertObservation(observation, persistObs, mapObs);
|
|
return true;
|
|
}
|
|
|
|
|
|
public Task SendObsBroadcast(List<PatientObservation> obsList, PatientLocation location)
|
|
{
|
|
// Display Subscription
|
|
var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
|
|
!s.Locations.IsNullOrEmpty() && s.Locations.Any(c =>
|
|
c.UnitName == location.UnitName &&
|
|
c.Bed == location.Bed &&
|
|
c.Room == location.Room
|
|
)).ToList();
|
|
|
|
|
|
foreach (var subscriber in displaySubscribers)
|
|
foreach (var obs in obsList)
|
|
_ = _clientMessageService.SendAsync(subscriber.Id, OperationType.Observation, obs);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task SendObsBroadcast(List<PatientObservation> obsList, ObjectId pocId)
|
|
{
|
|
// Display Subscription
|
|
var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
|
|
!s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
|
|
c == pocId
|
|
)).ToList();
|
|
|
|
|
|
foreach (var subscriber in displaySubscribers)
|
|
foreach (var obs in obsList)
|
|
_ = _clientMessageService.SendAsync(subscriber.Id, OperationType.Observation, obs);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public async Task SendObsBroadcast(BasePatientObservation obs)
|
|
{
|
|
if (obs.Name == null) return;
|
|
|
|
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.UnitString, patient.Bed);
|
|
|
|
|
|
var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
|
|
!s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
|
|
c == patient.PointOfCareId
|
|
)).ToList();
|
|
|
|
|
|
foreach (var subscriber in displaySubscribers)
|
|
_ = _clientMessageService.SendAsync(subscriber.Id, OperationType.Observation, obs);
|
|
}
|
|
|
|
public async void ProcessObservations(List<PatientObservation> observations, Patient patient,
|
|
DateTime messageTime, ObservationData? observationData = null)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug(
|
|
"Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING {observationsCount} OBSERVATIONS",
|
|
patient.Id, patient.PointOfCare, patient.Bed, messageTime, observations.Count);
|
|
|
|
ParentDataClass? parentData = null;
|
|
if (observationData != null)
|
|
parentData = new ParentDataClass
|
|
{
|
|
Code = observationData.Code,
|
|
CodingSystem = observationData.CodingSystem,
|
|
Name = observationData.Text
|
|
};
|
|
|
|
var obsToInsert = new List<PatientObservation>();
|
|
// Alarm to insert
|
|
// var alrmToInsert = new List<PatientObservationAlarm>();
|
|
|
|
foreach (var obs in observations)
|
|
{
|
|
obs.ParentData = parentData;
|
|
obs.MessageTime = messageTime;
|
|
obs.PatientId = patient.Id;
|
|
obs.Patient = patient;
|
|
obs.Id = ObjectId.GenerateNewId();
|
|
|
|
if (obs.Time == DateTime.MinValue)
|
|
obs.Time = DateTime.UtcNow;
|
|
|
|
if (obs.MessageTime == DateTime.MinValue)
|
|
obs.MessageTime = DateTime.UtcNow;
|
|
|
|
_logger.LogDebug(
|
|
"Patient: {patientId} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING observation {obs}",
|
|
patient.Id, patient.PointOfCare, patient.Bed, messageTime, obs);
|
|
obsToInsert.Add(obs);
|
|
}
|
|
|
|
obsToInsert = await _calculatedObservationsService.Value.MapList(obsToInsert);
|
|
|
|
_ = Task.WhenAll(obsToInsert.Select(obs =>
|
|
Task.Run(() => InsertObservation(obs)))); //.GetAwaiter().GetResult();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(
|
|
"Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} ERROR PROCESSING observations. Exception: {ex}",
|
|
patient.Id, patient.PointOfCare, patient.Bed, messageTime, ex);
|
|
}
|
|
}
|
|
|
|
public Task SaveRequestNurseObsAsync(ApiRequest request)
|
|
{
|
|
return Task.Run(() => SaveRequestNurseObs(request));
|
|
}
|
|
|
|
public async Task SaveRequest(ApiRequest apiRequest)
|
|
{
|
|
if (
|
|
string.IsNullOrEmpty(apiRequest.PatientNumber) &&
|
|
apiRequest.Location != null && apiRequest.Location.IsEmpty()
|
|
)
|
|
{
|
|
_logger.LogDebug("Patient and PointOfCare are nulls");
|
|
throw new ApiRequestException("Patient and PointOfCare are nulls");
|
|
}
|
|
|
|
_logger.LogDebug("patientNumber: {PatientNumber} location: {Location}", apiRequest.PatientNumber,
|
|
apiRequest.Location);
|
|
_logger.LogDebug("RequestType: {Type}", apiRequest.Type);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
switch (apiRequest.Type)
|
|
{
|
|
/*
|
|
* ORU_R01 - Unsolicited transmission of an observation message
|
|
* ORU_R40 - Unsolicited transmission of an alert observation message
|
|
*/
|
|
case "ORU_R40": // UNSOLICITED ALERT OBSERVATION
|
|
await _alarmService.SaveRequestAsync(apiRequest);
|
|
return;
|
|
case "ORU_R01": // UNSOLICITED OBSERVATON
|
|
|
|
if (!apiRequest.Alarms.IsNullOrEmpty())
|
|
{
|
|
await _alarmService.SaveRequestAsync(apiRequest);
|
|
return;
|
|
}
|
|
|
|
|
|
// OBSERVATIONS
|
|
if (apiRequest.Observation != null &&
|
|
(apiRequest.Observations == null || !apiRequest.Observations.Any()))
|
|
apiRequest.Observations = [apiRequest.Observation];
|
|
|
|
var obrcode = apiRequest.ObservationData?.Code ?? "";
|
|
|
|
if (apiRequest.Observations != null)
|
|
{
|
|
if (_intravenousLines.Contains(obrcode))
|
|
await ProcessIntravenousLinesObservation(apiRequest, patient);
|
|
else if (_allergies.Contains(obrcode))
|
|
await ProcessAllergiesObservation(apiRequest, patient);
|
|
else if (_drainageCode.Contains(obrcode))
|
|
await ProcessDrainageObservation(apiRequest, patient);
|
|
//TODO PARA SALIR EN EL RYC REFACTOR
|
|
else if (_isolationCode.Contains(obrcode))
|
|
await ProcessIsolationObservation(apiRequest, patient);
|
|
//TODO REFACTOR, CAMBIO RAPIDO PARA SALIR RYC, PENSAR SI NO SOLO HAY QUE FIARSE POR EL CÓDIGO SI NO POR TODA LA ESTRUCTURA
|
|
//DE CONFIG OBSERVATIONS, A LO MEJOR LA BD TIENE QUE INDICAR SI ES UN PROCESAMIENTO ESPECIAL? REVISAR.
|
|
else if (_positionCode.Contains(obrcode) &&
|
|
!"Temperatura(ºC)".Equals(apiRequest.ObservationData?.Text))
|
|
await ProcessPositionObservation(apiRequest, patient);
|
|
else if (_diagnosisCode.Contains(obrcode))
|
|
await _diagnosisService.SaveRequest(apiRequest, patient);
|
|
else
|
|
ProcessObservations(apiRequest.Observations, patient, 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");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(
|
|
"ERROR SAVING REQUEST: exception: {exMessage} trace: {exStackTrace}",
|
|
ex.Message, ex.StackTrace); //add traceability
|
|
throw new Exception(ex.Message);
|
|
}
|
|
}
|
|
|
|
public Task SaveRequestAsync(ApiRequest apiRequest)
|
|
{
|
|
//return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
|
|
return Task.Run(() => SaveRequest(apiRequest));
|
|
}
|
|
|
|
|
|
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId)
|
|
{
|
|
return await _observationRepository.FindByPatientIdAsync(patientId);
|
|
}
|
|
|
|
public async Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId,
|
|
string codingSystem, string name)
|
|
{
|
|
return await _observationRepository.FindByPatientIdAndCodingSystemAsync(patientId, codingSystem, name);
|
|
}
|
|
|
|
public async Task DeleteByPatientId(ObjectId id)
|
|
{
|
|
try
|
|
{
|
|
var deletedObservationList = await _observationRepository.FindByPatientId(id);
|
|
|
|
if (deletedObservationList == null || deletedObservationList.Count == 0)
|
|
return;
|
|
|
|
_logger.LogDebug("Delete Observations by Patient Id {id}", id);
|
|
await _observationRepository.DeleteByPatientId(id);
|
|
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", id.ToString()));
|
|
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, deletedObservationList,
|
|
null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error Deleting by PatientId. Exception: {ex}", ex);
|
|
}
|
|
}
|
|
|
|
public async Task Archive(PatientObservation observation)
|
|
{
|
|
await _observationArchiveRepository.InsertOneAsync(observation);
|
|
await _observationRepository.DeleteAsync(observation.Id);
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", observation.PatientId.ToString()));
|
|
|
|
}
|
|
|
|
public async Task Archive(Patient patient)
|
|
{
|
|
await ArchiveByPatientId(patient.Id);
|
|
}
|
|
|
|
public async Task ArchiveByPatientId(ObjectId id)
|
|
{
|
|
_logger.LogDebug("Archive Observations by Patient Id {id}", id);
|
|
var cursor = await FindByPatientIdAsync(id);
|
|
|
|
while (await cursor.MoveNextAsync())
|
|
foreach (var current in cursor.Current)
|
|
{
|
|
current.Id = ObjectId.GenerateNewId();
|
|
await _observationArchiveRepository.InsertOneAsync(current);
|
|
}
|
|
|
|
await DeleteByPatientId(id);
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", id.ToString()));
|
|
|
|
}
|
|
|
|
public async Task<List<PatientObservation?>> FindLastIntravenousLinesObservationByLocation(ObjectId patientId)
|
|
{
|
|
return await _observationRepository.AggregatedPatientActiveIntravenousLinesObservations(patientId);
|
|
}
|
|
|
|
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime()
|
|
{
|
|
return await _observationRepository.FindAllLastPatientObservationTime();
|
|
}
|
|
|
|
|
|
public async Task UpdateObservation(PatientObservation observation)
|
|
{
|
|
var obs = await _observationRepository.FindById(observation.Id);
|
|
if (obs != null)
|
|
{
|
|
await _observationRepository.Update(observation);
|
|
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs",observation.PatientId.ToString()));
|
|
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, obs, observation);
|
|
}
|
|
}
|
|
|
|
public async Task<PatientObservation?> FindLastBeforeDate(ObjectId patientId, DateTime date, string? obsName)
|
|
{
|
|
return await _observationRepository.FindLastObservationBeforeDate(patientId, obsName, date);
|
|
}
|
|
|
|
public async Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, DateTime date,
|
|
string? obsName)
|
|
{
|
|
return await _observationRepository.FindAnyWithSameDate(patientId, obsName, date);
|
|
}
|
|
|
|
//TODO To implement
|
|
public async Task<List<PatientObservation>> FindAllBeforeDate(ObjectId patientId, DateTime date,
|
|
List<string>? filterObservations = null)
|
|
{
|
|
return await _observationRepository.FindAnyBeforeDate(patientId, date);
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name,
|
|
int? expires)
|
|
{
|
|
return await _observationRepository.FindLatestUniqueValuesByName(patientId, name, expires);
|
|
}
|
|
|
|
//TODO To implement
|
|
public Task<List<PatientObservation>> FindAllAfterDate(ObjectId patientId, DateTime date,
|
|
List<string>? filterObservations = null)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public async Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId,
|
|
string name, int? endAfter = null, int? num = null)
|
|
{
|
|
return await _observationRepository.FindLastNotExpiredObservatonsByPatient(patientId, name, endAfter, num);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve all observations with expire time from configObservation service
|
|
/// Check all of this observations in patient_observations and expire them if (obs.time + config.expires) smaller than
|
|
/// current time
|
|
/// mark as expired in bd.
|
|
/// </summary>
|
|
public async Task CheckAndExpireObservations()
|
|
{
|
|
var count = 0;
|
|
_logger.LogDebug("start checking expired observations");
|
|
|
|
|
|
await using var enumerator = FindNotExpiredObservationsShouldBeExpired().GetAsyncEnumerator();
|
|
while (await enumerator.MoveNextAsync())
|
|
{
|
|
count++;
|
|
|
|
var current = enumerator.Current;
|
|
current.Expired = true;
|
|
|
|
await _observationRepository.Update(current);
|
|
}
|
|
|
|
_logger.LogDebug("found observations {count} ", count);
|
|
}
|
|
|
|
public async Task UpdateExpiredObservations(List<PatientObservation> patientObservations)
|
|
{
|
|
await _observationRepository.UpdateExpiredObservations(patientObservations);
|
|
var id = patientObservations.First().Id;
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", id.ToString()));
|
|
|
|
foreach (var observation in patientObservations)
|
|
{
|
|
var auxObs = await _auditService.DeepCopyAsync(observation);
|
|
observation.Expired = true;
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxObs, observation);
|
|
}
|
|
}
|
|
|
|
public async IAsyncEnumerable<PatientObservation> FindNotExpiredObservationsShouldBeExpired()
|
|
{
|
|
var expiringObservations = await _configObservationService.GetAllConfigs();
|
|
var expiringList = expiringObservations.ToList();
|
|
expiringList.RemoveAll(z => z.Expires == null);
|
|
var count = 0;
|
|
|
|
var uniqueNames = expiringList
|
|
.Select(item => item.Name)
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
|
|
using var enumerator = (await _observationRepository.FindNotExpired(uniqueNames)).GetEnumerator();
|
|
while (enumerator.MoveNext())
|
|
{
|
|
count++;
|
|
|
|
var current = enumerator.Current;
|
|
var patientid = current.PatientId;
|
|
var patient = await _patientService.FindById(patientid);
|
|
if (patient == null)
|
|
{
|
|
await _observationRepository.FindByPatientId(patientid);
|
|
|
|
await _observationRepository.DeleteByPatientId(patientid);
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", patientid.ToString()));
|
|
|
|
continue;
|
|
}
|
|
if(current.Name == null) continue;
|
|
await _configObservationService.GetConfigObservationItemsByName(current.Name);
|
|
var configObs = expiringObservations?
|
|
.FirstOrDefault();
|
|
if (configObs == null) continue;
|
|
|
|
if (!double.TryParse(configObs.Expires.ToString(), out var expires))
|
|
continue;
|
|
|
|
var expectedExpireTime = current.Time.AddMinutes(expires);
|
|
if (DateTime.Now > expectedExpireTime) yield return current;
|
|
}
|
|
|
|
_logger.LogDebug("expired observations retrieved {count} observations", count);
|
|
}
|
|
|
|
public async Task ExpireObservations()
|
|
{
|
|
//TODO expire each section
|
|
var expiringObservations = await _configObservationService.GetAllConfigs();
|
|
var expiringList = expiringObservations.ToList();
|
|
expiringList.RemoveAll(z => z.Expires is null or <= 0);
|
|
|
|
await _observationRepository.ExpireExpiredObservations(expiringList.Distinct()
|
|
.ToList());
|
|
}
|
|
|
|
public async Task ExpireObservationsAndRecalculateAsync()
|
|
{
|
|
try
|
|
{
|
|
GlobalData.AddData("isCheckingExpiration", true);
|
|
|
|
var configObservations =
|
|
await _configObservationService.GetAllConfigs();
|
|
|
|
var names = configObservations.Select(o => o.Name).ToList();
|
|
|
|
var patients = await _patientService.FindAll();
|
|
|
|
foreach (var patient in patients)
|
|
{
|
|
var listField = names?.Select(n => new Field
|
|
{
|
|
Name = n,
|
|
Last = 1,
|
|
OnlyExpired = true
|
|
}).ToList();
|
|
|
|
var lastPatientObservationsByName =
|
|
await _observationRepository.AggregatedPatientLastObservationsByField(patient.Id, listField);
|
|
|
|
foreach (var obs in
|
|
lastPatientObservationsByName)
|
|
await InsertObservation(obs, false, false); //Not really insert, only makes calcs
|
|
}
|
|
|
|
var obsToExpireList = new List<PatientObservation>();
|
|
var i = 0;
|
|
var count = 0;
|
|
await using var enumerator = FindNotExpiredObservationsShouldBeExpired().GetAsyncEnumerator();
|
|
while (await enumerator.MoveNextAsync())
|
|
{
|
|
i++;
|
|
//every 1000 cut list to avoid memory leaks when there are tons of data.
|
|
|
|
if (i > 1000)
|
|
{
|
|
await UpdateExpiredObservations(obsToExpireList);
|
|
i = 0;
|
|
obsToExpireList.Clear();
|
|
}
|
|
|
|
var current = enumerator.Current;
|
|
current.Expired = true;
|
|
|
|
obsToExpireList.Add(current);
|
|
count++;
|
|
}
|
|
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.PatientObservations));
|
|
|
|
_logger.LogDebug("number of expired observations should be expired:{count}", count);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("ERROR expire observations And recalculate {exMessage} trace: {exStackTrace}",
|
|
ex.Message, ex.StackTrace);
|
|
}
|
|
|
|
GlobalData.AddData("isCheckingExpiration", false);
|
|
}
|
|
|
|
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
|
|
{
|
|
await _observationRepository.UpdateManyObjectId(nameId, id, oldId);
|
|
}
|
|
|
|
public async Task InsertSimpleObservation(PatientObservation observation)
|
|
{
|
|
await _observationRepository.InsertOneAsync(observation);
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, observation);
|
|
}
|
|
|
|
public async Task<List<PatientObservation>> FindAllBetweenDates(
|
|
ObjectId patientId,
|
|
DateTime? startDate = null,
|
|
DateTime? endDate = null,
|
|
List<string>? filterObservations = null,
|
|
bool fromArchived = false,
|
|
PaginationFilter? filter = null
|
|
)
|
|
{
|
|
try
|
|
{
|
|
var collection = fromArchived
|
|
? _observationArchiveRepository.Collection
|
|
: _observationRepository.Collection;
|
|
|
|
var filterBuilder = Builders<PatientObservation>.Filter;
|
|
|
|
var conditions = new List<FilterDefinition<PatientObservation>>
|
|
{
|
|
filterBuilder.Eq(o => o.PatientId, patientId),
|
|
filterBuilder.Ne(o => o.Name, null),
|
|
//filterBuilder.In(o => o.Name, filterObservations ?? new List<string?>()),
|
|
filterBuilder.Gt(o => o.Time, startDate ?? DateTime.MinValue),
|
|
filterBuilder.Lt(o => o.Time, endDate ?? DateTime.MaxValue)
|
|
};
|
|
|
|
if (filterObservations != null && filterObservations.Any())
|
|
conditions.Add(filterBuilder.In(o => o.Name, filterObservations));
|
|
|
|
var combinedFilter = filterBuilder.And(conditions);
|
|
|
|
var findOptions = new FindOptions<PatientObservation>
|
|
{
|
|
Skip = (filter?.PageNumber - 1) * filter?.PageSize,
|
|
Limit = filter?.PageSize
|
|
};
|
|
|
|
var cursor = await collection.FindAsync(combinedFilter, findOptions);
|
|
|
|
var observationsList = await cursor.ToListAsync();
|
|
|
|
return observationsList;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("Error while getting PatientObservation between dates. Exception: {ex}", ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
public async Task ExpireAlertsAndPowerOffAsync()
|
|
{
|
|
try
|
|
{
|
|
GlobalData.AddData("isCheckingAlertsExpiration", true);
|
|
|
|
|
|
var pointOfCares = await _pointOfCareService.GetAllConfigs();
|
|
|
|
pointOfCares = pointOfCares
|
|
.Where(box =>
|
|
box.Configuration is { BeaconList: not null } &&
|
|
box.Configuration!.BeaconList.Any() &&
|
|
!box.Configuration!.BeaconList.First().Options.Emulate)
|
|
.ToList();
|
|
|
|
|
|
var configObs = await _configObservationService.GetAllConfigs();
|
|
var configList = configObs.ToList();
|
|
|
|
configList.RemoveAll(z => z.Alarm is not { Enabled: true });
|
|
|
|
|
|
if (configList.Count == 0)
|
|
return;
|
|
var patients = await _patientService.FindAll();
|
|
if (!patients.Any())
|
|
return;
|
|
|
|
//apagamos balizas de los boxes que no tienen pacientes por si se ha quedado alguna encendida
|
|
var emptyBoxes = pointOfCares
|
|
.Where(box => box.Status != StatusEnum.PointOfCare.InUse)
|
|
.ToList();
|
|
|
|
emptyBoxes.ForEach(async void (b) =>
|
|
{
|
|
try
|
|
{
|
|
await _lightBeaconService.PowerOffLed(b.Id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Exception powerOffLed {exMessage} trace: {exStackTrace}",
|
|
ex.Message, ex.StackTrace);
|
|
}
|
|
});
|
|
|
|
var configObservationItems = configObs.ToList();
|
|
var endAfterDefaultValue = configObservationItems
|
|
.Where(c => c.Alarm != null)
|
|
.Select(c =>
|
|
c.Alarm?.EndAfter ?? 0) // comprobación de null aquí por seguridad adicional.
|
|
.DefaultIfEmpty(0)
|
|
.Max();
|
|
|
|
foreach (var patient in patients)
|
|
{
|
|
//Nos quedamos con los pacientes que tienen cama
|
|
|
|
if (patient.Location.UnitName == nameof(VirtualPointOfCare.Moved) ||
|
|
patient.Location.UnitName == nameof(VirtualPointOfCare.Deleted) ||
|
|
patient.Location.UnitName == nameof(VirtualPointOfCare.Pushed) ||
|
|
patient.Location.UnitName == nameof(VirtualPointOfCare.Unknown))
|
|
continue;
|
|
|
|
_ = CheckBeacon(patient, configObs, endAfterDefaultValue);
|
|
|
|
_ = CheckRelay(patient, configObs, endAfterDefaultValue);
|
|
|
|
_ = CheckRecordings(patient, pointOfCares, configObs, endAfterDefaultValue);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("ERROR expire observations And recalculate {exMessage} trace: {exStackTrace}",
|
|
ex.Message, ex.StackTrace);
|
|
}
|
|
|
|
GlobalData.AddData("isCheckingAlertsExpiration", false);
|
|
}
|
|
|
|
public async Task<PaginationResponse<PatientObservation>> GetPaginatedObservations(PaginationFilter filter)
|
|
{
|
|
var result = _observationRepository.GetPaginatedObservations(filter);
|
|
|
|
var count = await result.CountDocumentsAsync();
|
|
|
|
var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
|
|
.Limit(filter.PageSize)
|
|
.ToCursorAsync();
|
|
|
|
|
|
var dataList = await data.ToListAsync();
|
|
|
|
return new PaginationResponse<PatientObservation>(dataList, filter.PageNumber, filter.PageSize, count);
|
|
}
|
|
|
|
|
|
private async Task DoRetentionActions(PatientObservation obs)
|
|
{
|
|
try
|
|
{
|
|
var result = await _configObservationService.RetentionActions(obs);
|
|
if (result == null || !result.RetentionPolicyValue.HasValue || string.IsNullOrEmpty(obs.Name)) return;
|
|
var deletedObs = new List<PatientObservation>();
|
|
switch (result.RetentionPolicy)
|
|
{
|
|
case RetentionPolicy.DeleteOlderDays:
|
|
deletedObs =
|
|
await _observationRepository.DeleteOlderDaysAsync(obs.Name, result.RetentionPolicyValue.Value);
|
|
break;
|
|
case RetentionPolicy.DeleteOlderSeconds:
|
|
deletedObs = await _observationRepository.DeleteOlderSecondsAsync(obs.Name,
|
|
result.RetentionPolicyValue.Value);
|
|
break;
|
|
case RetentionPolicy.DeleteOlderNumber:
|
|
deletedObs = await _observationRepository.DeleteOlderNumberAsync(obs.Name,
|
|
result.RetentionPolicyValue.Value);
|
|
break;
|
|
case RetentionPolicy.NoDelete:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
foreach (var observation in deletedObs)
|
|
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, observation, null);
|
|
|
|
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ByPatient("patients:latestObs", obs.PatientId.ToString()));
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError("Error doing retention actions. Exception: {ex}", ex);
|
|
}
|
|
}
|
|
|
|
private async void CheckForGroupedObs(PatientObservation obs)
|
|
{
|
|
try
|
|
{
|
|
//Removed ignore obs to future, we accept them and recalculate and front decide what to do
|
|
foreach (var wsg in _subscriberGroupedService.GetGrouped().Where(g =>
|
|
g.PatientId == obs.PatientId && obs.Name != null && g.Names.Contains(obs.Name) &&
|
|
g.IsNewObservationRelevantForGroup(obs)))
|
|
{
|
|
wsg.Timer.Stop();
|
|
|
|
var groupedField = new GroupedField
|
|
{
|
|
Names = wsg.Names,
|
|
Name = obs.Name ?? string.Empty,
|
|
Max = wsg.Max,
|
|
Group = wsg.Group.FirstOrDefault().Value,
|
|
Regularity = wsg.Regularity,
|
|
Result = wsg.Result,
|
|
Since = wsg.Since,
|
|
StartTimeShift = wsg.StartTimeShift
|
|
};
|
|
|
|
|
|
var newGroupedObservation = await _groupedObservationService
|
|
.GenerateGroupedObservation(obs.PatientId, groupedField, wsg.LastGroupedObservationObs, obs,
|
|
wsg.TimeZoneId);
|
|
|
|
_subscriberGroupedService.UpdateLastGroupedObsInGroup(wsg.HashCode, newGroupedObservation);
|
|
|
|
foreach (var gr in wsg.Group)
|
|
{
|
|
var grName = wsg.Group.GetValue(gr.Key);
|
|
if (grName == null) continue;
|
|
|
|
newGroupedObservation.Group = grName;
|
|
_ = Task.Run(() => _clientMessageService.SendAsync(gr.Key, OperationType.GroupedObservation,
|
|
newGroupedObservation));
|
|
}
|
|
|
|
//wsg.TimerReestart();
|
|
|
|
await _cacheService.DeleteByPatternAsync(CacheKeys.GroupedObs(obs.PatientId, obs.Name ?? string.Empty));
|
|
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError("error checking grouped observations: exception: {eMessage} trace:{eStackTrace}",
|
|
e.Message, e.StackTrace);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessIntravenousLinesObservation(ApiRequest apiRequest, Patient patient)
|
|
{
|
|
var isInsertable = false;
|
|
_logger.LogDebug("INSERT IntraVenousLineObservation");
|
|
|
|
if (apiRequest.ObsertationData == null || !apiRequest.ObsertationData.Time.HasValue)
|
|
{
|
|
_logger.LogError("Error INSERTING IntraVenousLineObservation. ");
|
|
return;
|
|
}
|
|
|
|
var obs = new PatientObservation
|
|
{
|
|
Time = apiRequest.ObsertationData.Time.Value,
|
|
Code = apiRequest.ObsertationData.Code,
|
|
CodingSystem = apiRequest.ObsertationData.CodingSystem,
|
|
PatientId = patient.Id,
|
|
ParentData = new ParentDataClass
|
|
{
|
|
Code = apiRequest.ObservationData?.Code, CodingSystem = apiRequest.ObservationData?.CodingSystem
|
|
},
|
|
MessageTime = apiRequest.MessageTime
|
|
};
|
|
|
|
|
|
//Todos los formatos de texto de vías son Catéter X: + localizacion
|
|
var typeLocation = apiRequest.ObservationData?.Text?.Split(':');
|
|
obs.Value = new PatientIntravenousLinesValue
|
|
{
|
|
Type = typeLocation != null ? typeLocation[0] : string.Empty
|
|
};
|
|
if (typeLocation is { Length: > 1 })
|
|
if (obs.Value is PatientIntravenousLinesValue obsValue)
|
|
obsValue.Location = typeLocation[1];
|
|
|
|
if (apiRequest.Observations != null)
|
|
{
|
|
if (obs.Value is not PatientIntravenousLinesValue obsValue)
|
|
return;
|
|
|
|
foreach (var observation in apiRequest.Observations)
|
|
{
|
|
var obsValueStr = observation.Value.ToString();
|
|
if (obsValueStr == null) continue;
|
|
|
|
switch (observation.Code)
|
|
{
|
|
case "273248003":
|
|
//In ICCA an intravenouse Line OBS should come with Insertado and Retirado but can come with more, ignore them.
|
|
if (obsValueStr.Equals("Insertado") || obsValueStr.Equals("Retirado"))
|
|
{
|
|
obsValue.Action = observation.Value.ToString() ?? "NULL";
|
|
isInsertable = true;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
break;
|
|
case "397898000":
|
|
obsValue.RemoveTime = (DateTime?)observation.Value;
|
|
break;
|
|
case "439272007":
|
|
obsValue.InsertTime = (DateTime?)observation.Value;
|
|
break;
|
|
case "228864003":
|
|
obsValue.Duration = observation.Value.ToString() ?? "NULL";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isInsertable) return;
|
|
|
|
await InsertObservation(obs);
|
|
}
|
|
|
|
private async Task ProcessAllergiesObservation(ApiRequest apiRequest, Patient patient)
|
|
{
|
|
_logger.LogDebug("INSERT AllergiesObservation");
|
|
|
|
if (apiRequest.ObservationData == null)
|
|
{
|
|
_logger.LogError("Error Processing Allergies Observation. ObservationData is null");
|
|
return;
|
|
}
|
|
|
|
if (!apiRequest.ObservationData.Time.HasValue)
|
|
{
|
|
_logger.LogError("Error Processing Allergies Observation. ObservationData time null");
|
|
return;
|
|
}
|
|
|
|
var obs = new PatientObservation
|
|
{
|
|
Time = apiRequest.ObservationData.Time.Value,
|
|
Name = apiRequest.ObsertationData?.Text ?? string.Empty,
|
|
Code = apiRequest.ObservationData.Code,
|
|
CodingSystem = apiRequest.ObservationData.CodingSystem,
|
|
PatientId = patient.Id,
|
|
ParentData = new ParentDataClass
|
|
{
|
|
Code = apiRequest.ObservationData.Code,
|
|
CodingSystem = apiRequest.ObservationData.CodingSystem,
|
|
Name = apiRequest.ObservationData.Text
|
|
},
|
|
MessageTime = apiRequest.MessageTime
|
|
};
|
|
|
|
List<PatientAllergiesValue> patientAllergiesValues = [];
|
|
|
|
PatientAllergiesValue allergiesValues = new();
|
|
|
|
if (apiRequest.Observations == null)
|
|
{
|
|
_logger.LogError("Error Processing Allergies Observation. Observations null");
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i <= apiRequest.Observations.Count - 1; i++)
|
|
{
|
|
switch (apiRequest.Observations[i].Code)
|
|
{
|
|
case "263490005":
|
|
if (apiRequest.Observations[i].Value.ToString() == "Sin alergias conocidas") return;
|
|
|
|
continue;
|
|
|
|
case "300916003": //Latex
|
|
if (apiRequest.Observations[i].Value.ToString() == "Si")
|
|
{
|
|
allergiesValues.Type = "Latex";
|
|
allergiesValues.Value = "Si";
|
|
//patientAllergiesValues.Add(allergiesValues);
|
|
//allergiesValues = new PatientAllergiesValue { };
|
|
}
|
|
|
|
break;
|
|
|
|
case "419199007":
|
|
allergiesValues.Type = apiRequest.Observations[i].Value.ToString();
|
|
break;
|
|
|
|
case "277054007":
|
|
case "416098002":
|
|
allergiesValues.Value = apiRequest.Observations[i].Value.ToString();
|
|
break;
|
|
|
|
case "281296001":
|
|
allergiesValues.Notes = apiRequest.Observations[i].Value.ToString();
|
|
break;
|
|
}
|
|
|
|
if (i == apiRequest.Observations.Count - 1)
|
|
{
|
|
if (allergiesValues is { Type: not null })
|
|
patientAllergiesValues.Add(allergiesValues);
|
|
//allergiesValues = new PatientAllergiesValue { value = new List<string>(), notes = new List<string>() };
|
|
allergiesValues = new PatientAllergiesValue();
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
if (apiRequest.Observations[i].Code == "300916003" ||
|
|
apiRequest.Observations[i].Code == "419199007")
|
|
{
|
|
if (allergiesValues is { Type: not null })
|
|
patientAllergiesValues.Add(allergiesValues);
|
|
//allergiesValues = new PatientAllergiesValue { value = new List<string>(), notes = new List<string>() };
|
|
allergiesValues = new PatientAllergiesValue();
|
|
}
|
|
|
|
i--;
|
|
}
|
|
}
|
|
|
|
obs.Value = patientAllergiesValues;
|
|
await InsertObservation(obs);
|
|
}
|
|
|
|
|
|
private async Task ProcessIsolationObservation(ApiRequest apiRequest, Patient patient)
|
|
{
|
|
if (apiRequest.ObservationData is { Text: "Aislamiento", Time: not null })
|
|
{
|
|
_logger.LogDebug("INSERT Isolation OBS");
|
|
var value = apiRequest.ObservationData.Value?.ToString()?.Replace(";", ",");
|
|
if (value == null)
|
|
{
|
|
_logger.LogError("Error Processing Isolation Observation. Value is null");
|
|
return;
|
|
}
|
|
|
|
var obs = new PatientObservation
|
|
{
|
|
Time = apiRequest.ObservationData.Time.Value,
|
|
Name = "Isolation",
|
|
CodingSystem = "ADAS",
|
|
PatientId = patient.Id,
|
|
Value = value,
|
|
MessageTime = apiRequest.MessageTime
|
|
};
|
|
|
|
await InsertObservation(obs);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessPositionObservation(ApiRequest apiRequest, Patient patient)
|
|
{
|
|
if (apiRequest.ObservationData != null &&
|
|
apiRequest.ObservationData?.Text?.ToUpper() == "CAMBIOS POSTURALES")
|
|
{
|
|
_logger.LogDebug("INSERT Position OBS");
|
|
var strValue = apiRequest.ObservationData.Value?.ToString();
|
|
|
|
if (string.IsNullOrEmpty(strValue))
|
|
{
|
|
var ob = apiRequest.Observation ?? apiRequest.Observations?.FirstOrDefault();
|
|
if (ob != null)
|
|
strValue = ob.Value.ToString();
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(strValue))
|
|
{
|
|
_logger.LogWarning("Position Observation value is null or empty.");
|
|
return;
|
|
}
|
|
|
|
if (!apiRequest.ObservationData.Time.HasValue)
|
|
{
|
|
_logger.LogWarning("Position Observation time is null. ");
|
|
return;
|
|
}
|
|
|
|
var obs = new PatientObservation
|
|
{
|
|
Time = apiRequest.ObservationData.Time.Value,
|
|
Name = "Patient_Position",
|
|
CodingSystem = "ADAS",
|
|
PatientId = patient.Id,
|
|
Value = strValue.Replace(";", ","),
|
|
MessageTime = apiRequest.MessageTime
|
|
};
|
|
|
|
await InsertObservation(obs);
|
|
}
|
|
}
|
|
|
|
|
|
private async Task ProcessDrainageObservation(ApiRequest apiRequest, Patient patient)
|
|
{
|
|
_logger.LogDebug("INSERT DrainageObservation");
|
|
|
|
if (apiRequest.ObservationData == null || !apiRequest.ObservationData.Time.HasValue ||
|
|
apiRequest.Observations == null)
|
|
{
|
|
_logger.LogWarning("Error Processing Drainage Observation.");
|
|
return;
|
|
}
|
|
|
|
var obs = new PatientObservation
|
|
{
|
|
Time = apiRequest.ObservationData.Time.Value,
|
|
|
|
Code = apiRequest.ObservationData.Code,
|
|
CodingSystem = apiRequest.ObservationData.CodingSystem,
|
|
PatientId = patient.Id,
|
|
ParentData = new ParentDataClass
|
|
{
|
|
Code = apiRequest.ObservationData.Code,
|
|
CodingSystem = apiRequest.ObservationData.CodingSystem,
|
|
Name = apiRequest.ObservationData.Text
|
|
},
|
|
MessageTime = apiRequest.MessageTime
|
|
};
|
|
|
|
if (apiRequest.Observation != null) obs.Time = apiRequest.Observation.Time;
|
|
|
|
obs.Value = new PatientDrainagesValue();
|
|
|
|
if (obs.Value is not PatientDrainagesValue obsVal)
|
|
{
|
|
_logger.LogDebug("Error Processing Drainage Observation. Value is null. ");
|
|
return;
|
|
}
|
|
|
|
foreach (var observation in apiRequest.Observations)
|
|
switch (observation.Code)
|
|
{
|
|
case "138875005":
|
|
|
|
switch (observation.Name ?? "")
|
|
{
|
|
case "Tipo de drenaje":
|
|
obsVal.Type = observation.Value.ToString();
|
|
break;
|
|
|
|
case "Altura columna(cmH2O)":
|
|
if (int.TryParse(observation.Value.ToString(), out var valueParsed))
|
|
obsVal.Height = valueParsed;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case "10546003":
|
|
obsVal.Location = observation.Value.ToString();
|
|
break;
|
|
|
|
case "56868008":
|
|
if (int.TryParse(observation.Value.ToString(), out var obsValue)) obsVal.Volume = obsValue;
|
|
|
|
break;
|
|
}
|
|
|
|
await InsertObservation(obs);
|
|
}
|
|
|
|
/**
|
|
* Las obs que se insertan de forma manual desde nurse deben seguir la logica contraria a las obs
|
|
* recibidas desde el censo
|
|
*/
|
|
public async Task SaveRequestNurseObs(ApiRequest apiRequest)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (apiRequest.Observations != null)
|
|
ProcessObservations(apiRequest.Observations, patient, apiRequest.MessageTime,
|
|
apiRequest.ObservationData);
|
|
}
|
|
|
|
|
|
private async Task CheckRecordings(Patient patient, List<PointOfCare> configs,
|
|
IEnumerable<ConfigObservation> obsConfigList, int defaultValue)
|
|
{
|
|
//paramos grabación si no hay alarmas activas y hay una grabación
|
|
var recordingEndAfter = obsConfigList
|
|
.Where(c => c.Alarm is { Recording: not null })
|
|
.Select(c => c.Alarm is { Recording: not null } ? c.Alarm.Recording.EndAfter : defaultValue)
|
|
.DefaultIfEmpty(defaultValue)
|
|
.Max();
|
|
|
|
var obsListWithRecordingAlarms =
|
|
await _observationRepository.FindLastNotExpiredObservatonsByPatient(patient.Id, "ADAS_ALARM",
|
|
recordingEndAfter);
|
|
|
|
if (obsListWithRecordingAlarms.Any())
|
|
return;
|
|
|
|
|
|
var boxCfg = configs.FirstOrDefault(box => box.Id == patient.PointOfCareId);
|
|
|
|
var roomId = boxCfg?.Configuration?.Id;
|
|
if (roomId == null || !int.TryParse(roomId.ToString(), out var roomIdParsed))
|
|
return;
|
|
|
|
var currentRecordings = await _recordingService.GetRecordings(roomIdParsed);
|
|
|
|
|
|
if (currentRecordings == null || !currentRecordings.Any())
|
|
return;
|
|
|
|
var manualRecordings =
|
|
currentRecordings.Where(r => r.AlarmType == AlarmEnum.Type.Manual);
|
|
|
|
foreach (var rec in manualRecordings)
|
|
{
|
|
_logger.LogDebug("PatientId: {obsid}. Send Stop Manual Recording Power Off", patient.Id);
|
|
|
|
var manualRecording = new ManualRecording
|
|
{
|
|
Recording = new MRecording
|
|
{
|
|
StartRecordingTime = rec.StartRecordingTime,
|
|
StopRecordingTime = rec.StopRecordingTime ?? DateTime.Now
|
|
}
|
|
};
|
|
|
|
if (boxCfg != null)
|
|
await _recordingService.SendRecordingData(patient, boxCfg, manualRecording, false);
|
|
}
|
|
}
|
|
|
|
private async Task CheckRelay(Patient patient,
|
|
IEnumerable<ConfigObservation> obsConfigList, int defaultValue)
|
|
{
|
|
var relayEndAfter = obsConfigList
|
|
.Where(c => c.Alarm is { OpenDoor: not null })
|
|
.Select(c => c.Alarm is { OpenDoor: not null } ? c.Alarm.OpenDoor.EndAfter : defaultValue)
|
|
.DefaultIfEmpty(defaultValue)
|
|
.Max();
|
|
|
|
var obsListWithRelayAlarms =
|
|
await _observationRepository.FindLastNotExpiredObservatonsByPatient(patient.Id, "ADAS_ALARM",
|
|
relayEndAfter);
|
|
|
|
if (!patient.PointOfCareId.HasValue)
|
|
{
|
|
_logger.LogError("PatientId: {PatId} has no points of Care on check relay", patient.Id);
|
|
return;
|
|
}
|
|
|
|
//Si no hay activas apagamos Relay
|
|
var relayConfig = await _pointOfCareService.FindById(patient.PointOfCareId.Value);
|
|
if (relayConfig is { Configuration.RelayIdList: not null } && !obsListWithRelayAlarms.Any())
|
|
{
|
|
_logger.LogDebug("PatientId: {PatId}. Send relay Power Off", patient.Id);
|
|
var relays = _relayService.GetRelayInList(relayConfig.Configuration.RelayIdList);
|
|
relays.ForEach(async void (r) =>
|
|
{
|
|
try
|
|
{
|
|
await _relayService.PowerOff(r);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.LogError("PatientId: {PatId}. Exception Power Off Relay {exMessage} trace: {exStackTrace}",
|
|
patient.Id, e.Message, e.StackTrace);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private async Task CheckBeacon(Patient patient,
|
|
IEnumerable<ConfigObservation> obsConfigList, int defaultValue)
|
|
{
|
|
var beaconEndAfter = obsConfigList
|
|
.Where(c => c.Alarm is { Enabled: true, Beacon.Enabled: true })
|
|
.Select(c => c.Alarm is { Beacon: not null } ? c.Alarm.Beacon.EndAfter : defaultValue)
|
|
.DefaultIfEmpty(defaultValue)
|
|
.Max();
|
|
|
|
var obsListWithBeaconAlarms =
|
|
await _observationRepository.FindLastNotExpiredObservatonsByPatient(patient.Id, "ADAS_ALARM",
|
|
beaconEndAfter);
|
|
|
|
|
|
//TODO: Heredar de la observación el endAfter si el de la baliza está 0
|
|
|
|
//Si no tiene alarmas activas y no está ya apagada lo hacemos
|
|
if (!obsListWithBeaconAlarms.Any() && patient.PointOfCareId.HasValue)
|
|
{
|
|
_logger.LogDebug("PatientId: {patientId}. Send Beacon code Power Off", patient.Id);
|
|
await _lightBeaconService.PowerOffLed(patient.PointOfCareId.Value);
|
|
}
|
|
}
|
|
} |