Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.BD;
|
||||
|
||||
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
|
||||
{
|
||||
private readonly ILogger<CalculatedObservations> _logger =
|
||||
serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
|
||||
private readonly Lazy<IObservationService> _observationService =
|
||||
serviceProvider.GetRequiredService<Lazy<IObservationService>>();
|
||||
|
||||
public Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
return Task.FromResult(obs)!;
|
||||
}
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
return Task.FromResult(treatment);
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
return Task.FromResult(diagnosis);
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
if (pumpObservation.Code == "IHE PCD-04")
|
||||
await CreatePumpAlarmObservation(pumpObservation);
|
||||
return pumpObservation;
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Task CreatePumpAlarmObservation(PumpObservation pumpObservation)
|
||||
{
|
||||
PatientObservation patientObservationAlarm = new()
|
||||
{
|
||||
CodingSystem = "ADAS_ALARM",
|
||||
Code = pumpObservation.AlarmState,
|
||||
Name = $"Alarm_Pump_{pumpObservation.AlarmType}",
|
||||
Value = pumpObservation.DeviceId != null
|
||||
? $"Device Id: {pumpObservation.DeviceId}"
|
||||
: $"Infusion Id {pumpObservation.InfusionId}",
|
||||
Time = pumpObservation.Time
|
||||
};
|
||||
|
||||
if (pumpObservation.PatientId.HasValue) patientObservationAlarm.PatientId = pumpObservation.PatientId.Value;
|
||||
|
||||
|
||||
//CheckAlarmConfig(patientObservationAlarm);
|
||||
try
|
||||
{
|
||||
_ = _observationService.Value.InsertObservation(patientObservationAlarm, mapObs: false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error inserting Pump Observation Alarm. Exception: {e}", e);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.CHUO;
|
||||
|
||||
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
|
||||
{
|
||||
private readonly ILogger<CalculatedObservations> _logger =
|
||||
serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
|
||||
}
|
||||
|
||||
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
_logger.LogTrace("Mapping {name} observation {obs}", obs.Name, obs);
|
||||
|
||||
return Task.FromResult<T?>(obs);
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
|
||||
}
|
||||
|
||||
//public Task<List<PatientObservation>> MapList(List<PatientObservation> obsToInsert)
|
||||
//{
|
||||
// throw new NotImplementedException();
|
||||
//}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
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.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.HGM;
|
||||
|
||||
//Custom for Hospital Gregorio Marañón
|
||||
public class CalculatedObservations : ICalculatedObservations
|
||||
{
|
||||
private readonly List<string> _highFrequencyVentilation = [];
|
||||
private readonly List<string> _invasiveVentilation = [];
|
||||
private readonly ILogger<CalculatedObservations> _logger;
|
||||
private readonly List<string> _nonInvasiveVentilation = [];
|
||||
private readonly Lazy<IObservationService> _observationService;
|
||||
|
||||
public CalculatedObservations(IServiceProvider serviceProvider)
|
||||
{
|
||||
var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>().Value;
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>();
|
||||
|
||||
var highFrequencyVentilation = apiSettings.HighFrequencyVentilation ?? null;
|
||||
highFrequencyVentilation?.ForEach(x => _highFrequencyVentilation.Add(x.Trim()));
|
||||
|
||||
var invasiveVentilation = apiSettings.InvasiveVentilation ?? null;
|
||||
invasiveVentilation?.ForEach(x => _invasiveVentilation.Add(x.Trim()));
|
||||
|
||||
var nonInvasiveVentilation = apiSettings.NonInvasiveVentilation ?? null;
|
||||
nonInvasiveVentilation?.ForEach(x => _nonInvasiveVentilation.Add(x.Trim()));
|
||||
}
|
||||
|
||||
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
if (string.IsNullOrEmpty(obs.Name)) return obs;
|
||||
|
||||
switch (obs.Name)
|
||||
{
|
||||
case "Resp_Mode":
|
||||
await CalculateVentilationMode(obs);
|
||||
break;
|
||||
}
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
}
|
||||
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task CalculateVentilationMode(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
var respTypeObs = new PatientObservation
|
||||
{
|
||||
Name = "Resp_Type",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time
|
||||
};
|
||||
|
||||
var strValue = pobs.Value.ToString();
|
||||
if (string.IsNullOrEmpty(strValue))
|
||||
respTypeObs.Value = nameof(RespirationType.None);
|
||||
else
|
||||
respTypeObs.Value = _highFrequencyVentilation.Contains(strValue)
|
||||
? nameof(RespirationType.HighFrequencyVentilation)
|
||||
: _invasiveVentilation.Contains(strValue)
|
||||
? respTypeObs.Value = nameof(RespirationType.Invasive)
|
||||
: _nonInvasiveVentilation.Contains(strValue)
|
||||
? respTypeObs.Value = nameof(RespirationType.NonInvasive)
|
||||
: nameof(RespirationType.None);
|
||||
|
||||
|
||||
var wasInserted = await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
|
||||
if (wasInserted)
|
||||
_logger.LogDebug("Calculated Resp_Type for patientid: {respTypeObsPatientid} value: {respType}",
|
||||
respTypeObs.PatientId, respTypeObs.Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
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.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using Serilog;
|
||||
|
||||
namespace adas_core.Application.Customizations.HPAZ;
|
||||
|
||||
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
|
||||
{
|
||||
private readonly Lazy<IAlarmService> _alarmService = serviceProvider.GetRequiredService<Lazy<IAlarmService>>();
|
||||
|
||||
|
||||
private readonly ApiSettings
|
||||
_apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>().Value; //apiSettings;
|
||||
|
||||
private readonly IConfigObservationService _configObservationService =
|
||||
serviceProvider.GetRequiredService<IConfigObservationService>(); //configObservationService;
|
||||
|
||||
private readonly ILogger<CalculatedObservations> _logger =
|
||||
serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
|
||||
//Use GroupedObservation to avoid circular dependency with _observationService
|
||||
private readonly Lazy<IObservationService> _observationService =
|
||||
serviceProvider.GetRequiredService<Lazy<IObservationService>>(); //observationService;
|
||||
|
||||
private readonly List<string> _pressBloodArteryMean = ["TAm"];
|
||||
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<PatientTreatment?>>(new List<PatientTreatment?>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the obs to an event or alarm depending on its type and inserts it if needed
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type</typeparam>
|
||||
/// <param name="source">Observation</param>
|
||||
/// <param name="onlyByName">True if the observation needs to generate an alert</param>
|
||||
/// <returns>Mapped observation</returns>
|
||||
public async Task<T?> Map<T>(T source, bool onlyByName) where T : BasePatientObservation
|
||||
{
|
||||
_logger.LogDebug("Mapping {obsName} mode observation {obs}", source.Name, source);
|
||||
|
||||
if (source is not BasePatientObservationValue obs) return source;
|
||||
|
||||
var name = obs is PatientObservationAlarm obsAlarm ? obsAlarm.Event : obs.Name;
|
||||
name ??= string.Empty;
|
||||
|
||||
|
||||
//No generamos alertas si viene onlyByName
|
||||
if (name == "Resp_Mode")
|
||||
obs = CalculateVentilationMode(obs);
|
||||
|
||||
if (name is "Sattc" or "FiO2") await CalculateSf(obs, name);
|
||||
|
||||
if (name is "FiO2" or "P_VAM" or "PaO2_Tidal") await CalculateOxygenationIndex(obs, name);
|
||||
|
||||
if (name is "FiO2" or "PaO2_Tidal") await CalculatePf(obs, name);
|
||||
|
||||
if (name == "TEST_ALARMA") await _alarmService.Value.CalculateAlarmTest(obs, name);
|
||||
|
||||
obs = await CheckObsWithSameTimeExistsAndIncrementTime(obs);
|
||||
|
||||
if (obs is PatientObservation pobs)
|
||||
{
|
||||
pobs = await CheckAlarmConfig(pobs);
|
||||
return pobs as T;
|
||||
}
|
||||
|
||||
return obs as T;
|
||||
}
|
||||
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (pumpObservation.Status is PumpEnum.Status.Alarm or PumpEnum.Status.Warning)
|
||||
{
|
||||
PatientObservation? patientObservation = null;
|
||||
string? name = null;
|
||||
|
||||
//La oclusión es para todos los tipos
|
||||
var volumetricAirOcclusionAlarm = _apiSettings.VolumetricAirOcclusionAlarm ?? null;
|
||||
|
||||
if (volumetricAirOcclusionAlarm != null && pumpObservation.AlarmType != null &&
|
||||
volumetricAirOcclusionAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
|
||||
{
|
||||
name = "VolumetricAirOcclusion";
|
||||
//alerta oclusión aire para bombas volumétricas
|
||||
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
|
||||
}
|
||||
|
||||
|
||||
var listInotropicMedicines = _apiSettings.InotropicMedicines ?? null;
|
||||
|
||||
if (listInotropicMedicines is { Count: > 0 } && pumpObservation.DrugName != null &&
|
||||
listInotropicMedicines.Contains(pumpObservation.DrugName)) //INOTRÓPICOS
|
||||
{
|
||||
var inotropicEndInfusionAlarm = _apiSettings.InotropicEndInfusionAlarm ?? null;
|
||||
|
||||
if (inotropicEndInfusionAlarm != null && pumpObservation.AlarmType != null &&
|
||||
inotropicEndInfusionAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
|
||||
{
|
||||
//Inotrópicos próxima a fin de infusión
|
||||
// Y
|
||||
//proximo fin de infusión, oclusión, y presencia aire….
|
||||
//Solo inotrópicos
|
||||
//Está en la línea 19 del excel y abarca la que hay en la línea 9
|
||||
// TODO: revisar en los logs que nombre de type (Buscar por crud xmlns)
|
||||
name = "InotropicEndInfusion";
|
||||
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var pumPressureAlarm = _apiSettings.PumPressureAlarm ?? null;
|
||||
|
||||
if (pumPressureAlarm != null && pumpObservation.AlarmType != null &&
|
||||
pumPressureAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
|
||||
{
|
||||
name = "Pressure";
|
||||
//alerta de presión(la alerta de la bomba se programa + -30mmHg por encima de la presión de la línea).
|
||||
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
|
||||
}
|
||||
|
||||
var endContinuousInfusionAlarm = _apiSettings.EndContinuousInfusionAlarm ?? null;
|
||||
|
||||
if (endContinuousInfusionAlarm != null && pumpObservation.AlarmType != null &&
|
||||
endContinuousInfusionAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
|
||||
{
|
||||
name = "EndContinuousInfusion";
|
||||
//próximo a fin de infusión /fin de infusión
|
||||
//información de bombas solo perfusión continua
|
||||
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
|
||||
}
|
||||
|
||||
var volumetricAirInLineAlarm = _apiSettings.VolumetricAirInLineAlarm ?? null;
|
||||
|
||||
if (volumetricAirInLineAlarm != null && pumpObservation.AlarmType != null &&
|
||||
volumetricAirInLineAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
|
||||
{
|
||||
name = "VolumetricAirInLine";
|
||||
//alerta aire para bombas volumétricas (burbujas de aire para la bomba)
|
||||
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (patientObservation != null && name != null)
|
||||
{
|
||||
_ = _observationService.Value.InsertObservation(patientObservation);
|
||||
_ = _alarmService.Value.SendAlarm(patientObservation, name, AlarmEnum.Name.Pump,
|
||||
AlarmEnum.Severity.None, AlarmEnum.Type.Auto);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error mapping alarm pump observation. Exception: {exMessage}", ex.Message);
|
||||
}
|
||||
|
||||
return pumpObservation;
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newObservation.Name))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Error FixTimeInconsistencyWithLast new observation name is null or empty: {newObservation}.",
|
||||
newObservation);
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
var lasObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
|
||||
[newObservation.Name]);
|
||||
var lastObservation = lasObservations.FirstOrDefault();
|
||||
|
||||
if (lastObservation != null && DateTime.Compare(
|
||||
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
|
||||
lastObservation.Time.Day, lastObservation.Time.Hour,
|
||||
lastObservation.Time.Minute, lastObservation.Time.Second),
|
||||
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
|
||||
newObservation.Time.Day, newObservation.Time.Hour,
|
||||
newObservation.Time.Minute, newObservation.Time.Second)
|
||||
) >= 0)
|
||||
newObservation.Time = lastObservation.Time.AddSeconds(1);
|
||||
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToMap)
|
||||
{
|
||||
var mappedObsList = listToMap
|
||||
.Select(obs => obs.DeepCopy())
|
||||
.Select(obs => _configObservationService.Map(obs).Result)
|
||||
.Where(obs => obs != null)
|
||||
.ToList();
|
||||
|
||||
// mappedObsList contains observation mapped
|
||||
// Check blue code
|
||||
|
||||
_ = CalculateBlueCodeList(mappedObsList);
|
||||
|
||||
|
||||
return Task.FromResult(listToMap);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(alarmToInsert.EventId))
|
||||
obs.Code = alarmToInsert.EventId;
|
||||
|
||||
if (!string.IsNullOrEmpty(alarmToInsert.Event))
|
||||
obs.Name = alarmToInsert.Event;
|
||||
|
||||
obs.Value = alarmToInsert.Value;
|
||||
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
Task ICalculatedObservations.SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
private async Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code, AlarmEnum.Type type)
|
||||
{
|
||||
await _alarmService.Value.SendAlarm(obs, name, code, AlarmEnum.Severity.None, type);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initial check to ignore old observations generating blue codes
|
||||
/// </summary>
|
||||
/// <param name="obs">Observation</param>
|
||||
/// <returns>True if it needs to be ignored</returns>
|
||||
private bool CheckIfObservationIsOlderAndShouldBeIgnored(PatientObservation obs)
|
||||
{
|
||||
if (obs.Time.ToUniversalTime().AddMinutes(_apiSettings.IgnoreCalcObservationsOlderInMinutesThan) <
|
||||
DateTime.UtcNow)
|
||||
{
|
||||
Log.Debug(
|
||||
"ignoring calc old observation: name:{obsName} obs_time:{obsTime} system_time {DateTimeNow} id:{obsId}",
|
||||
obs.Name ?? "null", obs.Time, DateTime.Now, obs.Id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static PatientObservation CreateAlarmObservation(string name, PatientObservation pobs)
|
||||
{
|
||||
return new PatientObservation
|
||||
{
|
||||
CodingSystem = "ADAS_ALARM",
|
||||
Code = name,
|
||||
Name = $"Alarm_{name}",
|
||||
Value = pobs.Value,
|
||||
PatientId = pobs.PatientId,
|
||||
Time = pobs.Time,
|
||||
Alarm = pobs.Alarm
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a blue code observation Lists needs to be generated and inserts it.
|
||||
/// </summary>
|
||||
/// <param name="obsList"></param>
|
||||
public async Task CalculateBlueCodeList(List<PatientObservation?>? obsList)
|
||||
{
|
||||
//Las 3 observaciones vienen siempre juntas en el mismo mensaje
|
||||
//Solo se comprueba si se cumplen 2 de las 3 condiciones
|
||||
|
||||
var condition = 0;
|
||||
var patientId = obsList?.FirstOrDefault()?.PatientId;
|
||||
if (patientId == null) return;
|
||||
|
||||
var fcObs = obsList?.FirstOrDefault(o => o is { Name: "FC" });
|
||||
var sattcObs = obsList?.FirstOrDefault(o => o is { Name: "Sattc" });
|
||||
var tamObs = obsList?.FirstOrDefault(o => o is { Name: "TAm" });
|
||||
|
||||
// FC < 60
|
||||
if (fcObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(fcObs) &&
|
||||
double.TryParse(fcObs.Value.ToString(), out var cardiacBeatRate) && cardiacBeatRate < 60)
|
||||
{
|
||||
Log.Debug("BlueCode condition: FC {fc} for patient: {obsPatientid} fc increase limit", cardiacBeatRate,
|
||||
fcObs.PatientId);
|
||||
condition++;
|
||||
}
|
||||
|
||||
// SpO2 < 80 (Sattc)
|
||||
if (sattcObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(sattcObs) &&
|
||||
double.TryParse(sattcObs.Value.ToString(), out var spO2Value) && spO2Value < 80)
|
||||
{
|
||||
Log.Debug("Blue code condition: SpO2 {sp} for patient: {obsPatientid} Sattc increase limit", spO2Value,
|
||||
sattcObs.PatientId);
|
||||
condition++;
|
||||
}
|
||||
|
||||
if (condition == 0) return;
|
||||
|
||||
// Early exit if 2 conditions are met
|
||||
if (condition == 2)
|
||||
{
|
||||
_ = SendBlueCode(fcObs);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TAm actual es un 50% por debajo de la medición de 30 seg antes
|
||||
if (tamObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(tamObs) &&
|
||||
double.TryParse(tamObs.Value.ToString(), out var tamObsValue))
|
||||
{
|
||||
// Buscamos el anterior
|
||||
var lastPressBloodObsList =
|
||||
await _observationService.Value.FindLastObservations(tamObs.PatientId, 1, _pressBloodArteryMean);
|
||||
var lastPressBloodObs = lastPressBloodObsList.FirstOrDefault();
|
||||
|
||||
// Diferencia entre la última observación es < 30 s
|
||||
if (lastPressBloodObs != null &&
|
||||
double.TryParse(lastPressBloodObs.Value.ToString(), out var lastPressBloodValue) &&
|
||||
(tamObs.Time - lastPressBloodObs.Time).TotalSeconds <= 35 &&
|
||||
tamObsValue <= lastPressBloodValue / 2)
|
||||
{
|
||||
Log.Debug("Blue code condition: TAm decrease limit over 30s for patient: {obsPatientid}",
|
||||
tamObs.PatientId);
|
||||
condition++;
|
||||
}
|
||||
}
|
||||
|
||||
if (condition >= 2) _ = SendBlueCode(tamObs);
|
||||
}
|
||||
|
||||
|
||||
private async Task SendBlueCode(PatientObservation? pobs)
|
||||
{
|
||||
if (pobs == null)
|
||||
return;
|
||||
|
||||
Log.Debug("Blue code Alarm for patient: {obsPatientid}", pobs.PatientId);
|
||||
|
||||
var blueCodeObservation = CreateAlarmObservation("BlueCode", pobs);
|
||||
|
||||
blueCodeObservation = await CheckAlarmConfig(blueCodeObservation);
|
||||
|
||||
_ = _observationService.Value.InsertObservation(blueCodeObservation);
|
||||
|
||||
_ = SendAlarm(blueCodeObservation, "BlueCode", AlarmEnum.Name.Blue, AlarmEnum.Type.Auto);
|
||||
}
|
||||
|
||||
|
||||
//PRVC : donde aparezca cambiar y mostrar en su lugar: VCRP
|
||||
//FLUJO ALTO: poner OAF en su lugar
|
||||
private BasePatientObservationValue CalculateVentilationMode(BasePatientObservationValue obs)
|
||||
{
|
||||
var value = obs is PatientObservationAlarm oAlarm ? oAlarm.Value
|
||||
: obs is PatientObservation o ? o.Value : null;
|
||||
|
||||
|
||||
if (value != null && value.ToString()!.Contains("PRVC"))
|
||||
obs.Value = value.ToString()!.Replace("PRVC", "VCRP");
|
||||
else if (value != null && value.ToString()!.Contains("FLUJ.ALTO")) obs.Value = "OAF";
|
||||
|
||||
_logger.LogDebug("Calculating patient id: {id} ventilation mode: {obsName}, value : {obsValue}", obs.PatientId,
|
||||
obs.Name, obs.Value);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
//TODO duplicado con el calculated del 12o, sacar a un utils
|
||||
private async Task<BasePatientObservationValue> CheckObsWithSameTimeExistsAndIncrementTime(
|
||||
BasePatientObservationValue obs)
|
||||
{
|
||||
if (obs is not PatientObservation pobs) return obs;
|
||||
|
||||
var existObsWithSameTime =
|
||||
await _observationService.Value.FindAnyWithSameDate(pobs.PatientId, pobs.Time, pobs.Name);
|
||||
|
||||
if (existObsWithSameTime != null && existObsWithSameTime.Any()) pobs.Time = pobs.Time.AddSeconds(1);
|
||||
|
||||
return pobs;
|
||||
}
|
||||
|
||||
//S/F Calc ( craneal saturation / FiO2
|
||||
private async Task CalculateSf(BasePatientObservationValue obs, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
PatientObservation? saturation = null;
|
||||
PatientObservation? fio2 = null;
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["FiO2", "Sattc"]);
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "FiO2":
|
||||
fio2 = (PatientObservation)obs;
|
||||
saturation = values.FirstOrDefault(o => o.Name == "Sattc");
|
||||
break;
|
||||
|
||||
case "Sattc":
|
||||
saturation = (PatientObservation)obs;
|
||||
fio2 = values.FirstOrDefault(o => o.Name == "FiO2");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fio2 != null && saturation != null && !fio2.Expired &&
|
||||
double.TryParse(fio2.Value.ToString(), out var fio) && !saturation.Expired &&
|
||||
double.TryParse(saturation.Value.ToString(), out var sat) && sat <= 97)
|
||||
{
|
||||
if (fio == 0)
|
||||
{
|
||||
_logger.LogError("Error calculating SF. FiO2: {fio}", fio);
|
||||
return;
|
||||
}
|
||||
|
||||
var sfValue = sat / fio;
|
||||
|
||||
sfValue = Math.Round(sfValue, 4);
|
||||
|
||||
var sfObs = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Name = "Sattc_FiO2",
|
||||
CodingSystem = "ADAS",
|
||||
Value = sfValue,
|
||||
Time = obs.Time
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(sfObs);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error calculate S/F observation. Exception: {exMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Oxygenation index is calculated => P_VAM x FiO2 x 100 / PaO2_Tidal
|
||||
/// the method is called when it receives P_VAM or FiO2 or PaO2_Tidal and tries to take the other values to perform the
|
||||
/// calculation if they are not
|
||||
/// in database then does nothing.
|
||||
/// </summary>
|
||||
public async Task CalculateOxygenationIndex(BasePatientObservationValue obs, string name)
|
||||
{
|
||||
var toSearchList = new List<string>();
|
||||
PatientObservation? pVam = null;
|
||||
PatientObservation? fiO2 = null;
|
||||
PatientObservation? paO2 = null;
|
||||
|
||||
toSearchList.AddRange(new List<string> { "P_VAM", "FiO2", "PaO2_Tidal" });
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
|
||||
|
||||
try
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "P_VAM":
|
||||
pVam = (PatientObservation)obs;
|
||||
fiO2 = values.FirstOrDefault(o => o.Name == "FiO2");
|
||||
paO2 = values.FirstOrDefault(o => o.Name == "PaO2_Tidal");
|
||||
break;
|
||||
|
||||
case "FiO2":
|
||||
fiO2 = (PatientObservation)obs;
|
||||
pVam = values.FirstOrDefault(o => o.Name == "P_VAM");
|
||||
paO2 = values.FirstOrDefault(o => o.Name == "PaO2_Tidal");
|
||||
break;
|
||||
|
||||
case "PaO2_Tidal":
|
||||
paO2 = (PatientObservation)obs;
|
||||
fiO2 = values.FirstOrDefault(o => o.Name == "FiO2");
|
||||
pVam = values.FirstOrDefault(o => o.Name == "P_VAM");
|
||||
break;
|
||||
}
|
||||
|
||||
//Controlar que FiO2 no haya expirada al recogerla
|
||||
if (fiO2 != null && paO2 != null && pVam != null &&
|
||||
int.TryParse(pVam.Value.ToString(), out var pvam) &&
|
||||
int.TryParse(fiO2.Value.ToString(), out var fio2) &&
|
||||
int.TryParse(paO2.Value.ToString(), out var pao2))
|
||||
{
|
||||
if (pao2 == 0)
|
||||
{
|
||||
_logger.LogError(
|
||||
"Error calculate Oxygenation Index observation. PaO2: {pao2}, observation: {obsName}", pao2,
|
||||
name);
|
||||
return;
|
||||
}
|
||||
|
||||
var oxygenationIndexValue = pvam * fio2 * 100 / pao2;
|
||||
|
||||
var oxygenationIndexObs = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Name = "Oxygenation_Index",
|
||||
CodingSystem = "ADAS",
|
||||
Value = oxygenationIndexValue,
|
||||
Time = obs.Time
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(oxygenationIndexObs);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error calculate Oxygenation Index observation. Exception: {exMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
//P/F Calc ( PaO2_Tidal / FiO2
|
||||
private async Task CalculatePf(BasePatientObservationValue obs, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
PatientObservation? paO2 = null;
|
||||
PatientObservation? fio2 = null;
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
|
||||
["FiO2", "PaO2_Tidal"]);
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "FiO2":
|
||||
fio2 = (PatientObservation)obs;
|
||||
paO2 = values.FirstOrDefault(o => o.Name == "PaO2_Tidal");
|
||||
break;
|
||||
|
||||
case "PaO2_Tidal":
|
||||
paO2 = (PatientObservation)obs;
|
||||
fio2 = values.FirstOrDefault(o => o.Name == "FiO2");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fio2 != null && paO2 != null && !fio2.Expired && !paO2.Expired &&
|
||||
double.TryParse(paO2.Value.ToString(), out var pa) &&
|
||||
double.TryParse(fio2.Value.ToString(), out var fi))
|
||||
{
|
||||
if (fi == 0)
|
||||
{
|
||||
_logger.LogError("Error Calculating PF. FiO2 value is 0. Observation:{obs}", obs);
|
||||
return;
|
||||
}
|
||||
|
||||
var sfValue = pa / fi;
|
||||
|
||||
sfValue = Math.Round(sfValue, 4);
|
||||
|
||||
var sfObs = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Name = "PaO2_FiO2",
|
||||
CodingSystem = "ADAS",
|
||||
Value = sfValue,
|
||||
Time = obs.Time
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(sfObs);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error calculate P/F observation. Exception: {exMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<PatientObservation?> CreatePumpAlarmObservation(string name,
|
||||
PumpObservation pumpObservation)
|
||||
{
|
||||
var positionPump = pumpObservation.IsAux != null && !pumpObservation.IsAux.Value
|
||||
? $"Rack {pumpObservation.GatewayNumber} Bomba {pumpObservation.Number}"
|
||||
: $"Rack Aux {pumpObservation.GatewayNumber} Bomba {pumpObservation.Number}";
|
||||
|
||||
|
||||
|
||||
PatientObservation patientObservation = new()
|
||||
{
|
||||
CodingSystem = "ADAS_ALARM",
|
||||
Code = $"Pump_{name}",
|
||||
Name = $"Alarm_Pump_{name}",
|
||||
Value = pumpObservation.DrugName ?? positionPump,
|
||||
Time = pumpObservation.Time
|
||||
};
|
||||
|
||||
if (pumpObservation.PatientId != null)
|
||||
patientObservation.PatientId = pumpObservation.PatientId.Value;
|
||||
|
||||
return await CheckAlarmConfig(patientObservation);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,883 @@
|
||||
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.MongoModels;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.HRYC;
|
||||
|
||||
public class CalculatedObservations : ICalculatedObservations
|
||||
{
|
||||
private readonly IOptions<ApiSettings> _apiSettings;
|
||||
private readonly List<string> _codesForInvasiveVentilation = [];
|
||||
|
||||
private readonly IConfigObservationService _configObservationService;
|
||||
|
||||
//private readonly Lazy<IBoxService> _boxService;
|
||||
private readonly List<string> _highFrequencyVentilation = [];
|
||||
private readonly Lazy<ILightBeaconService> _lightBeaconService;
|
||||
private readonly ILogger<CalculatedObservations> _logger;
|
||||
private readonly List<string> _nonInvasiveVentilation = [];
|
||||
private readonly Lazy<IObservationService> _observationService;
|
||||
private readonly Lazy<IPatientService> _patientService;
|
||||
|
||||
|
||||
public CalculatedObservations(IServiceProvider serviceProvider)
|
||||
{
|
||||
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>(); //observationService;
|
||||
_patientService = serviceProvider.GetRequiredService<Lazy<IPatientService>>();
|
||||
_apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;
|
||||
//_boxService = serviceProvider.GetRequiredService<Lazy<IBoxService>>();
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
_lightBeaconService = serviceProvider.GetRequiredService<Lazy<ILightBeaconService>>();
|
||||
_configObservationService =
|
||||
serviceProvider.GetRequiredService<IConfigObservationService>(); //configObservationService;
|
||||
|
||||
|
||||
var highFrenquencyVentilation = _apiSettings.Value.HighFrequencyVentilation ?? null;
|
||||
highFrenquencyVentilation?.ForEach(x => _highFrequencyVentilation.Add(x.Trim()));
|
||||
|
||||
|
||||
var nonInvasiveVentilation = _apiSettings.Value.NonInvasiveVentilation ?? null;
|
||||
nonInvasiveVentilation?.ForEach(x => _nonInvasiveVentilation.Add(x.Trim()));
|
||||
|
||||
var codesForInvasiveVentilation = //TODO comprobar si se refiere a InvasiveVentilation
|
||||
_apiSettings.Value.InvasiveVentilation ?? null;
|
||||
codesForInvasiveVentilation?.ForEach(x => _codesForInvasiveVentilation.Add(x.Trim()));
|
||||
}
|
||||
|
||||
|
||||
public async Task<T?> Map<T>(T obs, bool onlyByName) where T : BasePatientObservation
|
||||
{
|
||||
_logger.LogTrace("Mapping {name} observation {obs}", obs.Name, obs);
|
||||
|
||||
switch (obs.Name)
|
||||
{
|
||||
case "Resp_Mode":
|
||||
_logger.LogTrace("Mapping Resp mode observation {obs}", obs);
|
||||
await CalculateVentilationMode(obs);
|
||||
break;
|
||||
case "Diuresis":
|
||||
case "Weight_Current":
|
||||
if (obs.Name == "Weight_Current") await CalculateWeight_DiffObservation(obs);
|
||||
await CalculateDiureis_WeightObservation(obs);
|
||||
break;
|
||||
case "AllergiesObs":
|
||||
await CalculateAllergiesObservation(obs);
|
||||
break;
|
||||
case "DrainagesObs":
|
||||
await CalculateDrainagesObservation(obs);
|
||||
break;
|
||||
case "PEEP":
|
||||
case "Pleateu_Pressure":
|
||||
await CalculateDelta_PressureObservation(obs);
|
||||
break;
|
||||
case "Daily_Balance":
|
||||
await CalculateDaily_BalanceObservation(obs);
|
||||
break;
|
||||
case "Hydric_Balance":
|
||||
await CalculateHydricBalanceCalculated(obs);
|
||||
obs = (T)await CalculateHydricBalance(obs);
|
||||
break;
|
||||
case "Hour_Balance":
|
||||
obs = (T)await CalculateHourBalance(obs);
|
||||
break;
|
||||
case "FR":
|
||||
await CalculateRespRate(obs);
|
||||
await CalculateIrox(obs);
|
||||
break;
|
||||
case "Vent_Rate":
|
||||
await CalculateRespRate(obs);
|
||||
break;
|
||||
case "SpO2":
|
||||
case "FiO2":
|
||||
await CalculateIrox(obs);
|
||||
break;
|
||||
case "NEWS":
|
||||
await CalculateNews(obs);
|
||||
break;
|
||||
}
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
return Task.FromResult(new List<PatientTreatment?>().AsEnumerable());
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
return Task.FromResult(diagnosis);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newObservation.Name))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Error FixTimeInconsistencyWithLast newObservation name ia null or empty. Observation: {newObservation}",
|
||||
newObservation);
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
var lastObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
|
||||
[newObservation.Name]);
|
||||
var lastObservation = lastObservations.FirstOrDefault();
|
||||
|
||||
if (lastObservation != null && DateTime.Compare(
|
||||
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
|
||||
lastObservation.Time.Day, lastObservation.Time.Hour,
|
||||
lastObservation.Time.Minute, lastObservation.Time.Second),
|
||||
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
|
||||
newObservation.Time.Day, newObservation.Time.Hour,
|
||||
newObservation.Time.Minute, newObservation.Time.Second)
|
||||
) >= 0)
|
||||
newObservation.Time = lastObservation.Time.AddSeconds(1);
|
||||
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
public async Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
var ventRate = listToInsert.FirstOrDefault(obs => obs.Name == "MDC_VENT_RESP_RATE");
|
||||
var respRate = listToInsert.FirstOrDefault(obs => obs.Name == "MDC_RESP_RATE");
|
||||
|
||||
// Check if obs for Vent_Rate and FR is on the list to insert
|
||||
if (ventRate == null || respRate == null) return listToInsert;
|
||||
|
||||
var ventRateValue = double.TryParse(ventRate.Value.ToString(), out var v1) ? v1 : 0;
|
||||
// ventRate is on the list to insert but its value is 0
|
||||
if (ventRateValue == 0)
|
||||
{
|
||||
_logger.LogDebug("Find ventRate on PreMap request not processed because its value is 0");
|
||||
return listToInsert;
|
||||
}
|
||||
|
||||
var respRateValue = double.TryParse(ventRate.Value.ToString(), out var v2) ? v2 : 0;
|
||||
// respRate is on the list to insert but its value is 0
|
||||
if (respRateValue == 0)
|
||||
{
|
||||
_logger.LogDebug("Find respRate on PreMap request not processed because its value is 0");
|
||||
return listToInsert;
|
||||
}
|
||||
|
||||
|
||||
// Exists both obs and have value != 0
|
||||
// In that case we need to calculate Resp_Rate_Calculated just with the value of Vent_Rate
|
||||
try
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Find on PreMap Vent_Rate and FR in the same request process Vent_Rate first to calculate Resp_Rate_Calculated");
|
||||
await _observationService.Value.InsertObservation(ventRate);
|
||||
listToInsert.Remove(ventRate);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error while PreMap on insert obs: {ventRate} exception: {e}", ventRate, e);
|
||||
}
|
||||
|
||||
return listToInsert;
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IF news greater or equal than 5 and less than 7 is warning beacon and if greater or equal than 7 alert
|
||||
/// </summary>
|
||||
/// <param name="obs"></param>
|
||||
private async Task CalculateNews(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
var parsed = int.TryParse(pobs.Value.ToString(), out var valueParsed);
|
||||
var patient = await _patientService.Value.FindById(obs.PatientId);
|
||||
if (patient == null) return;
|
||||
//var beaconConfig = await _boxService.Value.GetBeaconConfig(new PatientLocation(patient.PointOfCare, patient.Bed));
|
||||
|
||||
//power off beacon
|
||||
|
||||
//recuperar la beacon del paciente y encenderla apagarla si el valor es > 5&6 o 7 alert
|
||||
|
||||
if ((parsed && valueParsed < 5) || !parsed)
|
||||
//await _balizaService.Value.PowerOffLed(beaconCnf);
|
||||
//await _observationService.Value.SendObsBroadcast(obs);
|
||||
await SendAlarm(obs, AlarmEnum.Name.NewsOff);
|
||||
else
|
||||
switch (valueParsed)
|
||||
{
|
||||
case >= 5 and < 7:
|
||||
_logger.LogDebug("beacon yellow alert by NEWS for patient: {patientid}", obs.PatientId);
|
||||
await SendAlarm(obs, AlarmEnum.Name.NewsWarning);
|
||||
//await _balizaService.Value.SendBeaconColor(patient, LightBeaconColor.YELLOW);
|
||||
break;
|
||||
case >= 7:
|
||||
_logger.LogDebug("beacon red alert by NEWS for patient: {patientid}", obs.PatientId);
|
||||
await SendAlarm(obs, AlarmEnum.Name.NewsAlert);
|
||||
//await _balizaService.Value.SendBeaconColor(patient, LightBeaconColor.RED);
|
||||
break;
|
||||
}
|
||||
|
||||
//if (obsAlarm == null) return;
|
||||
}
|
||||
|
||||
private async Task SendAlarm(BasePatientObservation obs, AlarmEnum.Name name)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
PatientObservation nObs = new()
|
||||
{
|
||||
CodingSystem = "ADAS_ALARM",
|
||||
Code = name.ToString(),
|
||||
Name = $"Alarm_{name}",
|
||||
PatientId = pobs.PatientId,
|
||||
Time = pobs.Time
|
||||
};
|
||||
try
|
||||
{
|
||||
//ConfigObservations
|
||||
var configObs = await _configObservationService.Get(new PatientObservation
|
||||
{
|
||||
Name = nObs.Name,
|
||||
PatientId = obs.PatientId
|
||||
}
|
||||
);
|
||||
if (configObs == null)
|
||||
{
|
||||
_logger.LogError("Config observation is null on send alarm NEWS for {nObs}", nObs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (configObs is { Alarm.Enabled: true })
|
||||
{
|
||||
nObs.Alarm = configObs.Alarm;
|
||||
|
||||
if (configObs.Alarm.Beacon is { Enabled: true })
|
||||
{
|
||||
var patient = await _patientService.Value.FindById(obs.PatientId);
|
||||
if (patient != null)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"PatientId: {nObsPatientId}. Send Beacon alarmName {configObsAlarmBeaconBeaconColorValue}",
|
||||
nObs.PatientId, configObs.Alarm.Beacon.BeaconColor);
|
||||
nObs.Value = configObs.Alarm.Beacon.BeaconColor.ToString();
|
||||
SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug(
|
||||
"Insert obs alarm for news at: {DateTimeNow} patientId: {nObsPatientId} color value: {nObsValue}",
|
||||
DateTime.Now, nObs.PatientId, nObs.Value);
|
||||
await _observationService.Value.InsertObservation(nObs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception sending alarm: {exMessage}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendBeaconCode(AlarmEnum.BeaconColor color, Patient patient)
|
||||
{
|
||||
if (!patient.PointOfCareId.HasValue)
|
||||
{
|
||||
_logger.LogError("Error sending beacon color on calculateObservations poc id on patient is null {Patient}",
|
||||
patient.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//(SpO2/FiO2)/FR IROX formula. Only calculate when all observations are in last 10 minutes.
|
||||
private async Task CalculateIrox(BasePatientObservation obs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pob = (PatientObservation)obs;
|
||||
var obsToCalc = new List<PatientObservation> { pob };
|
||||
switch (pob.Name)
|
||||
{
|
||||
case "SpO2":
|
||||
|
||||
var fio2Frvalues = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
|
||||
["FiO2", "FR"]);
|
||||
if (fio2Frvalues.Count > 0) obsToCalc.AddRange(fio2Frvalues);
|
||||
|
||||
break;
|
||||
case "FiO2":
|
||||
var spo2Frvalues = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
|
||||
["SpO2", "FR"]);
|
||||
if (spo2Frvalues.Count > 0) obsToCalc.AddRange(spo2Frvalues);
|
||||
|
||||
break;
|
||||
case "FR":
|
||||
var fio2Spo2Values = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
|
||||
["FiO2", "SpO2"]);
|
||||
|
||||
if (fio2Spo2Values.Count > 0) obsToCalc.AddRange(fio2Spo2Values);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!obsToCalc.All(o => o.Time.CompareTo(DateTime.UtcNow.AddMinutes(10)) <= 0)) return;
|
||||
|
||||
var fio2 = obsToCalc.FirstOrDefault(o => o.Name == "FiO2");
|
||||
var fr = obsToCalc.FirstOrDefault(o => o.Name == "FR");
|
||||
var spo2 = obsToCalc.FirstOrDefault(o => o.Name == "SpO2");
|
||||
if (fio2 == null || fr == null || spo2 == null) return;
|
||||
|
||||
|
||||
if (!double.TryParse(spo2.Value.ToString(), out var nSpo2) ||
|
||||
!double.TryParse(fio2.Value.ToString(), out var nFio2) ||
|
||||
!double.TryParse(fr.Value.ToString(), out var nFr))
|
||||
{
|
||||
_logger.LogWarning("Error parsing values. fio2: {fio2}, fr: {fr}, spo2: {spo2}", fio2, fr, spo2);
|
||||
return;
|
||||
}
|
||||
|
||||
var spo2Value = nSpo2;
|
||||
var fio2Value = nFio2;
|
||||
var frValue = nFr;
|
||||
|
||||
if (spo2Value == 0 || fio2Value == 0 || frValue == 0) return;
|
||||
|
||||
|
||||
var value = nSpo2 / nFio2 / nFr;
|
||||
|
||||
var irox = new PatientObservation
|
||||
{
|
||||
Name = "IROX",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time,
|
||||
Value = value
|
||||
};
|
||||
await _observationService.Value.InsertObservation(irox);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Exception calculating IROX, error: {eMessage}", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
RespRate solo se tiene que poner cuando no hay ningún ventRate en los últimos min y veinte segundos y el último no es un 0.
|
||||
|
||||
*/
|
||||
|
||||
//static readonly SemaphoreSlim semaphoreCalculateRespRate = new(1, 1);
|
||||
private async Task CalculateRespRate(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
_logger.LogDebug("CalculateRespRate {obsPatientid} wait {obsName} {obsTime} {pobsValue}", obs.PatientId,
|
||||
obs.Name, obs.Time, pobs.Value);
|
||||
if (Convert.ToDouble(pobs.Value) == 0) return;
|
||||
|
||||
if (Convert.ToDouble(pobs.Value) == 0) return;
|
||||
|
||||
var insert = true;
|
||||
if (obs.Name == "FR")
|
||||
{
|
||||
// Insertamos si no existe una obs Vent_Rate o si es existe tiene más de X" y su valor es > 0
|
||||
var lastVentRate =
|
||||
(await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["Vent_Rate"]))
|
||||
.FirstOrDefault();
|
||||
if (lastVentRate == null || (double.TryParse(lastVentRate.Value.ToString(), out var v) ? v : 0) == 0)
|
||||
{
|
||||
_logger.LogDebug("CalculateRespRate {id} lastVentRate: null", obs.PatientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var diff = DateTime.UtcNow.Subtract(lastVentRate.Time.ToUniversalTime());
|
||||
insert =
|
||||
lastVentRate.Time.ToUniversalTime().AddMinutes(_apiSettings.Value.CalculateRespRateVentExpires) <
|
||||
DateTime.UtcNow;
|
||||
_logger.LogDebug(
|
||||
"CalculateRespRate {patientid} lastVentRate: {value} - {time} passed {diff} {insert}",
|
||||
obs.PatientId, lastVentRate.Value, lastVentRate.Time, diff, insert ? "Inserted" : "Not inserted");
|
||||
}
|
||||
}
|
||||
|
||||
if (insert)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"CalculateRespRate {patientid} Insert Resp_Rate_Calculated with {name} - {time} value {value}",
|
||||
obs.PatientId, obs.Name, obs.Time, pobs.Value);
|
||||
var calculatedRespRate = new PatientObservation
|
||||
{
|
||||
Id = new ObjectId(),
|
||||
Name = "Resp_Rate_Calculated",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Name == "FR" ? obs.Time : obs.Time.AddSeconds(1),
|
||||
Value = pobs.Value,
|
||||
ParentData = new ParentDataClass { Name = obs.Name }
|
||||
};
|
||||
await _observationService.Value.InsertObservation(calculatedRespRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"CalculateRespRate {patientid} Insert Resp_Rate_Calculated with {name} - {time} value {value}",
|
||||
obs.PatientId, obs.Name, obs.Time, pobs.Value);
|
||||
}
|
||||
|
||||
_logger.LogDebug("CalculateRespRate {patientid} release {name} {time} {value}", obs.PatientId,
|
||||
obs.Name, obs.Time, pobs.Value);
|
||||
}
|
||||
|
||||
private async Task<BasePatientObservation> CalculateHydricBalance(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
|
||||
var lastHydricBalanceFromHour =
|
||||
await _observationService.Value.FindLastBeforeDate(pobs.PatientId, pobs.Time.AddMinutes(59),
|
||||
"Hydric_Balance");
|
||||
|
||||
if (lastHydricBalanceFromHour != null &&
|
||||
DateTime.Compare(new DateTime(lastHydricBalanceFromHour.Time.Year, lastHydricBalanceFromHour.Time.Month,
|
||||
lastHydricBalanceFromHour.Time.Day, lastHydricBalanceFromHour.Time.Hour, 0, 0)
|
||||
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
|
||||
pobs.Time = lastHydricBalanceFromHour.Time.AddSeconds(1);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
private async Task<BasePatientObservation> CalculateHourBalance(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
|
||||
var lastHourBalanceFromHour =
|
||||
await _observationService.Value.FindLastBeforeDate(pobs.PatientId, pobs.Time.AddMinutes(59),
|
||||
"Hour_Balance");
|
||||
|
||||
if (lastHourBalanceFromHour != null &&
|
||||
DateTime.Compare(new DateTime(lastHourBalanceFromHour.Time.Year, lastHourBalanceFromHour.Time.Month,
|
||||
lastHourBalanceFromHour.Time.Day, lastHourBalanceFromHour.Time.Hour, 0, 0)
|
||||
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
|
||||
pobs.Time = lastHourBalanceFromHour.Time.AddSeconds(1);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
private async Task CalculateHydricBalanceCalculated(BasePatientObservation obs)
|
||||
{
|
||||
//Hydric_Balance_Calculated
|
||||
//No guardamos como calculadas nunca horas futuras. En el obx siempre vienen con la hora y los minutos a 00
|
||||
|
||||
|
||||
var pobs = (PatientObservation)obs;
|
||||
var nowUniversalTime = DateTime.Now.ToUniversalTime();
|
||||
//La hora de la observación es mayor que la actual. No generamos la calculada
|
||||
if (DateTime.Compare(new DateTime(obs.Time.Year, obs.Time.Month, obs.Time.Day, obs.Time.Hour, 0, 0),
|
||||
new DateTime(nowUniversalTime.Year, nowUniversalTime.Month, nowUniversalTime.Day, nowUniversalTime.Hour,
|
||||
0, 0)) > 0
|
||||
)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"HYDRIC BALANCE: La hora de la observación es mayor que la actual. No generamos la calculada {obs}",
|
||||
obs);
|
||||
return;
|
||||
}
|
||||
|
||||
//Para la calculada. Si la última que tenemos es de la hora actual y lo que viene es de una hora anterior no entra como calculada.
|
||||
var lastHydricBalanceList = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
|
||||
["Hydric_Balance_Calculated"]);
|
||||
var lastHydricBalance = lastHydricBalanceList.FirstOrDefault();
|
||||
|
||||
if (lastHydricBalance != null && lastHydricBalance.Time.Hour == DateTime.UtcNow.Hour &&
|
||||
obs.Time.Hour != DateTime.UtcNow.Hour)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"HYDRIC BALANCE: ultima que tenemos es de la hora actual y lo que viene es de una hora anterior no entra como calculada {obs}",
|
||||
obs);
|
||||
return;
|
||||
}
|
||||
|
||||
//Si el último hydric balance es de una hora posterior a la observación que acaba de llegar no entra como calculada.
|
||||
if (lastHydricBalance != null &&
|
||||
DateTime.Compare(new DateTime(lastHydricBalance.Time.Year, lastHydricBalance.Time.Month,
|
||||
lastHydricBalance.Time.Day, lastHydricBalance.Time.Hour, 0, 0)
|
||||
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 1
|
||||
)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"HYDRIC BALANCE: Si el ultimo hydric balance es de una hora posterior a la observación que acaba de llegar no entra como calculada. {obs}",
|
||||
obs);
|
||||
return;
|
||||
}
|
||||
|
||||
var calculatedHydricBalance = new PatientObservation
|
||||
{
|
||||
Name = "Hydric_Balance_Calculated",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time.AddSeconds(1), //To discriminate updates.
|
||||
Value = pobs.Value,
|
||||
Units = obs.Units
|
||||
};
|
||||
|
||||
|
||||
if (lastHydricBalance != null && DateTime.Compare(
|
||||
new DateTime(lastHydricBalance.Time.Year, lastHydricBalance.Time.Month, lastHydricBalance.Time.Day,
|
||||
lastHydricBalance.Time.Hour, 0, 0)
|
||||
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
|
||||
calculatedHydricBalance.Time = lastHydricBalance.Time.AddSeconds(1);
|
||||
|
||||
await _observationService.Value.InsertObservation(calculatedHydricBalance);
|
||||
}
|
||||
|
||||
private async Task CalculateVentilationMode(BasePatientObservation obs)
|
||||
{
|
||||
_logger.LogDebug("CalculateVentilationMode {obs}", obs);
|
||||
var pobs = (PatientObservation)obs;
|
||||
|
||||
var respTypeObs = new PatientObservation
|
||||
{
|
||||
Name = "Resp_Type",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time
|
||||
};
|
||||
//https://epigram.teamwork.com/#/tasks/34765750
|
||||
if (obs.Code != null && _codesForInvasiveVentilation.Contains(obs.Code))
|
||||
{
|
||||
//añadir que sea solo cuando el valor es invasiva
|
||||
respTypeObs.Value = nameof(RespirationType.Invasive);
|
||||
respTypeObs.Time = obs.Time.AddSeconds(1);
|
||||
await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
|
||||
}
|
||||
else
|
||||
{
|
||||
var strValue = pobs.Value.ToString();
|
||||
if (strValue == null)
|
||||
return;
|
||||
|
||||
//si es en espera tendrá que insertar que es en espera
|
||||
if (!"EnESPERA".Equals(strValue))
|
||||
{
|
||||
respTypeObs.Value = _highFrequencyVentilation.Contains(strValue)
|
||||
? nameof(RespirationType.HighFrequencyVentilation)
|
||||
: _nonInvasiveVentilation.Contains(strValue)
|
||||
? respTypeObs.Value = nameof(RespirationType.NonInvasive)
|
||||
: nameof(RespirationType.Invasive);
|
||||
|
||||
await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateWeight_DiffObservation(BasePatientObservation obs)
|
||||
{
|
||||
var toSearchList = new List<string>();
|
||||
|
||||
toSearchList.AddRange(new List<string> { "Weight_Current" });
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
|
||||
|
||||
var pWeight = values.FirstOrDefault(o => o.Name == "Weight_Current");
|
||||
|
||||
var newWeightObs = (PatientObservation)obs;
|
||||
|
||||
if (pWeight?.Value != null && double.TryParse(pWeight.Value.ToString(), out var weight) &&
|
||||
double.TryParse(newWeightObs.Value.ToString(), out var newWeight))
|
||||
{
|
||||
var weightDiffValue = Math.Round(newWeight - weight, 2);
|
||||
|
||||
var weightDiff = new PatientObservation
|
||||
{
|
||||
Name = "Weight_Diff",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time,
|
||||
Value = weightDiffValue,
|
||||
Units = obs.Units
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(weightDiff);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateDiureis_WeightObservation(BasePatientObservation obs)
|
||||
{
|
||||
var toSearchList = new List<string>();
|
||||
|
||||
PatientObservation? diuresisObs;
|
||||
PatientObservation? weightObs;
|
||||
|
||||
toSearchList.AddRange(new List<string> { "Diuresis", "Weight_Current" });
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
|
||||
|
||||
if (obs.Name == "Diuresis")
|
||||
{
|
||||
diuresisObs = (PatientObservation)obs;
|
||||
weightObs = values.FirstOrDefault(o => o.Name == "Weight_Current");
|
||||
}
|
||||
else
|
||||
{
|
||||
weightObs = (PatientObservation)obs;
|
||||
diuresisObs = values.FirstOrDefault(o => o.Name == "Diuresis");
|
||||
|
||||
if (diuresisObs == null || CheckExpired(diuresisObs)) return;
|
||||
}
|
||||
|
||||
if (weightObs?.Value != null)
|
||||
if (double.TryParse(weightObs.Value.ToString(), out var nWeight) && nWeight != 0 &&
|
||||
double.TryParse(diuresisObs.Value.ToString(), out var diuresis))
|
||||
{
|
||||
var diruesisWeightValue = Math.Round(diuresis / nWeight, 2);
|
||||
|
||||
var diruesisWeight = new PatientObservation
|
||||
{
|
||||
Name = "Diuresis_Weight",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time,
|
||||
Value = diruesisWeightValue,
|
||||
Units = "ml/kg"
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(diruesisWeight);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateAllergiesObservation(BasePatientObservation obs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
|
||||
var allergiesObs = new PatientObservation
|
||||
{
|
||||
Name = "Allergies",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time,
|
||||
MessageTime = pobs.MessageTime
|
||||
};
|
||||
|
||||
var patientAllergiesValues =
|
||||
new List<PatientAllergiesValue>((IEnumerable<PatientAllergiesValue>)pobs.Value);
|
||||
|
||||
var allergiesValues = patientAllergiesValues.GroupBy(o => o.Type)
|
||||
.Select(x =>
|
||||
new PatientAllergiesValue
|
||||
{
|
||||
Type = x.Key,
|
||||
Value = string.Join(", ", x.Select(v => v.Value)),
|
||||
Notes = string.Join(", ", x.Select(n => n.Notes))
|
||||
}).ToList();
|
||||
|
||||
|
||||
var farmacosType = false;
|
||||
var farmacosValues = new List<string>();
|
||||
var values = new List<string>();
|
||||
|
||||
foreach (var allergies in allergiesValues)
|
||||
{
|
||||
if (allergies.Value == null)
|
||||
continue;
|
||||
|
||||
var type = allergies.Type?.Replace("Alergia a ", "").Replace("Alergia ", "").ToUpper();
|
||||
|
||||
if (type != null)
|
||||
switch (type)
|
||||
{
|
||||
case "FÁRMACOS":
|
||||
farmacosType = true;
|
||||
farmacosValues.Add(allergies.Value.ToUpper());
|
||||
break;
|
||||
|
||||
default:
|
||||
values.Add(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (farmacosType)
|
||||
{
|
||||
values.Add($"FÁRMACOS ({string.Join(", ", farmacosValues)})");
|
||||
allergiesObs.Status = StatusEnum.Type.Alert;
|
||||
}
|
||||
|
||||
allergiesObs.Value = string.Join(", ", values);
|
||||
|
||||
await _observationService.Value.InsertObservation(allergiesObs);
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
_logger.LogError("CalculateAllergiesObservation {obs}", obs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateDrainagesObservation(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
|
||||
var patientDraingesValue = (PatientDrainagesValue)pobs.Value;
|
||||
|
||||
if (patientDraingesValue.Type == "Drenaje ventricular")
|
||||
{
|
||||
var dve = new PatientObservation
|
||||
{
|
||||
Name = "DVE",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time,
|
||||
Units = obs.Units
|
||||
};
|
||||
|
||||
var drainageHeight = new PatientObservation
|
||||
{
|
||||
Name = "Drainage_Height",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = obs.Time
|
||||
};
|
||||
|
||||
if (patientDraingesValue.Volume != null)
|
||||
{
|
||||
dve.Value = patientDraingesValue.Volume;
|
||||
await _observationService.Value.InsertObservation(dve);
|
||||
}
|
||||
|
||||
if (patientDraingesValue.Height != null)
|
||||
{
|
||||
drainageHeight.Value = patientDraingesValue.Height;
|
||||
await _observationService.Value.InsertObservation(drainageHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateDelta_PressureObservation(BasePatientObservation obs)
|
||||
{
|
||||
var toSearchList = new List<string>();
|
||||
|
||||
PatientObservation? peep;
|
||||
PatientObservation? pleateuPressure;
|
||||
|
||||
toSearchList.AddRange(new List<string> { "PEEP", "Pleateu_Pressure" });
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
|
||||
var time = obs.Time;
|
||||
double pleateuPressureValue = 0;
|
||||
|
||||
if (obs.Name == "PEEP")
|
||||
{
|
||||
peep = (PatientObservation)obs;
|
||||
pleateuPressure = values.FirstOrDefault(o => o.Name == "Pleateu_Pressure");
|
||||
|
||||
if (pleateuPressure?.Time != null && time.CompareTo(pleateuPressure.Time) > 0) time = pleateuPressure.Time;
|
||||
}
|
||||
else
|
||||
{
|
||||
pleateuPressure = (PatientObservation)obs;
|
||||
peep = values.FirstOrDefault(o => o.Name == "PEEP");
|
||||
|
||||
if (peep?.Time != null && time.CompareTo(peep.Time) > 0) time = peep.Time;
|
||||
}
|
||||
|
||||
if (peep?.Value != null && pleateuPressure?.Value != null)
|
||||
{
|
||||
var peepSuccess = double.TryParse(peep.Value.ToString(), out var peepValue);
|
||||
if (!peepSuccess) peepValue = 0;
|
||||
|
||||
if (!peepSuccess) pleateuPressureValue = 0;
|
||||
|
||||
var deltaPressure = Math.Round(pleateuPressureValue - peepValue, 2);
|
||||
|
||||
|
||||
var deltaPressureObs = new PatientObservation
|
||||
{
|
||||
Name = "Delta_Pressure",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = time,
|
||||
Value = deltaPressure
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(deltaPressureObs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateDaily_BalanceObservation(BasePatientObservation obs)
|
||||
{
|
||||
if (obs.Time.ToLocalTime().Hour == 8)
|
||||
{
|
||||
var dailyBalanceCalculated = new PatientObservation
|
||||
{
|
||||
Name = "Daily_Balance_Calculated",
|
||||
CodingSystem = "ADAS",
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.Now,
|
||||
Units = obs.Units,
|
||||
Value = ((PatientObservation)obs).Value
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(dailyBalanceCalculated);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CheckExpired(PatientObservation obs)
|
||||
{
|
||||
if (obs.Expires == null) return false;
|
||||
|
||||
var timeExpire = obs.Time.ToLocalTime().AddSeconds(Convert.ToDouble(obs.Expires));
|
||||
|
||||
return DateTime.Now.CompareTo(timeExpire) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
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.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Customizations.HUVH.UCIA;
|
||||
|
||||
public class CalculatedObservations : ICalculatedObservations
|
||||
{
|
||||
private const string CodingSystem = "ADAS";
|
||||
private readonly List<string>? _antibioticList;
|
||||
private readonly List<string>? _antidepressantsList;
|
||||
private readonly List<string>? _antihypertensivesList;
|
||||
private readonly List<string>? _antipsicoticList;
|
||||
private readonly List<string>? _anxiolyticsList;
|
||||
private readonly List<string>? _inotropicMedicines;
|
||||
private readonly ILogger<CalculatedObservations> _logger;
|
||||
private readonly Lazy<IMedicineService> _medicineService;
|
||||
private readonly List<string>? _neuroMedicationList;
|
||||
private readonly Lazy<IObservationService> _observationService;
|
||||
|
||||
private readonly List<string>? _sedationList;
|
||||
private readonly List<string>? _serumColloidList;
|
||||
private readonly List<string>? _serumCrystalloidList;
|
||||
|
||||
private readonly Lazy<ITreatmentService> _treatmentService;
|
||||
|
||||
public CalculatedObservations(IServiceProvider serviceProvider)
|
||||
{
|
||||
_treatmentService = serviceProvider.GetRequiredService<Lazy<ITreatmentService>>();
|
||||
_medicineService = serviceProvider.GetRequiredService<Lazy<IMedicineService>>();
|
||||
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>();
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
|
||||
var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;
|
||||
|
||||
_sedationList = apiSettings.Value.Sedation;
|
||||
_inotropicMedicines = apiSettings.Value.InotropicMedicines;
|
||||
_antibioticList = apiSettings.Value.AntibioticList;
|
||||
_anxiolyticsList = apiSettings.Value.AnxiolyticList;
|
||||
_antipsicoticList = apiSettings.Value.AntipsicoticList;
|
||||
_antidepressantsList = apiSettings.Value.AntidepressantsList;
|
||||
_neuroMedicationList = apiSettings.Value.NeuroMedicationList;
|
||||
_serumCrystalloidList = apiSettings.Value.SerumCrystalloidList;
|
||||
_serumColloidList = apiSettings.Value.SerumColloidList;
|
||||
_antihypertensivesList = apiSettings.Value.AntihypertensivesList;
|
||||
}
|
||||
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
if (obs.Name is "PSI" or "RASS" or "TS") _ = CalculateOverSedation(obs);
|
||||
|
||||
if (obs.Name is "Inspiratory_Pressure" or "Compliancia" or "Air_Flow") _ = CalculateVentilationMode(obs);
|
||||
|
||||
if (obs.Name is "ECMO_Location" or "Location_Drainage_Cannula" or "Location_Return_Cannula")
|
||||
obs = (T)await CalculateEcmoLocation(obs);
|
||||
|
||||
if (obs.Name is "EVN" or "ANI" or "ESCID") _ = CalculateOverAnalgesia(obs);
|
||||
|
||||
if (obs.Name is "Pmeset" or "PEEP") _ = CalculateDrivingPressure(obs);
|
||||
|
||||
if (obs.Name is "SpO2_FiO2_Ratio" or "FR") _ = CalculateRoxIndex(obs);
|
||||
|
||||
if (obs.Name is "Diuresis") _ = CalculateDiuresis(obs);
|
||||
|
||||
if (obs.Name is "Rehabilitation") obs = (T)await CalculateRehabilitationAlarm(obs);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
public async Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
var order = treatment.PlacerOrder?.EntityIdentifier; //aquí almacenamos el número de orden
|
||||
if (string.IsNullOrEmpty(order)) return treatment;
|
||||
|
||||
//comprobamos el estado de los tratamientos anteriores
|
||||
var oldTreatments = await CheckExpiredPatientTreatments(treatment);
|
||||
|
||||
treatment.OrderControl = oldTreatments.Any() ? OrderControlType.Xo : OrderControlType.Nw;
|
||||
|
||||
if (treatment.EndTime != null)
|
||||
//el tratamiento ha expirado
|
||||
treatment.OrderControl = OrderControlType.Dc;
|
||||
|
||||
await CheckTreatmentMedicines(treatment);
|
||||
|
||||
return treatment;
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
//Esto viene del schedulerService para chequear los medicamentos activos
|
||||
CheckMedicationCategories(patientId, activeMedicines);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(id);
|
||||
return activeTreatments;
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newObservation.Name))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Error FixTimeInconsistencyWithLast. Observation name is null or empty. Observation: {newObservation}",
|
||||
newObservation);
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
var lastObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
|
||||
[newObservation.Name]);
|
||||
var lastObservation = lastObservations.FirstOrDefault();
|
||||
|
||||
if (lastObservation != null && DateTime.Compare(
|
||||
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
|
||||
lastObservation.Time.Day, lastObservation.Time.Hour,
|
||||
lastObservation.Time.Minute, lastObservation.Time.Second),
|
||||
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
|
||||
newObservation.Time.Day, newObservation.Time.Hour,
|
||||
newObservation.Time.Minute, newObservation.Time.Second)
|
||||
) >= 0)
|
||||
newObservation.Time = lastObservation.Time.AddSeconds(1);
|
||||
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task<BasePatientObservation> CalculateRehabilitationAlarm(BasePatientObservation obs)
|
||||
{
|
||||
//En rojo si el grado es de 0 a 2 incluidos, por más de 7 días
|
||||
|
||||
if (obs is not PatientObservation pobs || !int.TryParse(pobs.Value.ToString(), out var pobsValue) ||
|
||||
pobsValue > 2) return obs;
|
||||
|
||||
var result = await _observationService.Value.FindLastObservations(obs.PatientId, 6, ["Rehabilitation"]);
|
||||
|
||||
var obsInAlert = result.Count(o =>
|
||||
int.TryParse(o.Value.ToString(), out var oValue) && oValue is >= 0 and <= 2);
|
||||
if (obsInAlert == 6) pobs.Status = StatusEnum.Type.Alert;
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
private async Task CalculateDiuresis(BasePatientObservation obs)
|
||||
{
|
||||
// Calculado como el sumatorio de la Diuresis de las últimas 6h
|
||||
// entre el último peso medido del paciente y entre 6. Medido en ml/kg/h
|
||||
// Valor = sum(Diuresis últimas 6 horas) / Weight / 6
|
||||
|
||||
if (obs is not PatientObservation pobs)
|
||||
return;
|
||||
|
||||
double weightValue = 0;
|
||||
var numValues = pobs.Name switch
|
||||
{
|
||||
"Diuresis" => 2, // Si la observación entrante es "Diuresis", necesitamos las 2 últimas para completar 6h.
|
||||
"Weight" => 3, // Si la observación entrante es "Weight", necesitamos las 3 últimas de "Diuresis".
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (numValues == 0)
|
||||
return; // Si no es ni Diuresis ni Weight, no aplicamos cálculo.
|
||||
|
||||
// Obtener las observaciones de diuresis necesarias
|
||||
var diuresisObservations =
|
||||
await _observationService.Value.FindLastObservations(pobs.PatientId, numValues, ["Diuresis"]);
|
||||
var diuresisValues = diuresisObservations
|
||||
.Select(o => double.TryParse(o.Value.ToString(), out var value) ? value : 0)
|
||||
.ToList();
|
||||
|
||||
if (pobs.Name == "Diuresis")
|
||||
{
|
||||
// Si la observación entrante es Diuresis, la agregamos
|
||||
if (double.TryParse(pobs.Value.ToString(), out var currentDiuresis))
|
||||
diuresisValues.Insert(0, currentDiuresis);
|
||||
|
||||
// Buscar el último peso registrado
|
||||
var weightObservations =
|
||||
await _observationService.Value.FindLastObservations(pobs.PatientId, 1, ["Weight"]);
|
||||
var weightObs = weightObservations.FirstOrDefault();
|
||||
if (weightObs != null && double.TryParse(weightObs.Value.ToString(), out var wObsValue))
|
||||
weightValue = wObsValue;
|
||||
}
|
||||
else if (pobs.Name == "Weight" && double.TryParse(pobs.Value.ToString(), out var wValue))
|
||||
{
|
||||
// Si la observación entrante es Weight, asignamos su valor directamente.
|
||||
weightValue = wValue;
|
||||
}
|
||||
|
||||
// Validaciones antes de calcular
|
||||
if (diuresisValues.Count < 3)
|
||||
{
|
||||
_logger.LogWarning("Calculated Diuresis lacks sufficient values. Required: 3, Found: {count}",
|
||||
diuresisValues.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (weightValue <= 0)
|
||||
{
|
||||
_logger.LogWarning("Calculated Diuresis aborted due to invalid weight value: {weight}", weightValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cálculo del índice de diuresis
|
||||
var diuresisIndex = diuresisValues.Sum() / weightValue / 6;
|
||||
|
||||
var diuresisObs = new PatientObservation
|
||||
{
|
||||
PatientId = pobs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Calculated_Diuresis",
|
||||
Value = diuresisIndex
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(diuresisObs, mapObs: false);
|
||||
}
|
||||
|
||||
|
||||
private async Task CalculateRoxIndex(BasePatientObservation obs)
|
||||
{
|
||||
// Cálculo dividiendo el ratio SpO2/FiO2 entre la FR
|
||||
// Valor = (SpO2_FiO2_Ratio) / FR
|
||||
|
||||
if (obs is not PatientObservation pobs)
|
||||
return;
|
||||
|
||||
List<string>? requiredObservations = pobs.Name switch
|
||||
{
|
||||
"SpO2_FiO2_Ratio" => ["FR"], // Si la observación actual es SpO2_FiO2_Ratio, necesitamos FR
|
||||
"FR" => ["SpO2_FiO2_Ratio"], // Si la observación actual es FR, necesitamos SpO2_FiO2_Ratio
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (requiredObservations is null)
|
||||
return;
|
||||
|
||||
var result = await _observationService.Value.FindLastObservations(pobs.PatientId, 1, requiredObservations);
|
||||
var targetObs = result.FirstOrDefault();
|
||||
|
||||
if (targetObs == null || !double.TryParse(targetObs.Value.ToString(), out var targetValue))
|
||||
return;
|
||||
|
||||
// Determinar los valores necesarios para el cálculo
|
||||
var spo2FiO2Ratio = pobs.Name == "SpO2_FiO2_Ratio"
|
||||
? double.TryParse(pobs.Value.ToString(), out var ratio) ? ratio : 0
|
||||
: targetValue;
|
||||
|
||||
var fr = pobs.Name == "FR"
|
||||
? double.TryParse(pobs.Value.ToString(), out var frValue) ? frValue : 0
|
||||
: targetValue;
|
||||
|
||||
if (fr == 0) // Evita división por cero
|
||||
return;
|
||||
|
||||
var roxIndexObs = new PatientObservation
|
||||
{
|
||||
PatientId = pobs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Rox_Index",
|
||||
Value = spo2FiO2Ratio / fr
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(roxIndexObs, mapObs: false);
|
||||
}
|
||||
|
||||
private async Task CalculateDrivingPressure(BasePatientObservation obs)
|
||||
{
|
||||
//Diferencia entre la Pmeset y la PEEP
|
||||
//Calculo Valor = Pmeset - PEEP
|
||||
|
||||
try
|
||||
{
|
||||
if (obs is not PatientObservation pobs)
|
||||
return;
|
||||
|
||||
var targetObservation = pobs.Name switch
|
||||
{
|
||||
"Pmeset" => "PEEP",
|
||||
"PEEP" => "Pmeset",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (targetObservation is null)
|
||||
return;
|
||||
|
||||
var resultList =
|
||||
await _observationService.Value.FindLastObservations(obs.PatientId, 1, [targetObservation]);
|
||||
var targetObs = resultList.FirstOrDefault();
|
||||
if (targetObs == null || !int.TryParse(targetObs.Value.ToString(), out var targetValue))
|
||||
return;
|
||||
|
||||
var peepValue = pobs.Name == "PEEP"
|
||||
? int.TryParse(pobs.Value.ToString(), out var peep) ? peep : 0
|
||||
: targetValue;
|
||||
var pmesetValue = pobs.Name == "Pmeset"
|
||||
? int.TryParse(pobs.Value.ToString(), out var pmeset) ? pmeset : 0
|
||||
: targetValue;
|
||||
|
||||
var drivingPressureObs = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Driving_Pressure",
|
||||
Value = pmesetValue - peepValue
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(drivingPressureObs, mapObs: false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateOverAnalgesia(BasePatientObservation? obs, ObjectId? patientId = null)
|
||||
{
|
||||
// Mandar observación 'SOBREANALGESIA'
|
||||
// 0 <= EVN <= 3 o ESCID = 3 o ANI > 70
|
||||
// y
|
||||
// Perfusiones de morfina, remifentanilo y fentanilo sostenidas > 24h.
|
||||
|
||||
try
|
||||
{
|
||||
List<string> requiredObservations = ["EVN", "ESCID", "ANI"];
|
||||
|
||||
// Si patientId es null, la información proviene de una observación.
|
||||
// Si tiene valor, proviene de un tratamiento.
|
||||
if (!patientId.HasValue)
|
||||
{
|
||||
if (obs is not PatientObservation pobs || !int.TryParse(pobs.Value.ToString(), out _))
|
||||
return;
|
||||
|
||||
patientId = pobs.PatientId;
|
||||
}
|
||||
|
||||
var observations =
|
||||
await _observationService.Value.FindLastObservations(patientId.Value, 1, requiredObservations);
|
||||
|
||||
// Verificar si alguna observación cumple la condición
|
||||
var condition1 = observations.Any(observation =>
|
||||
int.TryParse(observation.Value.ToString(), out var obsValue) && observation.Name switch
|
||||
{
|
||||
"EVN" when obsValue is >= 0 and <= 3 => true,
|
||||
"ESCID" when obsValue == 3 => true,
|
||||
"ANI" when obsValue > 70 => true,
|
||||
_ => false
|
||||
});
|
||||
|
||||
if (!condition1)
|
||||
return;
|
||||
|
||||
// Verificar tratamientos activos y umbral de analgesia
|
||||
var treatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(patientId.Value);
|
||||
var patientTreatments = treatments.ToList();
|
||||
if (!patientTreatments.Any()) return;
|
||||
|
||||
var morfina =
|
||||
patientTreatments.FirstOrDefault(t => t != null && t.RequestedGiveCodes.Any(c => c.Text is "Morfina"));
|
||||
var remifentanilo = patientTreatments.FirstOrDefault(t =>
|
||||
t != null && t.RequestedGiveCodes.Any(c => c.Text is "Remifentanilo"));
|
||||
var fentanilo =
|
||||
patientTreatments.FirstOrDefault(t =>
|
||||
t != null && t.RequestedGiveCodes.Any(c => c.Text is "Fentanilo"));
|
||||
|
||||
if (morfina != null && !ExceedsAnalgesiaThreshold(morfina) &&
|
||||
remifentanilo != null && !ExceedsAnalgesiaThreshold(remifentanilo) &&
|
||||
fentanilo != null && !ExceedsAnalgesiaThreshold(fentanilo))
|
||||
return;
|
||||
|
||||
|
||||
var overAnalgesiaObs = new PatientObservation
|
||||
{
|
||||
PatientId = patientId.Value,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Over_Analgesia",
|
||||
Value = bool.TrueString
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(overAnalgesiaObs, mapObs: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error calculating Over_Analgesia. Exception: {ex}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private Task<BasePatientObservation> CalculateEcmoLocation(BasePatientObservation obs)
|
||||
{
|
||||
//Las opciones pueden ser VV, VA o ECMO.
|
||||
//Cuando llegue VV se muestra VV.
|
||||
//Cuando llegue VA se muestra VA.
|
||||
//Cuando llegue los siguientes parámetros, se debe mostrar ECMO: ECCO2r, VVDL, VA+V, VVDL+V, VV+V, VVA
|
||||
if (obs is not PatientObservation pobs)
|
||||
return Task.FromResult(obs);
|
||||
|
||||
var ecmoValues = new List<string> { "ECCO2r", "VVDL", "VA+V", "VVDL+V", "VV+V", "VVA" };
|
||||
|
||||
var obsValue = pobs.Value.ToString();
|
||||
|
||||
if (obsValue != null && ecmoValues.Contains(obsValue)) pobs.Value = "ECMO";
|
||||
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
private async Task CalculateVentilationMode(BasePatientObservation obs)
|
||||
{
|
||||
//- Si llega valor de PI pero NO COMPL, el tipo de ventilación es VMNI
|
||||
//- Si llega valor de PS pero no llega COMPL, el tipo de ventilación es VMNI
|
||||
//- Si llega COMPL, el tipo de ventilación es VMI
|
||||
//- Si llega Flujo, leer el tipo de ventilación de CCC(CNAF o CTAF)
|
||||
|
||||
var mode = string.Empty;
|
||||
|
||||
switch (obs.Name)
|
||||
{
|
||||
case "Inspiratory_Pressure":
|
||||
var complObs = await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["Compliancia"]);
|
||||
if (!complObs.Any())
|
||||
mode = "VMNI";
|
||||
|
||||
break;
|
||||
case "Compliancia":
|
||||
mode = "VMI";
|
||||
break;
|
||||
case "Air_Flow":
|
||||
var cccVentMode =
|
||||
await _observationService.Value.FindByPatientIdAndCodingSystemAsync(obs.PatientId, "CCC",
|
||||
"Ventilation_Mode");
|
||||
|
||||
if (!await cccVentMode.AnyAsync())
|
||||
return;
|
||||
mode = cccVentMode.First().Value.ToString() ?? string.Empty;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(mode)) return;
|
||||
|
||||
|
||||
var ventilationModeObservation = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Ventilation_Mode",
|
||||
Value = mode
|
||||
};
|
||||
|
||||
_ = _observationService.Value.InsertObservation(ventilationModeObservation, mapObs: false);
|
||||
}
|
||||
|
||||
private async Task CalculateOverSedation(BasePatientObservation? obs, ObjectId? patientId = null)
|
||||
{
|
||||
//Mandar observación 'SOBRESEDACIÓN'
|
||||
//cuando:
|
||||
//(RASS =-4 o RASS =-5) y
|
||||
//PSI < 25 y
|
||||
//TS > 5 y
|
||||
//(Propofol > 3 o Midazolam > 0,05 ó Isoflorano > 10)
|
||||
|
||||
try
|
||||
{
|
||||
var pobsName = string.Empty;
|
||||
|
||||
//si patientId viene null viene la observación
|
||||
//Si tiene valor viene de un tratamiento
|
||||
|
||||
if (!patientId.HasValue)
|
||||
{
|
||||
if (obs is not PatientObservation pobs || !int.TryParse(pobs.Value.ToString(), out var pobsValue))
|
||||
return;
|
||||
|
||||
patientId = pobs.PatientId;
|
||||
pobsName = pobs.Name;
|
||||
|
||||
switch (pobsName)
|
||||
{
|
||||
case "RASS" when pobsValue is not (-4 or -5):
|
||||
case "PSI" when pobsValue >= 25:
|
||||
case "TS" when pobsValue <= 5:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var requiredObservations = pobsName switch
|
||||
{
|
||||
"RASS" => ["PSI", "TS"],
|
||||
"PSI" => ["RASS", "TS"],
|
||||
"TS" => ["RASS", "PSI"],
|
||||
_ => new List<string> { "RASS", "PSI", "TS" }
|
||||
};
|
||||
|
||||
var result = await _observationService.Value.FindLastObservations(patientId.Value, 1, requiredObservations);
|
||||
var observations = result.ToDictionary(r => r.Name?.ToString() ?? "null");
|
||||
|
||||
if ((pobsName != "RASS" && (!observations.TryGetValue("RASS", out var rassObs) ||
|
||||
!int.TryParse(rassObs.Value.ToString(), out var rassValue) ||
|
||||
rassValue is not (-4 or -5))) ||
|
||||
(pobsName != "PSI" && (!observations.TryGetValue("PSI", out var psiObs) ||
|
||||
!int.TryParse(psiObs.Value.ToString(), out var psiValue) || psiValue >= 25)) ||
|
||||
(pobsName != "TS" && (!observations.TryGetValue("TS", out var tsObs) ||
|
||||
!int.TryParse(tsObs.Value.ToString(), out var tsValue) || tsValue <= 5)))
|
||||
return;
|
||||
|
||||
var treatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(patientId.Value);
|
||||
if (!treatments.Any(ExceedsSedationThreshold)) return;
|
||||
|
||||
var overSedationObs = new PatientObservation
|
||||
{
|
||||
PatientId = patientId.Value,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Over_Sedation",
|
||||
Value = bool.TrueString
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(overSedationObs, mapObs: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error calculating Over_Sedation. Exception: {ex}", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ExceedsAnalgesiaThreshold(PatientTreatment? treatment)
|
||||
{
|
||||
// Perfusiones de morfina, remifentanilo o fentanilo sostenidas > 24 h
|
||||
|
||||
return treatment is { StartTime: not null } &&
|
||||
treatment.StartTime.Value.AddSeconds(86400) < DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private static bool ExceedsSedationThreshold(PatientTreatment? treatment)
|
||||
{
|
||||
return (treatment != null &&
|
||||
treatment.RequestedGiveCodes.Any(c =>
|
||||
c.Text == "Propofol" && treatment.RequestedGiveAmountMinimum > 3)) ||
|
||||
(treatment != null && treatment.RequestedGiveCodes.Any(c =>
|
||||
c.Text == "Midazolam" && treatment.RequestedGiveAmountMinimum > 0.05)) ||
|
||||
(treatment != null && treatment.RequestedGiveCodes.Any(c =>
|
||||
c.Text == "Isoflorano" && treatment.RequestedGiveAmountMinimum > 10));
|
||||
}
|
||||
|
||||
private async Task<List<PatientTreatment>> CheckExpiredPatientTreatments(PatientTreatment treatment)
|
||||
{
|
||||
//si el tratamiento ha expirado actualizamos el OrderControl a DC y devolvemos las que siguen activas
|
||||
var result = new List<PatientTreatment>();
|
||||
var oldTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(treatment.PatientId);
|
||||
|
||||
foreach (var t in oldTreatments.Where(t => t != null))
|
||||
if (t is { EndTime: not null } && t.EndTime > DateTime.UtcNow)
|
||||
{
|
||||
t.OrderControl = OrderControlType.Dc;
|
||||
_ = _treatmentService.Value.UpdateTreatment(t);
|
||||
}
|
||||
else if (t != null)
|
||||
{
|
||||
result.Add(t);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task CheckTreatmentMedicines(PatientTreatment treatment)
|
||||
{
|
||||
var newMedicines = (await Task.WhenAll(treatment.RequestedGiveCodes
|
||||
.Select(async code => await _medicineService.Value.GetByCode(code.Identifier))))
|
||||
.Where(m => m != null)
|
||||
.ToList();
|
||||
|
||||
if (newMedicines.Count == 0) return;
|
||||
|
||||
var activeTreatments = (await GetActiveTreatmentsByPatient(treatment.PatientId)).ToList();
|
||||
var activeMedicines = (await _medicineService.Value.GetMedicinesOfTreatments(activeTreatments)).ToList();
|
||||
|
||||
// Combina ambas listas y selecciona solo elementos únicos con el mismo nombre
|
||||
var uniqueMedicines = newMedicines
|
||||
.Concat(activeMedicines)
|
||||
.GroupBy(m => m?.Name)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
// uniqueMedicines contiene la lista de medicamentos activos de un paciente
|
||||
CheckMedicationCategories(treatment.PatientId, uniqueMedicines as List<Medicine>);
|
||||
}
|
||||
|
||||
private void CheckMedicationCategories(ObjectId patientId, List<Medicine> uniqueMedicines)
|
||||
{
|
||||
var medicationCategories = new Dictionary<string, List<string>?>
|
||||
{
|
||||
{ "Sedation_Medication_Multivalue", _sedationList },
|
||||
{ "Inotropic_Medication_Multivalue", _inotropicMedicines },
|
||||
{ "Antibiotic_Medication_Multivalue", _antibioticList },
|
||||
{ "Anxiolytic_Medication_Multivalue", _anxiolyticsList },
|
||||
{ "Antipsychotic_Medication_Multivalue", _antipsicoticList },
|
||||
{ "Antidepressants_Medication_Multivalue", _antidepressantsList },
|
||||
{ "Neuro_Medication_Multivalue", _neuroMedicationList },
|
||||
{ "Crystalloid_Serum_Medication_Multivalue", _serumCrystalloidList },
|
||||
{ "Colloid_Serum_Medication_Multivalue", _serumColloidList },
|
||||
{ "Antihypertensives_Medication_Multivalue", _antihypertensivesList }
|
||||
};
|
||||
|
||||
foreach (var category in medicationCategories)
|
||||
{
|
||||
var observations = uniqueMedicines?
|
||||
.Where(m => category.Value != null && !string.IsNullOrEmpty(m.Name) && m.Name != null &&
|
||||
category.Value.Contains(m.Name))
|
||||
.ToList();
|
||||
|
||||
if (observations != null && observations.Any())
|
||||
_ = CreateMedicationMultivalueObservation(observations, patientId, category.Key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task CreateMedicationMultivalueObservation(IEnumerable<Medicine?> medications, ObjectId patientId,
|
||||
string obsName)
|
||||
{
|
||||
var newObs = new PatientObservation
|
||||
{
|
||||
PatientId = patientId,
|
||||
Name = obsName,
|
||||
CodingSystem = CodingSystem,
|
||||
Value = medications.Select(m => m?.Name).ToArray(),
|
||||
Time = DateTime.Now
|
||||
};
|
||||
|
||||
var lastObsList = await _observationService.Value.FindLastObservations(patientId, 1, [obsName]);
|
||||
var lastObs = lastObsList.FirstOrDefault();
|
||||
if (lastObs is { Expired: false })
|
||||
{
|
||||
//expiramos la anterior
|
||||
lastObs.Expired = true;
|
||||
_ = _observationService.Value.UpdateObservation(lastObs);
|
||||
}
|
||||
|
||||
await _observationService.Value.InsertObservation(newObs,
|
||||
mapObs: false); //mapObs:no volvemos a mapear la observación
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,711 @@
|
||||
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.Pumps;
|
||||
using adas_core.Domain.Utils;
|
||||
using adas_core.Domain.Utils.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.HUVH.UCIN;
|
||||
|
||||
public class CalculatedObservations : ICalculatedObservations
|
||||
{
|
||||
private const string Spo2PreObservationName = "SpO2";
|
||||
private const string Spo2PostObservationName = "SpO2_Post";
|
||||
|
||||
private const string Spo2PrePostObservationName = "SpO2_Pre_Post";
|
||||
private const string CodingSystem = "ADAS";
|
||||
|
||||
private readonly List<string> _complexityObservations =
|
||||
[
|
||||
"Respiratory_Device_Multivalue", "Intravenous_Routes_Multivalue", "Medication_Multivalue",
|
||||
"System_Multivalue", "Newborn_Weight", "Surgery_Multivalue"
|
||||
];
|
||||
|
||||
private readonly ILogger<CalculatedObservations> _logger;
|
||||
private readonly IMappingUtils _mappingUtils;
|
||||
private readonly Lazy<IMedicineService> _medicineService;
|
||||
private readonly Lazy<IObservationService> _observationService;
|
||||
|
||||
private readonly Lazy<ITreatmentService> _treatmentService;
|
||||
|
||||
|
||||
public CalculatedObservations(IServiceProvider serviceProvider)
|
||||
{
|
||||
_treatmentService = serviceProvider.GetRequiredService<Lazy<ITreatmentService>>();
|
||||
_medicineService = serviceProvider.GetRequiredService<Lazy<IMedicineService>>();
|
||||
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>();
|
||||
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
|
||||
|
||||
var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;
|
||||
_mappingUtils = new MappingUtils(apiSettings);
|
||||
}
|
||||
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
if (obs.Name is "Intervention_In" or "Intervention_Out") await CalculateInterventionMultiValueObservation(obs);
|
||||
if (obs.Name is "Surgery") await CalculateMultiValueObservation(obs);
|
||||
if (obs.Name != null && _complexityObservations.Contains(obs.Name))
|
||||
{
|
||||
_logger.LogDebug("Mapping observation Complexity {obs}", obs);
|
||||
//TODO ver si hay un máximo de puntuación por grupo de observaciones
|
||||
_ = CalculateComplexity(obs);
|
||||
}
|
||||
|
||||
if (obs.Name is Spo2PreObservationName or Spo2PostObservationName)
|
||||
{
|
||||
_logger.LogDebug("Pre-post saturation observation {obs}", obs);
|
||||
_ = CalculateSaturation_DiffObservation(obs);
|
||||
}
|
||||
|
||||
if (obs.Name is "ALPS" or "NPASS_Sedation" or "NPASS_Analgesia") _ = CalculatePainScale(obs);
|
||||
|
||||
|
||||
if (obs.Name is "Last_Defecation") obs = (T)CalculateLastDefecationValue(obs);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
public async Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
var order = treatment.PlacerOrder?.EntityIdentifier; //aquí almacenamos el número de orden
|
||||
if (string.IsNullOrEmpty(order)) return treatment;
|
||||
|
||||
//comprobamos el estado de los tratamientos anteriores
|
||||
var oldTreatments = await CheckExpiredPatientTreatments(treatment);
|
||||
|
||||
treatment.OrderControl = oldTreatments.Any() ? OrderControlType.Xo : OrderControlType.Nw;
|
||||
|
||||
if (treatment.EndTime != null)
|
||||
//el tratamiento ha expirado
|
||||
treatment.OrderControl = OrderControlType.Dc;
|
||||
|
||||
await CheckTreatmentMedicines(treatment);
|
||||
|
||||
return treatment;
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
|
||||
}
|
||||
|
||||
public async Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
var medicineObs = new PatientObservation
|
||||
{
|
||||
PatientId = patientId,
|
||||
Name = "Medication_Multivalue",
|
||||
CodingSystem = CodingSystem,
|
||||
Value = activeMedicines.Select(m => m.Name ?? string.Empty).Distinct().ToArray(),
|
||||
Time = DateTime.Now
|
||||
};
|
||||
|
||||
var lastMedicationsObs =
|
||||
await _observationService.Value.FindLastObservations(patientId, 1, ["Medication_Multivalue"]);
|
||||
var lastMedicationObs = lastMedicationsObs.FirstOrDefault();
|
||||
if (lastMedicationObs != null)
|
||||
{
|
||||
//expiramos la anterior
|
||||
lastMedicationObs.Expired = true;
|
||||
_ = _observationService.Value.UpdateObservation(lastMedicationObs);
|
||||
}
|
||||
|
||||
await _observationService.Value
|
||||
.InsertObservation(
|
||||
medicineObs); //mapObs:volvemos a mapear la observación para que pase por el cálculo de la complejidad
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(id);
|
||||
return activeTreatments;
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newObservation.Name))
|
||||
{
|
||||
_logger.LogError(
|
||||
"Error FixTimeInconsistencyWithLast. Observation name is null or empty. Observation: {newObservation}",
|
||||
newObservation);
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
var lastObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
|
||||
[newObservation.Name]);
|
||||
var lastObservation = lastObservations.FirstOrDefault();
|
||||
|
||||
if (lastObservation != null && DateTime.Compare(
|
||||
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
|
||||
lastObservation.Time.Day, lastObservation.Time.Hour,
|
||||
lastObservation.Time.Minute, lastObservation.Time.Second),
|
||||
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
|
||||
newObservation.Time.Day, newObservation.Time.Hour,
|
||||
newObservation.Time.Minute, newObservation.Time.Second)
|
||||
) >= 0)
|
||||
newObservation.Time = lastObservation.Time.AddSeconds(1);
|
||||
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task CalculatePainScale(BasePatientObservation obs)
|
||||
{
|
||||
if (obs is not PatientObservation pobs) return;
|
||||
|
||||
|
||||
object valueObs;
|
||||
|
||||
switch (pobs.Name)
|
||||
{
|
||||
case "ALPS":
|
||||
valueObs = pobs.Value;
|
||||
break;
|
||||
case "NPASS_Sedation":
|
||||
{
|
||||
//Buscamos la complementaria "NPASS Analgesia"
|
||||
var analgesiaObs =
|
||||
await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["NPASS_Analgesia"]);
|
||||
var analgesiaValue = analgesiaObs.FirstOrDefault()?.Value.ToString() ?? "0";
|
||||
|
||||
valueObs = $"{pobs.Value}/{analgesiaValue}";
|
||||
}
|
||||
break;
|
||||
case "NPASS_Analgesia":
|
||||
{
|
||||
//Buscamos la complementaria "NPASS Sedation"
|
||||
var sedationObs =
|
||||
await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["NPASS_Sedation"]);
|
||||
var sedationValue = sedationObs.FirstOrDefault()?.Value.ToString() ?? "0";
|
||||
|
||||
valueObs = $"{sedationValue}/{pobs.Value}";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var painScaleObs = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = "ADAS",
|
||||
Name = "Pain_Scale",
|
||||
Value = valueObs
|
||||
};
|
||||
|
||||
_ = _observationService.Value.InsertObservation(painScaleObs, mapObs: false);
|
||||
}
|
||||
|
||||
private static BasePatientObservation CalculateLastDefecationValue(BasePatientObservation obs)
|
||||
{
|
||||
if (obs is not PatientObservation pobs) return obs;
|
||||
//Assign obs time to value
|
||||
//Convert the UTC DateTime to local time.
|
||||
var localTime = pobs.Time.ToLocalTime();
|
||||
|
||||
//Format the local time into a short date/time string.
|
||||
pobs.Value = localTime.ToString("dd/MM/yyyy HH:mm");
|
||||
|
||||
return pobs;
|
||||
}
|
||||
|
||||
|
||||
private async Task CalculateMultiValueObservation(BasePatientObservation obs)
|
||||
{
|
||||
//El valor de la observación es un array de strings
|
||||
if (obs is not PatientObservation pobs || string.IsNullOrEmpty(obs.Name)) return;
|
||||
|
||||
var newObsName = $"{obs.Name}_Multivalue";
|
||||
var actualObsInBd =
|
||||
await _observationService.Value.FindLastObservations(obs.PatientId, 1, [newObsName]);
|
||||
|
||||
|
||||
var newValue = new List<string>();
|
||||
|
||||
if (pobs.Value is not string stringValueName) return;
|
||||
|
||||
|
||||
if (actualObsInBd.Count > 0)
|
||||
if (actualObsInBd.First().Value is string[] oldValue)
|
||||
{
|
||||
oldValue = oldValue.Where(x => x != stringValueName).ToArray();
|
||||
newValue.AddRange(oldValue);
|
||||
}
|
||||
|
||||
newValue.Add(stringValueName);
|
||||
|
||||
if (newValue.Count == 0) return;
|
||||
|
||||
//Expiramos la anterior
|
||||
var actualObs = actualObsInBd.FirstOrDefault();
|
||||
if (actualObs != null)
|
||||
{
|
||||
actualObs.Expired = true;
|
||||
|
||||
await _observationService.Value.UpdateObservation(actualObs);
|
||||
}
|
||||
|
||||
var newObservation = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = newObsName,
|
||||
Value = newValue.ToArray()
|
||||
};
|
||||
|
||||
_logger.LogDebug(
|
||||
"Calculate {name}_Multivalue {outpatient} Insert Calculated {time} value {value}",
|
||||
newObsName, newObservation.PatientId, newObservation.Time,
|
||||
newObservation.Value.ToJson());
|
||||
|
||||
await _observationService.Value.InsertObservation(newObservation);
|
||||
}
|
||||
|
||||
private async Task CalculateInterventionMultiValueObservation(BasePatientObservation obs)
|
||||
{
|
||||
if (obs is not PatientObservation pobs || string.IsNullOrEmpty(obs.Name) ||
|
||||
pobs.Value is not string valueObs) return;
|
||||
|
||||
if (!GetInterventionValue(valueObs, out var r) || r == null) return;
|
||||
|
||||
var obsType = r.Value.type; //"Intravenous_Routes";
|
||||
var obsGroup = r.Value.Name; // "Vía arterial";
|
||||
var newObsName = $"{obsType}_Multivalue";
|
||||
|
||||
// Obtener la última observación multivalue
|
||||
var result = await _observationService.Value.FindLastObservations(obs.PatientId, 1, [newObsName]);
|
||||
var actualMultiValueObsInDb = result.FirstOrDefault(o => !o.Expired);
|
||||
|
||||
var oldValue = actualMultiValueObsInDb?.Value as string[] ?? [];
|
||||
var newValue = new List<string>(oldValue);
|
||||
|
||||
if (obs.Name.Contains("_In"))
|
||||
{
|
||||
//Por seguridad solo tomamos el último valor que nos llega
|
||||
//Puede ser que no se acuerden de cancelar el anterior antes de añadir un nuevo dispositivo y no puede haber dos dispositivos del mismo tipo activos al mismo tiempo
|
||||
if (obsType == "Respiratory_Device")
|
||||
newValue.Clear();
|
||||
|
||||
// Inserción dispositivo
|
||||
if (!oldValue.Contains(obsGroup))
|
||||
newValue.Add(obsGroup);
|
||||
}
|
||||
else if (obs.Name.Contains("_Out"))
|
||||
{
|
||||
// Retirada del dispositivo
|
||||
//Buscamos la inserción
|
||||
var name = $"{obs.Name.Split('_')[0]}_In";
|
||||
var inObs = await _observationService.Value.FindLastNotExpiredObservatonsByPatient(obs.PatientId, name);
|
||||
|
||||
var sameValueObs = inObs.Where(o => o.Value == pobs.Value).ToList();
|
||||
|
||||
if (sameValueObs.Any())
|
||||
// Si hay más de una observación del mismo tipo, no eliminar del multivalue
|
||||
if (sameValueObs.Count > 1)
|
||||
return;
|
||||
|
||||
// Actualizar valor eliminando el grupo
|
||||
newValue.Remove(obsGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Expiramos la anterior
|
||||
if (actualMultiValueObsInDb is { Expired: false })
|
||||
{
|
||||
actualMultiValueObsInDb.Expired = true;
|
||||
await _observationService.Value.UpdateObservation(actualMultiValueObsInDb);
|
||||
}
|
||||
|
||||
// Si no hay valores nuevos, no se crea una nueva observación
|
||||
if (!newValue.Any())
|
||||
return;
|
||||
|
||||
|
||||
// Crear y registrar nueva observación
|
||||
var newObservation = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = newObsName,
|
||||
Value = newValue.ToArray()
|
||||
};
|
||||
|
||||
_logger.LogDebug(
|
||||
"Calculate {name}_Multivalue {patientid} Insert Calculated {time} value {value}",
|
||||
newObsName, newObservation.PatientId, newObservation.Time,
|
||||
newObservation.Value.ToJson()
|
||||
);
|
||||
|
||||
await _observationService.Value.InsertObservation(newObservation);
|
||||
}
|
||||
|
||||
private bool GetInterventionValue(string valueObs, out (string type, string Name, string Group)? r)
|
||||
{
|
||||
var code = valueObs.Split(" ")[0]; //
|
||||
if (code.EndsWith(".")) // Verifica si termina con un punto
|
||||
code = code.Substring(0, code.Length - 1).Replace('.', ','); // Remueve el último carácter
|
||||
|
||||
if (!double.TryParse(code, out var codeParsed))
|
||||
{
|
||||
r = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
r = _mappingUtils.SearchByCode(codeParsed, "Intervention"); //TODO validar este parámetro con Lola
|
||||
|
||||
return r != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculamos la complejidad basándonos en el valor de Respiratorio/Vías/Medicación/Sistemas/Peso nacimiento/cirugía
|
||||
* cuando entra una nueva observación que afecte a complejidad, se recalcula de 0 para no tener que estar pendiente de qué puntos corresponden a qué observación
|
||||
* Valor máximo 46 Guardamos la observación que nos llega y recalculamos complejidad.
|
||||
*/
|
||||
private async Task CalculateComplexity(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = obs as PatientObservation;
|
||||
if (pobs == null) return;
|
||||
|
||||
var complexity = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem,
|
||||
Name = "Complexity",
|
||||
Value = 0
|
||||
};
|
||||
|
||||
var complexityValue = 0;
|
||||
|
||||
// Diccionario para manejar las operaciones basadas en el nombre de la observación
|
||||
var operations = new Dictionary<string, Func<ObjectId, PatientObservation?, Task<int>>>
|
||||
{
|
||||
{
|
||||
"Respiratory_Device_Multivalue",
|
||||
(patientId, observation) =>
|
||||
CalculateMultivalueObservationComplexityValue(patientId, "Respiratory_Device_Multivalue",
|
||||
observation)
|
||||
},
|
||||
{
|
||||
"Intravenous_Routes_Multivalue",
|
||||
(patientId, observation) =>
|
||||
CalculateMultivalueObservationComplexityValue(patientId, "Intravenous_Routes_Multivalue",
|
||||
observation)
|
||||
},
|
||||
{
|
||||
"Medication_Multivalue",
|
||||
(patientId, observation) =>
|
||||
CalculateMultivalueObservationComplexityValue(patientId, "Medication_Multivalue", observation)
|
||||
},
|
||||
{
|
||||
"System_Multivalue",
|
||||
(patientId, observation) =>
|
||||
CalculateMultivalueObservationComplexityValue(patientId, "System_Multivalue", observation)
|
||||
},
|
||||
{ "Newborn_Weight", CalculateWeightNewBornValue },
|
||||
{
|
||||
"Surgery_Multivalue",
|
||||
(patientId, observation) =>
|
||||
CalculateMultivalueObservationComplexityValue(patientId, "Surgery_Multivalue", observation)
|
||||
}
|
||||
};
|
||||
|
||||
// Realizar cálculos iterando sobre las operaciones
|
||||
foreach (var operation in operations)
|
||||
complexityValue += await operation.Value(pobs.PatientId, operation.Key.Equals(pobs.Name) ? pobs : null);
|
||||
|
||||
complexity.Value = Math.Min(complexityValue, 46); // Limitar el valor máximo a 46
|
||||
|
||||
_logger.LogDebug(
|
||||
"complexity for patient {patientid} => inserting complexity value: {complexityValue} for patient: {complexityPatientid} at time {fixedTimeComplexityTime}",
|
||||
complexity.PatientId, complexity.Value, complexity.PatientId, complexity.Time);
|
||||
|
||||
await _observationService.Value.InsertObservation(complexity, mapObs: false);
|
||||
}
|
||||
|
||||
private async Task<int> CalculateMultivalueObservationComplexityValue(ObjectId patientId, string observationName,
|
||||
PatientObservation? observation = null)
|
||||
{
|
||||
var result =
|
||||
await _observationService.Value.FindLastObservations(patientId, 1, [observationName]);
|
||||
var actualObsInBd = result.FirstOrDefault();
|
||||
|
||||
var valueObj = observation == null ? actualObsInBd?.Value : observation.Value;
|
||||
var valueList = valueObj as string[];
|
||||
|
||||
var valueToReturn = valueList?.Sum(v => _mappingUtils.GetComplexityValue(v)) ?? 0;
|
||||
|
||||
_logger.LogDebug(
|
||||
"complexity for patient {patientid} => observation: {observationName} plus complexity: {Value}", patientId,
|
||||
observationName, valueToReturn);
|
||||
|
||||
return valueToReturn;
|
||||
}
|
||||
|
||||
|
||||
private async Task<int> CalculateWeightNewBornValue(ObjectId patientId, PatientObservation? pobs = null)
|
||||
{
|
||||
var result =
|
||||
await _observationService.Value.FindLastObservations(patientId, 1, ["Newborn_Weight"]);
|
||||
var actualObsInBd = result.FirstOrDefault();
|
||||
|
||||
string? strValue;
|
||||
string? obsName;
|
||||
if (pobs == null)
|
||||
{
|
||||
if (actualObsInBd?.Value == null) return 0;
|
||||
|
||||
strValue = actualObsInBd.Value.ToString();
|
||||
obsName = actualObsInBd.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pobs.Units == "kg") pobs = ParseWeight(pobs);
|
||||
|
||||
strValue = pobs.Value.ToString();
|
||||
obsName = pobs.Name;
|
||||
}
|
||||
|
||||
if (!double.TryParse(strValue, out var valueParsed) || obsName == null)
|
||||
return 0;
|
||||
|
||||
var weightNewBornValue = _mappingUtils.GetComplexityValue(obsName, valueParsed);
|
||||
|
||||
|
||||
_logger.LogDebug(
|
||||
"complexity for patient {patientid} => Weight_Newborn plus complexity: {Weight_NewbornValue}",
|
||||
patientId, weightNewBornValue);
|
||||
|
||||
return weightNewBornValue;
|
||||
}
|
||||
|
||||
private PatientObservation ParseWeight(PatientObservation obs)
|
||||
{
|
||||
if (!double.TryParse(obs.Value.ToString(), out var dValue))
|
||||
{
|
||||
_logger.LogError("Error casting weight Observation {obs}:", obs);
|
||||
return obs;
|
||||
}
|
||||
|
||||
obs.Units = "gr";
|
||||
obs.Value = dValue * 1000;
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
private async Task CalculateSaturation_DiffObservation(BasePatientObservation obs)
|
||||
{
|
||||
var toSearchList = new List<string>();
|
||||
PatientObservation? pre;
|
||||
PatientObservation? post;
|
||||
|
||||
toSearchList.AddRange(new List<string> { Spo2PreObservationName, Spo2PostObservationName });
|
||||
|
||||
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
|
||||
var time = obs.Time;
|
||||
|
||||
if (obs.Name == Spo2PreObservationName)
|
||||
{
|
||||
pre = (PatientObservation)obs;
|
||||
post = values.FirstOrDefault(o => o.Name == Spo2PostObservationName);
|
||||
|
||||
if (post?.Time != null && time.CompareTo(post.Time) > 0) time = post.Time;
|
||||
}
|
||||
else
|
||||
{
|
||||
post = (PatientObservation)obs;
|
||||
pre = values.FirstOrDefault(o => o.Name == Spo2PreObservationName);
|
||||
|
||||
if (pre?.Time != null && time.CompareTo(pre.Time) > 0) time = pre.Time;
|
||||
}
|
||||
|
||||
if (pre?.Value != null && post?.Value != null)
|
||||
{
|
||||
var preSuccess = double.TryParse(pre.Value.ToString(), out var preValue);
|
||||
if (!preSuccess) preValue = 0;
|
||||
var postSuccess = double.TryParse(post.Value.ToString(), out var postValue);
|
||||
if (!postSuccess) postValue = 0;
|
||||
|
||||
var saturationDiff = Math.Round(preValue - postValue, 2);
|
||||
|
||||
var saturationDiffObs = new PatientObservation
|
||||
{
|
||||
Name = "SpO2_Diff",
|
||||
CodingSystem = CodingSystem,
|
||||
PatientId = obs.PatientId,
|
||||
Time = time,
|
||||
Value = saturationDiff
|
||||
};
|
||||
var saturationPrePost = new PatientObservation
|
||||
{
|
||||
Name = Spo2PrePostObservationName,
|
||||
CodingSystem = CodingSystem,
|
||||
PatientId = obs.PatientId,
|
||||
Time = time,
|
||||
Value = pre.Value + "/" + post.Value
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(saturationDiffObs);
|
||||
await _observationService.Value.InsertObservation(saturationPrePost);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<PatientTreatment>> CheckExpiredPatientTreatments(PatientTreatment treatment)
|
||||
{
|
||||
//si el tratamiento ha expirado actualizamos el OrderControl a DC y devolvemos las que siguen activas
|
||||
var result = new List<PatientTreatment>();
|
||||
var oldTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(treatment.PatientId);
|
||||
|
||||
foreach (var t in oldTreatments)
|
||||
{
|
||||
if (t == null) continue;
|
||||
|
||||
if (t.PlacerOrder?.EntityIdentifier == treatment.PlacerOrder?.EntityIdentifier)
|
||||
{
|
||||
//Es el mismo tratamiento actualizamos
|
||||
|
||||
if (t.EndTime != null && t.EndTime > DateTime.UtcNow) treatment.OrderControl = OrderControlType.Dc;
|
||||
|
||||
treatment.Id = t.Id; //Asignamos el ID del tratamiento existente para actualizarlo
|
||||
_ = _treatmentService.Value.UpdateTreatment(treatment);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task CheckTreatmentMedicines(PatientTreatment treatment)
|
||||
{
|
||||
var newMedicines = new List<Medicine>();
|
||||
var hasVasoactives = false;
|
||||
foreach (var code in treatment.RequestedGiveCodes)
|
||||
{
|
||||
var m = await _medicineService.Value.GetByCode(code.Identifier);
|
||||
if (m == null) continue;
|
||||
|
||||
//Si el código es de tipo nutrición entonces creamos una observación de ese tipo que tendrá el valor mapeado con el nombre correspondiente
|
||||
//CreateNutritionObservation y NO agregamos a medicamentos
|
||||
var r = _mappingUtils.SearchByCode(m.Codes.First(), "Treatment");
|
||||
if (r is { type: "Nutrition" })
|
||||
{
|
||||
await CreateNutritionObservation(r.Value.group, treatment.PatientId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r is { type: "Medication", group: "Drogas vasoactivas" })
|
||||
hasVasoactives = true;
|
||||
|
||||
newMedicines.Add(m);
|
||||
}
|
||||
|
||||
if (hasVasoactives)
|
||||
{
|
||||
var vObs = new PatientObservation
|
||||
{
|
||||
Name = "HasVasoactive",
|
||||
Value = true,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = CodingSystem
|
||||
};
|
||||
|
||||
await _observationService.Value.InsertObservation(vObs, mapObs: false);
|
||||
}
|
||||
|
||||
if (newMedicines.Count == 0)
|
||||
return;
|
||||
|
||||
var _ = await GetActiveTreatmentsByPatient(treatment.PatientId);
|
||||
|
||||
var activeTreatments = _.ToList();
|
||||
|
||||
var __ = await _medicineService.Value.GetMedicinesOfTreatments(activeTreatments); //Todo revisar
|
||||
var activeMedicines = __.ToList();
|
||||
|
||||
// Combina ambas listas y selecciona solo elementos únicos con el mismo nombre
|
||||
var uniqueMedicines = newMedicines
|
||||
.Concat(activeMedicines)
|
||||
.GroupBy(m => m.Name)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
await CalculateMedicineObservation(uniqueMedicines, treatment.PatientId);
|
||||
}
|
||||
|
||||
private async Task CreateNutritionObservation(string value, ObjectId patientId)
|
||||
{
|
||||
var nutritionObs = new PatientObservation
|
||||
{
|
||||
PatientId = patientId,
|
||||
Name = "Feeding_Type",
|
||||
CodingSystem = CodingSystem,
|
||||
Value = value,
|
||||
Time = DateTime.Now
|
||||
};
|
||||
|
||||
if (value.ToLower().Contains("parenteral"))
|
||||
{
|
||||
nutritionObs.Name = "Parenteral";
|
||||
nutritionObs.Value = "SI";
|
||||
}
|
||||
|
||||
var lastNutritionObsList =
|
||||
await _observationService.Value.FindLastObservations(patientId, 1, [nutritionObs.Name]);
|
||||
var lastNutritionObs = lastNutritionObsList.FirstOrDefault();
|
||||
if (lastNutritionObs is { Expired: false })
|
||||
{
|
||||
//expiramos la anterior
|
||||
lastNutritionObs.Expired = true;
|
||||
_ = _observationService.Value.UpdateObservation(lastNutritionObs);
|
||||
}
|
||||
|
||||
await _observationService.Value.InsertObservation(nutritionObs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.NursePlan;
|
||||
|
||||
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
|
||||
{
|
||||
private readonly Lazy<IObservationService> _observationService =
|
||||
new(serviceProvider.GetRequiredService<IObservationService>);
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
|
||||
}
|
||||
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
return Task.FromResult(obs)!;
|
||||
}
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
return Task.FromResult(treatment);
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
return Task.FromResult(diagnosis);
|
||||
}
|
||||
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class ApiRequestException(string message) : Exception($"Api request exception: {message}");
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class BadRequestException : Exception
|
||||
{
|
||||
public BadRequestException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public BadRequestException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class ConflictException : Exception
|
||||
{
|
||||
public ConflictException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public ConflictException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class CustomArgumentException : Exception
|
||||
{
|
||||
public CustomArgumentException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public CustomArgumentException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class ForbbidenException : Exception
|
||||
{
|
||||
public ForbbidenException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public ForbbidenException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class InvalidFormatException : Exception
|
||||
{
|
||||
public InvalidFormatException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public InvalidFormatException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class NotFoundException : Exception
|
||||
{
|
||||
public NotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
public NotFoundException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public NotFoundException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class TokenException(string message) : Exception(message);
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class UnauthorizedException : Exception
|
||||
{
|
||||
public UnauthorizedException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public UnauthorizedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Enums;
|
||||
|
||||
namespace adas_core.Application.Exceptions;
|
||||
|
||||
public class UnprocessableEntityException : Exception
|
||||
{
|
||||
public UnprocessableEntityException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
|
||||
{
|
||||
}
|
||||
|
||||
public UnprocessableEntityException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Providers;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace adas_core.Application.Providers;
|
||||
|
||||
public class AdasProvider(IOptions<ProvidersSettings> providerSettings, IHttpClientFactory httpClientFactory)
|
||||
: BaseProvider(providerSettings, httpClientFactory)
|
||||
{
|
||||
/*
|
||||
* Get observations of:
|
||||
* Predict of UCI stay duration
|
||||
* Predict of medications for patients
|
||||
*/
|
||||
public override async Task<List<PatientObservation>> GetObservations(Patient patient)
|
||||
{
|
||||
var calculatedObservations = new List<PatientObservation>();
|
||||
|
||||
//get data from two endpoints one from UCI stay another for medication
|
||||
var predictOfStay = await GetPredictOfStay(patient.PatientNumber);
|
||||
|
||||
if (predictOfStay == null || predictOfStay.Result.ToLower().Contains("error"))
|
||||
Log.Error("Error retrieving predict of stay in ADAS result, message: {predictOfStay}", predictOfStay);
|
||||
else
|
||||
{
|
||||
var durationPredicted1 = predictOfStay.Payload.MaxBy(i => i.PercentageMin)!.Duration;
|
||||
var durationPredicted2 = predictOfStay.Payload.MinBy(i => i.PercentageMin)!.Duration;
|
||||
|
||||
calculatedObservations.Add(
|
||||
new PatientObservation
|
||||
{
|
||||
PatientId = patient.Id,
|
||||
CodingSystem = "ADAS",
|
||||
Name = "Stay_Predict_Days_Most_Confident",
|
||||
//get the most confident interval to show the most probable scenario
|
||||
Value = durationPredicted1,
|
||||
Time = DateTime.UtcNow
|
||||
});
|
||||
|
||||
calculatedObservations.Add(new PatientObservation
|
||||
{
|
||||
PatientId = patient.Id,
|
||||
CodingSystem = "ADAS",
|
||||
Name = "Stay_Predict_Days_Less_Confident",
|
||||
//get the less confident interval to show the less probable scenario
|
||||
Value = durationPredicted2,
|
||||
Time = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var predictMedication = await GetPredictMedications(patient.PatientNumber);
|
||||
|
||||
|
||||
if (predictMedication != null && predictMedication.Any())
|
||||
calculatedObservations.Add(new PatientObservation
|
||||
{
|
||||
PatientId = patient.Id,
|
||||
CodingSystem = "ADAS",
|
||||
Name = "Medication_Predict",
|
||||
Value = ParseAdasMedicationsToMedicationObservation(predictMedication),
|
||||
Time = DateTime.UtcNow
|
||||
});
|
||||
|
||||
|
||||
return calculatedObservations;
|
||||
}
|
||||
|
||||
private static string ParseAdasMedicationsToMedicationObservation(
|
||||
List<ResultModelPredictMedicationAdas> medicationPredict)
|
||||
{
|
||||
return string.Join("^", medicationPredict.Take(5));
|
||||
}
|
||||
|
||||
private async Task<ResultModelPredictOfStayAdas?> GetPredictOfStay(string? patientNumber)
|
||||
{
|
||||
if (patientNumber == null)
|
||||
{
|
||||
Log.Error("Error retrieving ADAS predict of stay patient number is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var client = GetClient();
|
||||
var response = await client.GetAsync($"predictStay?patientNumber={patientNumber}");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<ResultModelPredictOfStayAdas>(content);
|
||||
}
|
||||
|
||||
Log.Error(
|
||||
"Error retrieving calculated ADAS observation length of stay for patient: {patientNumber}, status code: {response.StatusCode} ",
|
||||
patientNumber, response.StatusCode);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Exception retrieving ADAS predict of stay for patient: {patientNumber} exception: {e}",
|
||||
patientNumber, e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<ResultModelPredictMedicationAdas>?> GetPredictMedications(string? patientNumber)
|
||||
{
|
||||
if (patientNumber == null)
|
||||
{
|
||||
Log.Error("Error retrieving ADAS predict of stay for patient number is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var client = GetClient();
|
||||
|
||||
var response = await client.GetAsync($"predictMedicationNeedsPharmacy?patientNumber={patientNumber}");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<List<ResultModelPredictMedicationAdas>>(content);
|
||||
}
|
||||
|
||||
Log.Error(
|
||||
"Error retrieving calculated ADAS medicines for patient: {patientNumber}, status code: {response.StatusCode}",
|
||||
patientNumber, response.StatusCode);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Exception retrieving ADAS predict medicines for patient: {patientNumber} exception: {e}",
|
||||
patientNumber, e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private HttpClient GetClient()
|
||||
{
|
||||
var client = HttpClientFactory.CreateClient();
|
||||
client.BaseAddress = new Uri(Url);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Providers;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace adas_core.Application.Providers;
|
||||
|
||||
public abstract class BaseProvider(
|
||||
IOptions<ProvidersSettings> providerSettings,
|
||||
IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
protected IHttpClientFactory HttpClientFactory = httpClientFactory;
|
||||
protected string Url { get; set; } = providerSettings.Value.Url;
|
||||
|
||||
public abstract Task<List<PatientObservation>> GetObservations(Patient patient);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IAdmissionRepository : IMongoRepository<Admission>
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
|
||||
Task Update(Admission admission);
|
||||
|
||||
Task UpdateLocation(ObjectId id, ObjectId newLocation);
|
||||
|
||||
Task UpdatePatient(ObjectId id, Person patient);
|
||||
|
||||
Task<IEnumerable<Admission>> FindAll();
|
||||
|
||||
Task<Admission?> FindById(ObjectId id);
|
||||
|
||||
Task<Admission?> FindByNhc(string nhc);
|
||||
|
||||
//Task<IEnumerable<Admission>?> FindByUnit(string unit);
|
||||
|
||||
Task<List<Admission>> FindByLocation(PatientLocation location);
|
||||
|
||||
Task<IEnumerable<Admission>?> FindByOrigin(string origin);
|
||||
Task<Admission?> InsertOneAsyncAndReturn(Admission origin);
|
||||
Task<Admission?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
|
||||
Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId);
|
||||
Task<List<Admission>> FindByPointOfCareId(ObjectId pocId);
|
||||
Task<List<Admission>> FindByUnitIds(List<ObjectId> unitIds);
|
||||
Task<long> CountByUnitId(ObjectId unitId);
|
||||
|
||||
Task<IEnumerable<Admission>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
|
||||
string typeName);
|
||||
|
||||
Task<IEnumerable<Admission>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
|
||||
Task<bool> DeleteAdmissionsByUnitId(ObjectId unitId);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IAlarmRepository : IMongoRepository<PatientObservationAlarm>
|
||||
{
|
||||
Task<List<PatientObservationAlarm>> AggregatedPatientLastObservationsByField(ObjectId patientId,
|
||||
List<Field>? filterObservations = null);
|
||||
|
||||
Task<List<PatientObservationAlarm>> AggregatedPatientNotExpiredObservationsByField(ObjectId patientId,
|
||||
List<Field>? filterObservations, List<ConfigObservation> configAlarm);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using adas_core.Domain.Models;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IAppointmentArchiveRepository
|
||||
{
|
||||
Task InsertOneAsync(PatientAppointment patientAppointment);
|
||||
Task DeleteBeforeDate(DateTime date);
|
||||
Task<long> InsertBatch(IEnumerable<PatientAppointment> appointment);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IAppointmentRepository : IMongoRepository<PatientAppointment>
|
||||
{
|
||||
Task<List<PatientAppointment>> GetByPatient(ObjectId patientId);
|
||||
|
||||
new Task InsertOneAsync(PatientAppointment appointment);
|
||||
Task Update(PatientAppointment appointment);
|
||||
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
|
||||
new Task DeleteAsync(ObjectId id);
|
||||
Task<IAsyncCursor<PatientAppointment>> FindByPatientIdAsync(ObjectId patientId);
|
||||
Task DeleteByPatientId(ObjectId patientId);
|
||||
Task<PatientAppointment?> FindByPatientAndVisitNumber(ObjectId patientId, string visitNumber);
|
||||
Task<PatientAppointment?> FindByPatientAndReason(ObjectId patientId, string? appointmentReason);
|
||||
Task<List<PatientAppointment>> FindByLocation(PatientLocation location);
|
||||
Task<List<PatientAppointment>> FindByPoC(PointOfCare poc);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IArchivePatientCarePlanRepository : IMongoRepository<PatientCarePlan>
|
||||
{
|
||||
Task<List<PatientCarePlan>?> FindByPatientId(ObjectId patientId);
|
||||
Task<List<PatientCarePlan>?> FindByPatientId(string patientId);
|
||||
Task<List<PatientCarePlan>?> FindByPatientNumber(string patientId);
|
||||
Task<List<PatientCarePlan>> FindAll();
|
||||
|
||||
Task<List<PatientCarePlan>?> FindByIds(ObjectId oldPatientId, string? oldPatientPatientId,
|
||||
string? oldPatientPatientNumber);
|
||||
|
||||
Task InsertManyAsync(List<PatientCarePlan> patientCarePla);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IAuthorityRepository : IMongoRepository<Authorization>
|
||||
{
|
||||
public Task<Authorization> GetById(ObjectId authId);
|
||||
void CreateNewAuthority(string roleName, ObjectId userId);
|
||||
public Task<List<Authorization>> GetUserAuthorities(ObjectId userId);
|
||||
public Task<List<Authorization>> GetAllAuthorities();
|
||||
public Task<bool> DeleteAllAuthoritiesByUser(ObjectId userId);
|
||||
Task<bool> DeleteAllAuthoritiesByUnit(ObjectId unitId);
|
||||
Task<bool> DeleteAllAuthoritiesByDisplay(ObjectId displayId);
|
||||
Task<List<Authorization>> GetByUnitId(ObjectId unitId);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface ICameraRepository : IMongoRepository<Camera>
|
||||
{
|
||||
Task<Camera?> GetById(ObjectId cameraId);
|
||||
Task<Camera?> GetByName(string name);
|
||||
List<Camera> GetCameraInList(List<ObjectId> configurationRelayList);
|
||||
IFindFluent<Camera, Camera> GetPaginatedCameras(PaginationFilter request);
|
||||
Task<Camera?> InsertOneCamera(Camera camera);
|
||||
Task<Camera?> UpdateCameraAsync(ObjectId objectId, Camera camera);
|
||||
Task<List<Camera>> GetSearchByNameCameras(string textToSearch);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IConfigObservationRepository : IMongoRepository<ConfigObservation>
|
||||
{
|
||||
Task<ConfigObservation?> FindById(ObjectId id);
|
||||
|
||||
Task<ConfigObservation?> Update(ConfigObservation configObservation);
|
||||
|
||||
Task<ConfigObservation?> Delete(ObjectId id);
|
||||
|
||||
Task<List<ObjectId>> FindAllIds();
|
||||
|
||||
Task<ICollection<ConfigObservation>> FindAll();
|
||||
|
||||
Task<List<string>> GetConfigNames(string id);
|
||||
Task<List<string>> GetConfigNames();
|
||||
Task<long> Count();
|
||||
Task<ICollection<ConfigObservation>> GetPaginatedItems(PaginationFilter filter);
|
||||
Task<ConfigObservation?> FindByName(string name);
|
||||
Task<ConfigObservation?> GetByCodeSysAndCode(string? codingSystem, string? code);
|
||||
Task<ConfigObservation> InsertOneAsyncAndReturn(ConfigObservation configObservationItem);
|
||||
Task<List<ConfigObservation>> FindAllByName(string name);
|
||||
|
||||
Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem, string? name,
|
||||
string? originalName);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IConfigPumpsRepository : IMongoRepository<ConfigPumps>
|
||||
{
|
||||
Task<ConfigPumps?> FindById(string id);
|
||||
Task<List<ConfigPumps>?> GetAllConfigs();
|
||||
|
||||
Task<ConfigPumps?> UpdateConfig(ConfigPumps config);
|
||||
Task<bool> DeleteConfig(ConfigPumps config);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IConfigUnitsRepository : IMongoRepository<ConfigUnits>
|
||||
{
|
||||
Task<ConfigUnits?> FindById(string id);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDeviceRepository : IMongoRepository<Device>
|
||||
{
|
||||
Task<Device?> FindByMacAddr(string deviceDtoMacAddr);
|
||||
Task<Device?> FindBySerialNumber(string deviceDtoSerialNumber);
|
||||
Task<Device?> FindByUuid(string deviceDtoUuid);
|
||||
Task<Device?> FindByKey(string deviceDtoKey);
|
||||
Task UpdateDeviceStats(ObjectId id,DeviceDto deviceExist);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using adas_core.Domain.Models;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDiagnosisArchiveRepository
|
||||
{
|
||||
Task InsertOneAsync(PatientDiagnosis patientDiagnosis);
|
||||
Task DeleteBeforeDate(DateTime date);
|
||||
Task<long> InsertBatch(IEnumerable<PatientDiagnosis> diagnosis);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using adas_core.Domain.Models;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDiagnosisRepository : IMongoRepository<PatientDiagnosis>
|
||||
{
|
||||
Task<List<PatientDiagnosis>> GetByPatient(ObjectId patientId);
|
||||
|
||||
new Task InsertOneAsync(PatientDiagnosis diagnosis);
|
||||
new Task DeleteAsync(ObjectId id);
|
||||
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
|
||||
Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId);
|
||||
Task DeleteByPatientId(ObjectId patientId);
|
||||
Task<PatientDiagnosis?> FindByPatientIdAndCode(ObjectId patientId, string? diagnosisCode, string? codingSystem);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDischargeRepository : IMongoRepository<Discharge>
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
|
||||
Task Update(Discharge discharge);
|
||||
|
||||
Task UpdateUnit(ObjectId id, string unit);
|
||||
|
||||
Task UpdatePatient(ObjectId id, Patient patient);
|
||||
|
||||
Task<IEnumerable<Discharge>> FindAll();
|
||||
|
||||
Task<Discharge?> FindById(ObjectId id);
|
||||
|
||||
Task<IEnumerable<Discharge>?> FindByUnit(string unit);
|
||||
|
||||
Task<long> CountByUnitId(ObjectId unitId);
|
||||
|
||||
Task<IEnumerable<Discharge>?> FindByDestination(string destination);
|
||||
|
||||
Task<IEnumerable<Discharge>?> FindByService(string service);
|
||||
Task<IEnumerable<Discharge>?> GetDischargesByUnitIds(IEnumerable<ObjectId>? unitIds);
|
||||
|
||||
|
||||
Task<Discharge?> GetDischargeByLocation(PatientLocation location);
|
||||
Task<Discharge?> GetByPatientId(ObjectId patientId);
|
||||
|
||||
Task<IEnumerable<Discharge>?> FindByPoCId(ObjectId pocId);
|
||||
Task<Discharge?> GetDischargeByPointOfCareId(ObjectId poc);
|
||||
|
||||
Task<IEnumerable<Discharge>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
|
||||
string typeName);
|
||||
|
||||
Task<IEnumerable<Discharge>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
|
||||
Task<bool> DeleteByUnitId(ObjectId unitId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDisplayCardConfigRepository : IMongoRepository<CardConfig>
|
||||
{
|
||||
Task<List<CardConfig>> GetAll();
|
||||
Task<CardConfig?> GetById(ObjectId configId);
|
||||
Task<CardConfig?> InsertOneAsyncAndReturn(CardConfig config);
|
||||
Task<UpdateResponse<CardConfig?>> UpdateOne(CardConfig? config);
|
||||
Task<CardConfig?> DeleteOne(ObjectId configId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDisplayChartConfigRepository : IMongoRepository<ChartConfig>
|
||||
{
|
||||
Task<List<ChartConfig>> GetAll();
|
||||
Task<ChartConfig?> GetById(ObjectId configId);
|
||||
Task<ChartConfig?> InsertOneAsyncAndReturn(ChartConfig config);
|
||||
Task<UpdateResponse<ChartConfig?>> UpdateOne(ChartConfig? config);
|
||||
Task<ChartConfig?> DeleteOne(ObjectId configId);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.DTO.Display;
|
||||
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 MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDisplayConfigRepository : IMongoRepository<DisplayConfig>
|
||||
{
|
||||
Task<List<DisplayConfig>> GetAll();
|
||||
IFindFluent<DisplayConfig, DisplayConfigSummary> GetAllPaginated(PaginationFilter request);
|
||||
Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type);
|
||||
Task<DisplayConfig?> GetById(ObjectId id);
|
||||
Task<DisplayConfig?> GetDefault(DisplayConfigEnums.DisplayType type);
|
||||
Task<DisplayConfig?> InsertOneAsyncAndReturn(DisplayConfig config);
|
||||
Task<SmartDisplay?> UpdateSmartDisplay(ObjectId displayConfigId, SmartDisplay? newDisplayConfig);
|
||||
|
||||
Task<DisplayNurse?> UpdateDisplayNurse(ObjectId displayConfigId, DisplayNurseDto? newDisplayConfig,
|
||||
List<string> nurseObs);
|
||||
|
||||
Task<DisplayConfig?> GetDefaultByUnitIdAndType(ObjectId unitId, DisplayConfigEnums.DisplayType displayType);
|
||||
Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfig);
|
||||
Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems);
|
||||
Task<bool> UpdateBaseConfig(ObjectId objectIdConfigDisplay, DisplayConfig baseConfig);
|
||||
Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig);
|
||||
Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name);
|
||||
Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields);
|
||||
Task<DisplayConfig?> DeleteDisplayConfig(ObjectId objectIdConfigDisplay);
|
||||
Task<List<DisplayConfigMinimalResponse>> GetAllCompact();
|
||||
Task<List<ObjectId>> GetAllByCardConfigId(ObjectId cardConfigId);
|
||||
Task<List<ObjectId>> GetAllByCardConfigIdAndRotating(ObjectId cardConfigId);
|
||||
Task<bool> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId);
|
||||
Task<bool> AddChartId(ObjectId? displayConfigId, ObjectId newChartIdToAdd);
|
||||
Task<UpdateResult> UpdateDeletedChartConfig(ObjectId deletedId);
|
||||
Task<ChartConfig> GetChartConfig(ObjectId objectIdConfigChart);
|
||||
Task<bool> UpdateDetailConfigId(ObjectId? displayConfigId, ObjectId? resultId);
|
||||
Task<List<ObjectId>> GetAllByCardDetailConfigId(ObjectId baseConfigId);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDisplayDetailConfigRepository : IMongoRepository<CardDetailsConfig>
|
||||
{
|
||||
Task<List<CardDetailsConfig>> GetAll();
|
||||
Task<CardDetailsConfig?> GetById(ObjectId configId);
|
||||
Task<CardDetailsConfig?> InsertOneAsyncAndReturn(CardDetailsConfig config);
|
||||
Task<UpdateResponse<CardDetailsConfig?>> UpdateOne(CardDetailsConfig? config);
|
||||
Task<CardDetailsConfig?> DeleteOne(ObjectId configId);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IDisplayRepository : IMongoRepository<Display>
|
||||
{
|
||||
Task<List<Display>> GetAll();
|
||||
|
||||
// ¿FilterByAuthorities / FindByUser
|
||||
// Delete
|
||||
// FindByUnit
|
||||
// UpdateConfig -> algo en específico? ¿Toda la colección?
|
||||
Task<List<Display>> GetByPointOfCare(PointOfCare pointOfCare);
|
||||
|
||||
// Task<List<Display>> GetByUser();
|
||||
Task<Display?> GetByName(string name);
|
||||
Task<Display?> GetById(ObjectId id);
|
||||
Task<Display?> GetByIdWithConfigDisplay(ObjectId id);
|
||||
Task<List<Display>> GetByUnitId(ObjectId id);
|
||||
Task<List<Display>> GetByConfigId(ObjectId id);
|
||||
|
||||
Task<Display?> UpdatePointOfCareList(ObjectId objectId, List<ObjectId> listPocObId);
|
||||
Task<Display?> UpdateConfig(ObjectId oldDisplayId, DisplayConfig newDisplayConfigCast);
|
||||
Task<Display?> UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay);
|
||||
Task<Display> UpdateName(Display display, string name);
|
||||
IFindFluent<Display, Display> GetPaginatedDisplays(PaginationFilter filter);
|
||||
Task<long> IsDisplayConfigInUse(ObjectId displayConfigId);
|
||||
Task<Display?> UpdateConfigId(ObjectId objectId, ObjectId displayConfigId);
|
||||
Task<long> CountByUnitId(ObjectId unitId);
|
||||
Task<bool> DeleteManyByUnitId(ObjectId unitId);
|
||||
Task<List<Display>> GetByCardConfigId(ObjectId configId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IHistoricalConfigChangesRepository : IMongoRepository<HistoricalConfigChanges>
|
||||
{
|
||||
Task<HistoricalConfigChanges?> FindById(ObjectId id);
|
||||
|
||||
Task<HistoricalConfigChanges?> Update(HistoricalConfigChanges historicalConfigChange);
|
||||
|
||||
Task<HistoricalConfigChanges?> Delete(ObjectId id);
|
||||
|
||||
Task<List<ObjectId>> FindAllIds();
|
||||
|
||||
Task<ICollection<HistoricalConfigChanges>> FindAll();
|
||||
new Task<HistoricalConfigChanges?> InsertOneAsync(HistoricalConfigChanges patientObservation);
|
||||
|
||||
Task<ICollection<HistoricalConfigChanges>> FindLastHistoricalConfigChangesByType(
|
||||
DisplayConfigEnums.ConfigTypes cfgType, int num = 10);
|
||||
|
||||
Task<ICollection<HistoricalConfigChanges>> FindLastHistoricalConfigChangesByUser(string user,
|
||||
DisplayConfigEnums.ConfigTypes? cfgType = null, int num = 10);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface ILightBeaconRepository : IMongoRepository<LightBeacon>
|
||||
{
|
||||
List<LightBeacon> GetLightBeaconInList(List<ObjectId> configurationRelayList);
|
||||
Task<LightBeacon?> GetById(ObjectId relayId);
|
||||
Task<LightBeacon?> GetByName(string name);
|
||||
Task<LightBeacon?> InsertOneAsyncAndReturn(LightBeacon beacon);
|
||||
IFindFluent<LightBeacon, LightBeacon> GetPaginatedRelays(PaginationFilter filter);
|
||||
Task<List<LightBeacon>> GetSearchByName(string textToSearch);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IMasterListRepository<T> : IMongoRepository<T> where T : MasterList
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
Task Update(T list);
|
||||
Task<T?> FindById(ObjectId id, LocaleEnum? locale);
|
||||
Task<T?> FindById(ObjectId id);
|
||||
Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale);
|
||||
Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId);
|
||||
Task<IEnumerable<T>> GetAll();
|
||||
Task<IEnumerable<MasterListDto>> GetAllWithoutOptions();
|
||||
Task<T?> FindByName(string name);
|
||||
Task<List<OptionList>> GetMasterListByIdAndTextSearchContaining(ObjectId id, string? textSearch);
|
||||
Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt);
|
||||
Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt, LocaleEnum locale);
|
||||
Task<OptionList?> UpdateFullMasterListOption(ObjectId id, OptionList newOpt);
|
||||
Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt);
|
||||
Task<bool> DeleteMasterListOption(ObjectId id, ObjectId deleteOptId);
|
||||
Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id, UpdateMasterListDetailsDto opt);
|
||||
Task<bool> UpdateMasterListName(ObjectId id, string name);
|
||||
Task<bool> UpdateMasterListDescription(ObjectId id, string description);
|
||||
Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt);
|
||||
IFindFluent<T, T> GetPaginatedMasterList(PaginationFilter filter);
|
||||
Task<List<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId);
|
||||
Task<int> Count();
|
||||
Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id, FilterOptionListElement? filterOption);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IMedicineRepository : IMongoRepository<Medicine>
|
||||
{
|
||||
Task<Medicine?> GetMedicine(string code);
|
||||
|
||||
Task<List<Medicine>> GetMedicineByCodeOrNote(List<string> codeNotes);
|
||||
Task<Medicine?> GetMedicineByName(string name);
|
||||
Task<List<Medicine>> GetAll();
|
||||
Task<Medicine?> GetMedicineById(ObjectId medicineId);
|
||||
Task<Medicine?> PostMedicine(Medicine medicine);
|
||||
Task<Medicine?> UpdateMedicine(Medicine medicine);
|
||||
Task DeleteMedicineById(ObjectId medicineId);
|
||||
IAggregateFluent<BsonDocument> GetDistinctFieldDataQuery(string field);
|
||||
|
||||
IFindFluent<Medicine, Medicine> GetPaginatedMedicines(PaginationFilter filter);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IMongoRepository<T>
|
||||
{
|
||||
IMongoCollection<T> Collection { get; }
|
||||
|
||||
string GetCollectionName();
|
||||
Task InsertOneAsync(T obj);
|
||||
Task<T?> DeleteAsync(ObjectId id);
|
||||
Task UpdateOneAsync(ObjectId id, T obj);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface INoticeRepository : IMongoRepository<Notice>
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
Task Update(Notice notice);
|
||||
Task<IEnumerable<Notice>> FindAll();
|
||||
Task<Notice?> FindById(ObjectId id);
|
||||
Task<IEnumerable<Notice>?> FindByDate(DateTime date);
|
||||
Task<IEnumerable<Notice>?> FindByType(string type);
|
||||
Task<IEnumerable<Notice>?> FindByDisplayId(ObjectId displayId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using adas_core.Domain.Models;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IObservationArchiveRepository : IMongoRepository<PatientObservation>
|
||||
{
|
||||
new Task InsertOneAsync(PatientObservation patientObservation);
|
||||
Task DeleteBeforeDate(DateTime date);
|
||||
Task<long> InsertBatch(IEnumerable<PatientObservation> observations);
|
||||
|
||||
Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num, DateTime lastDate,
|
||||
List<string> filterObservations);
|
||||
|
||||
Task<List<PatientObservation>> FindAllFromPatient(ObjectId patientId);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IObservationRepository : IMongoRepository<PatientObservation>
|
||||
{
|
||||
Task<List<BsonDocument>> AggregatedPatientGroupedObservations(ObjectId patientId, GroupedField groupedField);
|
||||
|
||||
Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
|
||||
List<string>? filterObservations = null);
|
||||
|
||||
Task<List<PatientObservation>> AggregatedPatientLastObservationsByLastDate(ObjectId patientId, int num,
|
||||
DateTime lastDate, List<string>? filterObservations = null);
|
||||
|
||||
Task<List<PatientObservation>> AggregatedPatientLastObservationsByField(ObjectId patientId,
|
||||
List<Field>? filterObservations = null);
|
||||
|
||||
Task<IEnumerable<PatientObservation>> FindLastObservations(ObjectId patientId, string codingSystem, string code,
|
||||
int num = 2);
|
||||
|
||||
Task<List<PatientObservation>> FindLastObservationsByCodingSystem(ObjectId patientId, string codingSystem,
|
||||
int num = 10);
|
||||
|
||||
Task<PatientObservation?> FindLastObservationBeforeDate(ObjectId patientId, string? name, DateTime date);
|
||||
|
||||
Task UpdateExpiredObservations(List<PatientObservation> expiredObservations);
|
||||
|
||||
Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, string? name, DateTime date);
|
||||
Task<List<PatientObservation>> FindAnyBeforeDate(ObjectId patientId, DateTime date);
|
||||
|
||||
Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name, int? expires);
|
||||
|
||||
new Task InsertOneAsync(PatientObservation patientObservation);
|
||||
Task DeleteByPatientId(ObjectId id);
|
||||
Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId);
|
||||
|
||||
Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId, string codingSystem,
|
||||
string name);
|
||||
|
||||
new Task DeleteAsync(ObjectId id);
|
||||
Task<List<PatientObservation>> DeleteOlderDaysAsync(string name, int retentionPolicyValue);
|
||||
Task<List<PatientObservation>> DeleteOlderNumberAsync(string name, int retentionPolicyValue);
|
||||
Task<bool> ExistBySystemId(ObjectId patientid, string systemId);
|
||||
Task<List<PatientObservation>> DeleteOlderSecondsAsync(string name, int value);
|
||||
Task<List<PatientObservation?>> AggregatedPatientActiveIntravenousLinesObservations(ObjectId patientId);
|
||||
Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime();
|
||||
|
||||
Task<PatientObservation?> FindById(ObjectId id);
|
||||
|
||||
Task<List<PatientObservation>?> FindByPatientId(ObjectId id);
|
||||
Task Update(PatientObservation observation);
|
||||
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
|
||||
|
||||
Task<IEnumerable<PatientObservation>> FindNotExpired(List<string?>? filterObservations);
|
||||
|
||||
Task<IEnumerable<PatientObservation>> FindByName(string name, DateTime? date);
|
||||
|
||||
Task UpdateMany(IEnumerable<PatientObservation> patientObservations, UpdateDefinition<PatientObservation> update);
|
||||
|
||||
Task<IEnumerable<PatientObservation>> FindAll();
|
||||
|
||||
Task ExpireExpiredObservations(List<ConfigObservation> configObservationsToExpire);
|
||||
//Task<PaginationResponse<PatientObservation>> GetPaginatedObservations(PaginationFilter filter);
|
||||
|
||||
IFindFluent<PatientObservation, PatientObservation> GetPaginatedObservations(PaginationFilter filter);
|
||||
|
||||
Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId, string name,
|
||||
int? endAfter = null, int? num = null);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPatientArchiveRepository : IMongoRepository<Patient>
|
||||
{
|
||||
Task<Patient?> FindByPatientNumber(string patientNumber);
|
||||
|
||||
Task<List<Patient>> FindAll();
|
||||
Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPatientCarePlanRepository : IMongoRepository<PatientCarePlan>
|
||||
{
|
||||
Task<List<PatientCarePlan>> FindByPatientId(ObjectId patientId);
|
||||
Task<List<PatientCarePlan>> FindByUserId(ObjectId userId);
|
||||
Task<List<PatientCarePlan>> FindAll();
|
||||
Task UpdateManyObjectId(string patientid, ObjectId patientId, ObjectId oldId);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPatientRepository : IMongoRepository<Patient>
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
Task<Patient?> FindByLocation(PatientLocation location);
|
||||
Task<Patient?> FindById(ObjectId id);
|
||||
Task UpdateLocation(ObjectId id, PatientLocation location);
|
||||
Task UpdateLocation(ObjectId id, ObjectId location);
|
||||
Task UpdateAttendingDoctor(ObjectId id, Person attendingDoctor);
|
||||
Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true);
|
||||
Task Update(Patient patient);
|
||||
Task<Patient?> FindByPatientNumber(string patientNumber);
|
||||
Task<Patient?> FindByPatientId(string patientId);
|
||||
Task<List<Patient>> FindAll();
|
||||
Task<List<Patient>> FindByPointOfCare(string pointOfCare);
|
||||
Task<List<Patient>> FindByPointOfCare(ObjectId pointOfCare);
|
||||
Task<Patient?> FindByPointOfCareId(ObjectId pointOfCare);
|
||||
Task<List<Patient>> FindDischargedPatients();
|
||||
Task<Patient?> UpdateOne(Patient updatedPatient);
|
||||
Task<List<Patient>> FindInActivePoC();
|
||||
Task<List<Patient>> FindInInactivePoC();
|
||||
Task<List<Patient>> FindPatientsNotUpdatedSince(DateTime date);
|
||||
Task<Patient?> FindByPatientId(ObjectId patientId);
|
||||
Task<Patient?> UpdatePatientIncomingData(ObjectId patientId, Patient person);
|
||||
Task<Patient?> UpdatePatientDemographicData(ObjectId patientId, Patient person);
|
||||
Task<Patient> FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare);
|
||||
Task<long> CountByUnitId(ObjectId unitId);
|
||||
Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
|
||||
IFindFluent<Patient, Patient> GetPaginatedPatients(PaginationFilter filter);
|
||||
Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes);
|
||||
Task<List<Patient>> FindAllPatientWithFinishedTests(int archiveTestEndDateAfterMinutes);
|
||||
Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes);
|
||||
Task<List<Patient>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt, string typeName);
|
||||
Task<List<Patient>> GetPatientsByUnitIds(List<ObjectId> unitIds, string typeName);
|
||||
Task<IEnumerable<Patient>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPoCMappingRepository
|
||||
{
|
||||
Task<PoCMapping?> FindByKey(string key);
|
||||
string GetCollectionName();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPoCSettingsRepository : IMongoRepository<PoCSettings>
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
|
||||
Task<PoCSettings?> FindByLocation(PatientLocation location);
|
||||
|
||||
Task<PoCSettings?> FindById(ObjectId id);
|
||||
|
||||
Task Update(PoCSettings pocSettings);
|
||||
|
||||
Task<List<PoCSettings>> FindAll();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPointOfCareRepository : IMongoRepository<PointOfCare>
|
||||
{
|
||||
Task Delete(ObjectId id);
|
||||
|
||||
Task Update(PointOfCare pointOfCare);
|
||||
|
||||
Task UpdateUnitId(ObjectId id, Unit unit);
|
||||
|
||||
Task UpdateConfiguration(ObjectId id, PointOfCareConfiguration configuration);
|
||||
|
||||
Task<PointOfCare?> FindById(ObjectId id);
|
||||
|
||||
Task<IEnumerable<PointOfCare>?> FindAllByUnitId(ObjectId unit);
|
||||
|
||||
Task<IEnumerable<PointOfCare>?> FindByRoom(string room);
|
||||
|
||||
Task<List<PointOfCare>> FindByFilter(FilterDefinition<PointOfCare> filter,
|
||||
ProjectionDefinition<PointOfCare>? projection = null);
|
||||
|
||||
Task<IEnumerable<PointOfCare>?> FindByBed(string bed);
|
||||
Task<List<PointOfCare>?> GetAll();
|
||||
Task<List<PointOfCare>?> GetAllConfigs();
|
||||
|
||||
Task<List<PointOfCare>?> GetAllLocationInfo();
|
||||
|
||||
Task<PointOfCare?> GetPoCConfiguration(ObjectId pocId);
|
||||
|
||||
|
||||
Task<PointOfCare?> FindByBedAndUnitId(string? bed, ObjectId unitId);
|
||||
|
||||
Task<PointOfCare?> FindByPatientLocation(PatientLocation patientLocation);
|
||||
|
||||
Task<IEnumerable<PointOfCare>> FindByUnitAndStatus(ObjectId unitId, StatusEnum.PointOfCare status,
|
||||
bool excludeVirtual = false);
|
||||
|
||||
Task UpdateRelayConfig(ObjectId pocId, List<Relay> relayConfig);
|
||||
Task UpdateRelayConfig(ObjectId pocId, List<ObjectId> relayConfig);
|
||||
|
||||
IFindFluent<PointOfCare, PointOfCare> GetPaginatedPoCs(PaginationFilter filter);
|
||||
Task<long> CountByUnitId(ObjectId unitId);
|
||||
Task<long> CountVirtualsByUnitId(ObjectId unitId);
|
||||
Task<bool> DeleteManyByUnitId(ObjectId unitId);
|
||||
Task<PointOfCare?> FindByIdAllConfig(ObjectId id);
|
||||
Task<HashSet<ObjectId>> FindAllIdCamerasInUse();
|
||||
Task<HashSet<ObjectId>> FindAllIdRelaysInUse();
|
||||
Task<HashSet<ObjectId>> FindAllIdBeaconsInUse();
|
||||
Task<IEnumerable<PointOfCare>?> FindAllByUnitIdWithDevices(ObjectId unitId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPumpAlarmEventRepository
|
||||
{
|
||||
Task InsertAsync(PumpAlarmEvent alarmEvent);
|
||||
|
||||
Task<IEnumerable<PumpAlarmEvent>> FindByDeviceIdAsync(
|
||||
string deviceId,
|
||||
DateTime? from = null,
|
||||
DateTime? to = null,
|
||||
int? limit = null);
|
||||
|
||||
Task<PumpAlarmEvent?> FindLastByDeviceIdAsync(string deviceId);
|
||||
|
||||
// cleanup
|
||||
Task DeleteByPatientId(ObjectId patientId);
|
||||
|
||||
Task<long> UpdateManyObjectIdByFiledNameAsync(string fieldName, ObjectId newId, ObjectId oldId);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPumpAlarmStateRepository
|
||||
{
|
||||
Task<PumpAlarmState?> FindActiveAsync(
|
||||
string deviceId,
|
||||
PumpEnum.AlarmType? alarmType,
|
||||
string? alarmCodeMdc = null);
|
||||
|
||||
Task<IEnumerable<PumpAlarmState>> FindAllActiveByDeviceAsync(string deviceId);
|
||||
|
||||
Task UpsertActiveAsync(PumpAlarmState state);
|
||||
|
||||
Task RemoveAsync(
|
||||
string deviceId,
|
||||
PumpEnum.AlarmType? alarmType,
|
||||
string? alarmCodeMdc = null);
|
||||
|
||||
//cleanup
|
||||
Task DeleteByPatientId(ObjectId patientId);
|
||||
|
||||
Task<long> UpdateManyObjectIdByFieldNameAsync(string fieldName, ObjectId newId, ObjectId oldId);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces
|
||||
{
|
||||
public interface IPumpArchiveRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Devuelve la colección subyacente
|
||||
/// </summary>
|
||||
IMongoCollection<PumpObservation> Collection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Inserta una observación de bomba en la colección de archivo.
|
||||
/// </summary>
|
||||
Task InsertAsync(PumpObservation obs);
|
||||
|
||||
/// <summary>
|
||||
/// Inserta múltiples observaciones en la colección de archivo.
|
||||
/// </summary>
|
||||
Task InsertManyAsync(IEnumerable<PumpObservation> observations);
|
||||
|
||||
/// <summary>
|
||||
/// Búsqueda por paciente para auditoría o restauración.
|
||||
/// </summary>
|
||||
Task<IEnumerable<PumpObservation>> FindByPatientIdAsync(ObjectId patientId,
|
||||
DateTime? from = null, DateTime? to = null, int? limit = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPumpObservationRepository
|
||||
{
|
||||
Task InsertAsync(PumpObservation obs);
|
||||
Task InsertManyAsync(IEnumerable<PumpObservation> observations);
|
||||
|
||||
Task<IEnumerable<PumpObservation>> FindByDeviceIdAsync(
|
||||
string deviceId,
|
||||
DateTime? from = null,
|
||||
DateTime? to = null,
|
||||
int? limit = null);
|
||||
|
||||
Task<PumpObservation?> FindLastByDeviceIdAsync(string deviceId);
|
||||
Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTimeAsync();
|
||||
|
||||
Task<IEnumerable<PumpObservation>> FindByPatientId(ObjectId? patientId);
|
||||
|
||||
Task<List<PumpObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num = 100);
|
||||
Task DeleteByPatientId(ObjectId? patientId);
|
||||
Task<long> DeleteOlderThanDaysAsync(int days, string? name = null);
|
||||
Task<long> DeleteKeepLastNAsync(int maxCount);
|
||||
|
||||
Task<long> UpdateManyObjectIdByFieldAsync(string fieldName, ObjectId newId, ObjectId? oldId);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IPumpStateRepository
|
||||
{
|
||||
Task<PumpState?> FindByDeviceIdAsync(string deviceId);
|
||||
|
||||
Task UpsertAsync(PumpState state);
|
||||
|
||||
Task<IEnumerable<PumpState>> GetAllAsync();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using adas_core.Domain.Models;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IRecordingAlertArchiveRepository : IMongoRepository<PatientRecordingAlert>
|
||||
{
|
||||
new Task InsertOneAsync(PatientRecordingAlert recordingAlert);
|
||||
Task DeleteBeforeDate(DateTime date);
|
||||
Task<long> InsertBatch(IEnumerable<PatientRecordingAlert> recordingAlerts);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using adas_core.Domain.Models;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IRecordingAlertRepository : IMongoRepository<PatientRecordingAlert>
|
||||
{
|
||||
Task<List<PatientRecordingAlert>> AggregatedPatientLastObservations(ObjectId patientId, int num);
|
||||
|
||||
Task<List<PatientRecordingAlert>> FindLastObservations(ObjectId patientId, string name, int num = 2);
|
||||
new Task InsertOneAsync(PatientRecordingAlert patientRecordingAlert);
|
||||
Task DeleteOlderDaysAsync(string name, int value);
|
||||
Task DeleteOlderNumberAsync(string name, int value);
|
||||
Task DeleteByPatientId(ObjectId patientId);
|
||||
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
|
||||
Task<IAsyncCursor<PatientRecordingAlert>> FindByPatientIdAsync(ObjectId patientId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IRelayRepository : IMongoRepository<Relay>
|
||||
{
|
||||
Task<Relay?> GetById(ObjectId relayId);
|
||||
List<Relay> GetRelayByTypeInList(List<ObjectId> configurationRelayList, RelayEnum.Type type);
|
||||
List<Relay> GetRelayInList(List<ObjectId> configurationRelayList);
|
||||
IFindFluent<Relay, Relay> GetPaginatedRelays(PaginationFilter filter);
|
||||
Task<Relay?> InsertOneRelayAsync(Relay request);
|
||||
Task<Relay?> UpdateRelayAsync(ObjectId objectId, Relay relay);
|
||||
Task<Relay?> GetByName(string? requestRelayName);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface ISectionRepository : IMongoRepository<Section>
|
||||
{
|
||||
Task<List<Section>> FindByLocation(PatientLocation location);
|
||||
Task<Section?> FindById(object id);
|
||||
Task<Section?> FindById(string id);
|
||||
Task<Section?> FindBySection(string section);
|
||||
Task<Section?> FindByPointOfCare(string pointOfCare);
|
||||
Task<List<Section>> GetAll();
|
||||
|
||||
Task<Section?> UpdateSection(Section section);
|
||||
//Task<Section?> UpdateSectionItems(Section section);
|
||||
//Task<Section?> UpdateSectionConfig(Section section);
|
||||
//Section UpdateSectionItems(string sectionId, string group, string boxName, BoxResponse boxUpdated);
|
||||
|
||||
Task<Section?> InsertOneSection(Section section);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IServiceConfigRepository : IMongoRepository<ServiceConfig>
|
||||
{
|
||||
Task<ServiceConfig?> FindById(ObjectId oid);
|
||||
Task<ServiceConfig?> FindById(string id);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using adas_core.Domain.Models;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface ITreatmentArchiveRepository
|
||||
{
|
||||
Task InsertOneAsync(PatientTreatment patientTreatment);
|
||||
Task DeleteBeforeDate(DateTime date);
|
||||
Task<long> InsertBatch(IEnumerable<PatientTreatment> treatments);
|
||||
|
||||
Task<List<PatientTreatment>> FindAllFromPatient(ObjectId patientId);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface ITreatmentRepository : IMongoRepository<PatientTreatment>
|
||||
{
|
||||
Task<IEnumerable<PatientTreatment>> GetByPatientId(ObjectId patientId);
|
||||
Task<IAsyncCursor<PatientTreatment>> GetById(ObjectId id);
|
||||
new Task InsertOneAsync(PatientTreatment treatment);
|
||||
new Task DeleteAsync(ObjectId id);
|
||||
Task<bool> Update(PatientTreatment treatment);
|
||||
Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId);
|
||||
Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId);
|
||||
|
||||
Task<bool> DeleteByPatientId(ObjectId patientId);
|
||||
Task<List<PatientTreatment>> FindBolusTreatments(ObjectId patientId);
|
||||
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
|
||||
IFindFluent<PatientTreatment, PatientTreatment> GetPaginatedTreatments(PaginationFilter filter);
|
||||
Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IUnitRepository : IMongoRepository<Unit>
|
||||
{
|
||||
//Task<Unit?> FindByLocation(PatientLocation location);
|
||||
Task<Unit?> FindById(object id);
|
||||
|
||||
Task<Unit?> FindByName(string section);
|
||||
|
||||
//Task<Unit?> FindByPointOfCare(PointOfCare pointOfCare);
|
||||
Task<List<Unit>> GetAll();
|
||||
Task<Unit?> UpdateUnit(Unit section);
|
||||
Task<Unit?> InsertOneUnit(Unit unit);
|
||||
Task<IEnumerable<Unit>> FindByMasterListId(ObjectId id, MasterListType masterListType);
|
||||
Task<IEnumerable<Unit>> FindByMasterListId(ObjectId id);
|
||||
Task<Unit?> UpdateUnitMasterList(UpdateUnitIdListDto updateUnitListDto);
|
||||
Task<Unit?> UpdateUnitInfo(ObjectId unitId, string name, string title);
|
||||
Task<bool> UpdateConfiguration(ObjectId unitIdParsed, UnitConfiguration unitConfiguration);
|
||||
IFindFluent<Unit, Unit> GetPaginatedUnits(PaginationFilter filter);
|
||||
Task<long> CountUnitsByMasterListId(ObjectId id, MasterListType masterListType);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Repositories.Interfaces;
|
||||
|
||||
public interface IUserRepository : IMongoRepository<User>
|
||||
{
|
||||
Task<User?> GetUser(string username, string password);
|
||||
Task<User?> GetById(ObjectId id);
|
||||
Task<User?> GetByUserName(string name);
|
||||
Task<User?> GetByName(string name);
|
||||
Task<User?> GetByUserAndAuthoritesName(string name);
|
||||
Task<User?> UpdateUser(User user, bool updatePass);
|
||||
IFindFluent<User, User> GetPaginatedUsers(PaginationFilter filter);
|
||||
Task<User> GetOrCreateSystemUser();
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
using adas_core.Application.Exceptions;
|
||||
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.DTO;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
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 AdminPanelService(
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
IPatientService patientService,
|
||||
IConfigObservationService configObservationService,
|
||||
IMedicineService medicineService,
|
||||
IPointOfCareService pocService,
|
||||
IUnitService unitService,
|
||||
ILogger<AdminPanelService> logger,
|
||||
IAdmissionService admissionService,
|
||||
IAuthService authService,
|
||||
IDischargeService dischargeService,
|
||||
IDisplayService displayService)
|
||||
: IAdminPanelService
|
||||
{
|
||||
private readonly List<string> _defaultIdRecord = apiSettings.Value.DefaultIdRecord ?? [];
|
||||
|
||||
|
||||
#region Patient
|
||||
|
||||
public async Task<bool> ArchivePatient(Patient patient)
|
||||
{
|
||||
try
|
||||
{
|
||||
await patientService.ArchivePatient(patient);
|
||||
|
||||
logger.LogDebug("archived patientid {patientid} from ADMPanel", patient.Id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Patient?> CreatePatient(AdmPanelRequest admRequest)
|
||||
{
|
||||
var patient = new Patient
|
||||
{
|
||||
Id = ObjectId.GenerateNewId()
|
||||
};
|
||||
await UpdateNewPatient(patient, admRequest);
|
||||
await patientService.Insert(patient);
|
||||
|
||||
logger.LogDebug("Inserted {patientid} from ADMPanel", patient.Id);
|
||||
return patient;
|
||||
}
|
||||
|
||||
private async Task UpdateNewPatient(Patient patient, AdmPanelRequest admRequest)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(admRequest.PatientNumber)) patient.PatientNumber = admRequest.PatientNumber;
|
||||
if (admRequest.AdmTime.HasValue) patient.AdmTime = admRequest.AdmTime;
|
||||
if (admRequest.Patient != null && !admRequest.Patient.IsEmptyDontCheckIds())
|
||||
patient.Person = admRequest.Patient;
|
||||
if (admRequest.Patient != null && (admRequest.Patient.Ids == null || admRequest.Patient.Ids.Count == 0))
|
||||
{
|
||||
if (patient.Person is { Ids: null }) patient.Person.Ids = new Dictionary<string, string>();
|
||||
foreach (var item in _defaultIdRecord) patient.Person?.Ids?.Add(item, admRequest?.PatientNumber ?? "null");
|
||||
}
|
||||
|
||||
patient.Person?.SetIds(patient.Person?.Ids ?? new Dictionary<string, string>());
|
||||
|
||||
if (admRequest is { UnitId: not null })
|
||||
{
|
||||
var unit = await unitService.FindById(admRequest.UnitId);
|
||||
patient.UnitId = unit?.Id ?? admRequest.UnitId;
|
||||
patient.UnitString = unit?.Name;
|
||||
|
||||
|
||||
if (admRequest is { PointOfCareId: not null })
|
||||
{
|
||||
//El paciente existe en la localizacion no te dejo insertarlo
|
||||
var patientInLocation = await patientService.FindByPointOfCareId(admRequest.PointOfCareId.Value);
|
||||
if (patientInLocation != null)
|
||||
{
|
||||
logger.LogError(
|
||||
"trying to insert patientid {patientid} in to location already in use. PointOfcareId: {poc}",
|
||||
patient.Id, patient.PointOfCareId);
|
||||
throw new Exception($"patient already in location: {patient.Location}");
|
||||
}
|
||||
|
||||
var poc = await pocService.FindById(admRequest.PointOfCareId.Value);
|
||||
if (poc == null)
|
||||
{
|
||||
logger.LogError(
|
||||
"trying to insert patientid {patientid} in to location not found. PointOfcareId: {poc}",
|
||||
patient.Id, admRequest.PointOfCareId);
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
await pocService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.InUse);
|
||||
|
||||
patient.Location = new PatientLocation
|
||||
(
|
||||
bed: poc.Bed,
|
||||
room: poc.Room,
|
||||
unitName: unit?.Name
|
||||
);
|
||||
patient.PointOfCareId = poc.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pocUnknown =
|
||||
await pocService.FindByBedAndUnitId(VirtualPointOfCare.Unknown.ToString(), patient.UnitId);
|
||||
|
||||
patient.PointOfCareId = pocUnknown?.Id;
|
||||
}
|
||||
|
||||
patient.UpdateDate = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Patient?> FindPatientById(ObjectId id)
|
||||
{
|
||||
return await patientService.FindById(id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public async Task<Patient?> FindPatientByLocation(PatientLocation location)
|
||||
{
|
||||
return await patientService.FindByLocation(location);
|
||||
}
|
||||
|
||||
public async Task<Patient?> FindPatientByPatientNumber(string patientNumber)
|
||||
{
|
||||
return await patientService.FindByPatientNumber(patientNumber);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePatientData(AdmPanelRequest request, Patient oldPatient)
|
||||
{
|
||||
//traer el paciente que se quiere actualizar con los valores que tenga en la base de datos a una variable
|
||||
//actualizar unicamente los campos que tengan que ver con los datos del paciente
|
||||
if (request.PatientNumber == null || request.Patient == null || request.Patient.IsEmptyDontCheckIds())
|
||||
{
|
||||
logger.LogError("Patient Data not updated. Old Patient:{oldPatient}. Api Request {request}", oldPatient,
|
||||
request);
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
}
|
||||
|
||||
var patientNumberChanged = oldPatient.PatientNumber != request.PatientNumber;
|
||||
await patientService.UpdatePatientData(oldPatient.Id, request.PatientNumber, request.Patient,
|
||||
patientNumberChanged);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePatientLocation(AdmPanelRequest request)
|
||||
{
|
||||
var patient = await patientService.FindByLocation(request.OldLocation);
|
||||
|
||||
var patientExistsInLocation = await patientService.FindByLocation(request.Location);
|
||||
|
||||
//Si ya hay un paciente en esa localización
|
||||
if (patientExistsInLocation != null)
|
||||
//Si hay un paciente distinto en la nueva localización movemos al anterior
|
||||
if (patient != null && patientExistsInLocation.PatientNumber != patient.PatientNumber)
|
||||
await patientService.UpdateLocation(patientExistsInLocation.Id,
|
||||
new PatientLocation(VirtualPointOfCare.Pushed.ToString(), ObjectId.GenerateNewId().ToString()));
|
||||
|
||||
if (patient != null)
|
||||
{
|
||||
if (request.Location?.Bed == null || string.IsNullOrEmpty(request.Location.Bed))
|
||||
request.Location = new PatientLocation(VirtualPointOfCare.Pushed.ToString(),
|
||||
ObjectId.GenerateNewId().ToString());
|
||||
|
||||
|
||||
//to solve problems when empty beds with not scapped ""
|
||||
request.Location.Bed = request.Location?.Bed?.Replace("\"", "");
|
||||
|
||||
await patientService.UpdateLocation(patient.Id, request.Location);
|
||||
patient = await patientService.FindById(patient.Id);
|
||||
if (patient != null) await patientService.Update(patient);
|
||||
}
|
||||
else
|
||||
{
|
||||
patient = new Patient
|
||||
{
|
||||
Id = ObjectId.GenerateNewId()
|
||||
};
|
||||
await patientService.Insert(patient);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Patient?> FindPatient(AdmPanelRequest request)
|
||||
{
|
||||
var findByLocation = !request.Location?.IsFullEmpty();
|
||||
|
||||
return await patientService.FindPatient(request.PatientId, request.PatientNumber, request.Location,
|
||||
findByLocation ?? false) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public async Task<Patient?> FindByLocation(PatientLocation location)
|
||||
{
|
||||
return await patientService.FindByLocation(location) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ConfigObservations
|
||||
|
||||
public async Task<bool> CreateConfig(ConfigObservation configObservation)
|
||||
{
|
||||
_ = await configObservationService.CreateConfig(configObservation) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateConfig(ConfigObservation configObservation)
|
||||
{
|
||||
_ = await configObservationService.UpdateConfig(configObservation) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteConfigObservationItem(ObjectId id)
|
||||
{
|
||||
_ = await configObservationService.RemoveConfigItem(id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unit
|
||||
|
||||
public async Task<Unit?> InsertUnit(Unit unit)
|
||||
{
|
||||
//Insertamos la unidad y creamos los PoCs por defecto para esa unidad
|
||||
var result = await unitService.InsertOne(unit);
|
||||
if (result != null)
|
||||
{
|
||||
// Por cada valor del enum VirtualPointOfCare, creamos un PointOfCare asociado a la unidad
|
||||
foreach (var pocEnum in Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>())
|
||||
{
|
||||
var poc = new PointOfCare
|
||||
{
|
||||
UnitId = result.Id,
|
||||
Status = StatusEnum.PointOfCare.Available,
|
||||
Room = pocEnum.ToString(),
|
||||
Bed = pocEnum.ToString()
|
||||
};
|
||||
var insertedPoc = await pocService.InsertPointOfCare(poc);
|
||||
if (insertedPoc != null)
|
||||
{
|
||||
result.PointOfCareIds ??= [];
|
||||
result.PointOfCareIds.Add(insertedPoc.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizamos la unidad con los nuevos PointOfCareIds
|
||||
await unitService.UpdateUnit(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public async Task<UnitInfoDto?> GetUnitDependencyDto(Unit unit)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new UnitInfoDto(unit)
|
||||
{
|
||||
Admissions = await admissionService.CountAdmissionsByUnitId(unit.Id),
|
||||
Discharges = await dischargeService.CountDischargesByUnitId(unit.Id),
|
||||
Displays = await displayService.CountDisplaysByUnitId(unit.Id),
|
||||
Patients = await patientService.CountPatientsByUnitId(unit.Id),
|
||||
PointOfCares = await pocService.CountPoCsByUnitId(unit.Id),
|
||||
VirtualPointOfCares = await pocService.CountVirtualPoCsByUnitId(unit.Id)
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteUnitById(ObjectId unitId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var unit = await unitService.FindById(unitId);
|
||||
|
||||
if (unit == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
|
||||
//No se puede borrar una unidad que est� en uso
|
||||
var patients = await patientService.CountPatientsByUnitId(unitId);
|
||||
if (patients > 0)
|
||||
//Si tiene pacientes no se permite borrado
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictResourceInUse);
|
||||
|
||||
//Borramos admisiones
|
||||
await admissionService.DeleteAdmissionsByUnitId(unitId);
|
||||
//discharges
|
||||
await dischargeService.DeleteDischargesByUnitId(unitId);
|
||||
//Borramos Authorizations
|
||||
await authService.DeleteByUnitId(unitId);
|
||||
|
||||
//Displays
|
||||
await displayService.DeleteDisplaysByUnitId(unitId);
|
||||
|
||||
//PointOfCares
|
||||
await pocService.DeletePoCsByUnitId(unitId);
|
||||
|
||||
|
||||
//Una vez borrados los recursos asociados a la unidad, borramos la unidad
|
||||
await unitService.DeleteUnitById(unit);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Medicine
|
||||
|
||||
public async Task<Medicine?> GetMedicineById(ObjectId medicineId)
|
||||
{
|
||||
return await medicineService.GetMedicineById(medicineId) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public async Task<Medicine?> PostMedicine(Medicine medicine)
|
||||
{
|
||||
var newMedicine = await medicineService.PostMedicine(medicine) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
return newMedicine;
|
||||
}
|
||||
|
||||
public async Task<Medicine?> UpdateMedicine(Medicine medicine)
|
||||
{
|
||||
var updatedMedicine = await medicineService.UpdateMedicine(medicine) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
return updatedMedicine;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteMedicineById(string medicineId)
|
||||
{
|
||||
if (!ObjectId.TryParse(medicineId, out var objectId))
|
||||
throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
|
||||
await medicineService.DeleteMedicineById(objectId);
|
||||
_ = await medicineService.GetMedicineById(ObjectId.Parse(medicineId)) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,744 @@
|
||||
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.DTO;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class AdmissionService(
|
||||
ILogger<AdmissionService> logger,
|
||||
ISubscribersService subscribersService,
|
||||
IAdmissionRepository admissionRepository,
|
||||
IClientMessageService clientMessageService,
|
||||
IUnitService unitService,
|
||||
IPatientService patientService,
|
||||
IPointOfCareService pointOfCareService,
|
||||
IDisplayService displayService,
|
||||
IDischargeService dischargeService,
|
||||
IPatientArchiveRepository patientArchiveRepository,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
IMasterListServiceFactory masterListServiceFactory)
|
||||
: IAdmissionService
|
||||
{
|
||||
// Auditory logs
|
||||
|
||||
|
||||
public async Task DeleteAdmissionAsync(Admission admission)
|
||||
{
|
||||
await DeleteAdmissionByIdAsync(admission.Id);
|
||||
}
|
||||
|
||||
public async Task DeleteAdmissionByIdAsync(ObjectId admissionId)
|
||||
{
|
||||
var admissionAux = await admissionRepository.FindById(admissionId);
|
||||
if (admissionAux == null)
|
||||
{
|
||||
logger.LogInformation("Error deleting Admission not found, id: {AdmissionId} ", admissionId);
|
||||
return;
|
||||
}
|
||||
|
||||
await admissionRepository.Delete(admissionId);
|
||||
|
||||
if (admissionAux.PointOfCareId.HasValue)
|
||||
{
|
||||
var poc = await pointOfCareService.GetInfo(admissionAux.PointOfCareId.Value);
|
||||
if (poc != null && poc.AdmissionId == admissionId)
|
||||
{
|
||||
poc.AdmissionId = null;
|
||||
poc.Admission = null;
|
||||
await pointOfCareService.Update(poc);
|
||||
|
||||
|
||||
if (poc.Status != StatusEnum.PointOfCare.Locked && poc.Status != StatusEnum.PointOfCare.InUse)
|
||||
await pointOfCareService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.Available);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogInformation("Admission id: {AdmissionId} DELETED ", admissionId);
|
||||
|
||||
SendAdmissionBroadcast(admissionAux, OperationType.DeleteAdmission);
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, admissionAux, null);
|
||||
}
|
||||
|
||||
public async Task DeleteAdmissionsByUnitId(ObjectId unitId)
|
||||
{
|
||||
_ = await admissionRepository.DeleteAdmissionsByUnitId(unitId);
|
||||
}
|
||||
|
||||
|
||||
public async Task<Admission?> GetAdmissionByIdAsync(ObjectId admissionId)
|
||||
{
|
||||
var result = await admissionRepository.FindById(admissionId);
|
||||
if (result?.PointOfCareId != null)
|
||||
{
|
||||
var poc = await pointOfCareService.GetInfo(result.PointOfCareId.Value, null,false);
|
||||
result.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Admission>> GetAdmissionsAsync()
|
||||
{
|
||||
var resultList = await admissionRepository.FindAll() ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var admissionsAsync = resultList.ToList();
|
||||
foreach (var admission in admissionsAsync)
|
||||
if (admission.PointOfCareId != null)
|
||||
{
|
||||
var poc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value, null,false);
|
||||
admission.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
|
||||
}
|
||||
|
||||
return admissionsAsync;
|
||||
}
|
||||
|
||||
public async Task<Admission?> InsertAdmission(Admission admission)
|
||||
{
|
||||
var admissionAux = await admissionRepository.FindByNhc(admission.Nhc);
|
||||
if (admissionAux != null) throw new ConflictException(HttpEnum.ErrorMessage.BadRequestDuplicateData);
|
||||
PointOfCare? pointOfCare = null;
|
||||
//Bloqueamos el pointOfCare en el caso de que lo tenga asignado
|
||||
if (admission.PointOfCareId.HasValue)
|
||||
{
|
||||
pointOfCare = await pointOfCareService.GetInfo(admission.PointOfCareId.Value) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
|
||||
//pointOfCare.AdmissionId = admission.Id;
|
||||
//pointOfCare.Admission = admission;
|
||||
admission.PatientLocation = new PatientLocation(pointOfCare.UnitName, pointOfCare.Bed, pointOfCare.Room);
|
||||
}
|
||||
|
||||
var insertedAdmission = await admissionRepository.InsertOneAsyncAndReturn(admission) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
|
||||
if (pointOfCare is { AdmissionId: null })
|
||||
{
|
||||
pointOfCare.Admission = insertedAdmission;
|
||||
pointOfCare.AdmissionId = insertedAdmission.Id;
|
||||
if (pointOfCare.Status == StatusEnum.PointOfCare.Available)
|
||||
pointOfCare.Status = StatusEnum.PointOfCare.Reserved;
|
||||
|
||||
await pointOfCareService.Update(pointOfCare);
|
||||
}
|
||||
|
||||
SendAdmissionBroadcast(insertedAdmission, OperationType.NewAdmission);
|
||||
// Obtener información el usuario autenticado
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, insertedAdmission);
|
||||
|
||||
return insertedAdmission;
|
||||
}
|
||||
|
||||
public async Task UpdateAdmissionAsync(Admission admission)
|
||||
{
|
||||
var oldAdmission = await admissionRepository.FindById(admission.Id);
|
||||
if (oldAdmission == null) return;
|
||||
if (oldAdmission.PointOfCareId.HasValue)
|
||||
{
|
||||
var pocOld = await pointOfCareService.GetInfo(oldAdmission.PointOfCareId.Value, null,false);
|
||||
if (pocOld != null)
|
||||
oldAdmission.PatientLocation = new PatientLocation(pocOld.UnitName, pocOld.Bed, pocOld.Room);
|
||||
}
|
||||
|
||||
if (admission.PointOfCareId.HasValue)
|
||||
{
|
||||
var poc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value, null,false);
|
||||
if (poc != null) admission.PatientLocation = new PatientLocation(poc.UnitName, poc.Bed, poc.Room);
|
||||
}
|
||||
|
||||
await admissionRepository.Update(admission);
|
||||
|
||||
await HandlePointOfCareChange(admission, oldAdmission);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission, admission);
|
||||
SendAdmissionBroadcast(admission, OperationType.UpdateAdmission);
|
||||
}
|
||||
|
||||
public async Task AdmitPatient(Admission admission, bool isNew = false)
|
||||
{
|
||||
if (admission.PointOfCareId == null)
|
||||
{
|
||||
logger.LogError("PointOfCare is required. Admission: {Admission}", admission);
|
||||
return;
|
||||
}
|
||||
|
||||
var unit = await unitService.FindById(admission.UnitId);
|
||||
if (unit == null)
|
||||
{
|
||||
logger.LogError("Unit {Name} not found. Unit Id: ", admission.UnitId);
|
||||
return;
|
||||
}
|
||||
|
||||
var pointOfCare = await pointOfCareService.FindById(admission.PointOfCareId.Value);
|
||||
if (pointOfCare == null)
|
||||
{
|
||||
logger.LogError("Point of Care not found. Patient not created. {Admission}", admission);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Patient patient = new()
|
||||
{
|
||||
PointOfCareId = pointOfCare.Id,
|
||||
UnitId = pointOfCare.UnitId,
|
||||
PointOfCare = pointOfCare,
|
||||
UnitString = pointOfCare.UnitName,
|
||||
Bed = pointOfCare.Bed,
|
||||
Room = pointOfCare.Room,
|
||||
PatientNumber = admission.Nhc,
|
||||
AdmTime = DateTime.UtcNow,
|
||||
Person = admission.Person,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
DischargeStatus = pointOfCare.Unit?.DischargeStatusList?.Options.FirstOrDefault(),
|
||||
Origin = admission.Origin,
|
||||
OriginAux = admission.OriginAux,
|
||||
Diagnosis = admission.Diagnosis,
|
||||
DiagnosisAux = admission.DiagnosisAux,
|
||||
Allergies = admission.Allergies,
|
||||
Insulation = admission.Insulation,
|
||||
LanguageBarrier = admission.LanguageBarrier,
|
||||
PassiveSitting = admission.PassiveSitting,
|
||||
Altable = new OptionList
|
||||
{
|
||||
Name = "NotAltable",
|
||||
IconDefault = "icNotAltable",
|
||||
OptionType = "NotAltable"
|
||||
},
|
||||
Visits = pointOfCare.Unit?.VisitOptionList?.Options.FirstOrDefault(),
|
||||
AccessControl = pointOfCare.Unit?.AccessControlList?.Options.FirstOrDefault(),
|
||||
Location = new PatientLocation
|
||||
(
|
||||
unit.Name,
|
||||
pointOfCare.Bed,
|
||||
pointOfCare.Room
|
||||
)
|
||||
};
|
||||
if (unit.AltableOptionListId is not null)
|
||||
{
|
||||
var list = await masterListServiceFactory.GetMasterListById(MasterListType.AltableOptionList,
|
||||
unit.AltableOptionListId.Value, LocaleEnum.Default) as AltableOptionList;
|
||||
var listOpt = list?.Options.FirstOrDefault(c => c.OptionType == "NotAltable");
|
||||
if (listOpt != null)
|
||||
patient.Altable = listOpt;
|
||||
}
|
||||
|
||||
await patientService.Insert(patient);
|
||||
await SetPointOfCareStatus(pointOfCare.Id, StatusEnum.PointOfCare.InUse);
|
||||
|
||||
if (!isNew)
|
||||
await DeleteAdmissionAsync(admission);
|
||||
|
||||
if (admission.Insulation != null)
|
||||
await patientService.UpdatePatientMasterList(
|
||||
patient.Id,
|
||||
MasterListType.InsulationList,
|
||||
[admission.Insulation],
|
||||
null, null);
|
||||
|
||||
if (admission.Allergies != null)
|
||||
await patientService.UpdatePatientMasterList(
|
||||
patient.Id,
|
||||
MasterListType.AllergyList,
|
||||
admission.Allergies,
|
||||
null, null);
|
||||
if (admission.Diagnosis != null)
|
||||
await patientService.UpdatePatientMasterList(
|
||||
patient.Id,
|
||||
MasterListType.DiagnosisList,
|
||||
[admission.Diagnosis],
|
||||
null, null);
|
||||
if (admission.Origin != null)
|
||||
await patientService.UpdatePatientMasterList(
|
||||
patient.Id,
|
||||
MasterListType.OriginList,
|
||||
[admission.Origin],
|
||||
null, null);
|
||||
if (admission.LanguageBarrier != null)
|
||||
await patientService.UpdatePatientMasterList(
|
||||
patient.Id,
|
||||
MasterListType.LanguageBarrierList,
|
||||
admission.LanguageBarrier,
|
||||
null, null);
|
||||
if (admission.PassiveSitting != null)
|
||||
await patientService.UpdatePatientMasterList(
|
||||
patient.Id,
|
||||
MasterListType.PassiveSittingList,
|
||||
[admission.PassiveSitting],
|
||||
null, null);
|
||||
}
|
||||
|
||||
public async Task ReturnPatientToAdmissions(ObjectId patientId)
|
||||
{
|
||||
var patient = await patientService.FindById(patientId);
|
||||
if (patient == null)
|
||||
{
|
||||
logger.LogError("Error returning the patient Id: {Id} to admission", patientId);
|
||||
return;
|
||||
}
|
||||
|
||||
var unit = await unitService.FindById(patient.UnitId);
|
||||
if (unit == null)
|
||||
{
|
||||
logger.LogError("Unit not found by Id. {Name}", patient.UnitId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (patient.PointOfCareId != null)
|
||||
{
|
||||
var poc = await pointOfCareService.FindById(patient.PointOfCareId.Value);
|
||||
Admission admission = new()
|
||||
{
|
||||
Nhc = patient.PatientNumber ?? string.Empty,
|
||||
PointOfCareId = poc?.Id,
|
||||
UnitId = unit.Id,
|
||||
Person = patient.Person ??
|
||||
new Person(), //No debería ser null en este punto, pero así quito el warning
|
||||
Origin = patient.Origin,
|
||||
OriginAux = patient.OriginAux,
|
||||
Diagnosis = patient.Diagnosis,
|
||||
PassiveSitting = patient.PassiveSitting,
|
||||
DiagnosisAux = patient.DiagnosisAux,
|
||||
Allergies = patient.Allergies,
|
||||
Insulation = patient.Insulation,
|
||||
LanguageBarrier = patient.LanguageBarrier,
|
||||
AdmissionDate = patient.AdmTime ?? DateTime.UtcNow,
|
||||
PatientLocation = patient.Location
|
||||
};
|
||||
|
||||
await InsertAdmission(admission);
|
||||
}
|
||||
|
||||
var dis = await dischargeService.GetDischargeByPatientId(patient.Id);
|
||||
if (dis != null) await dischargeService.DeleteDischargeByIdAsync(dis.Id);
|
||||
await patientService.ArchivePatient(patient);
|
||||
|
||||
pointOfCareService.CheckNextAdmission(patient.PointOfCareId);
|
||||
}
|
||||
|
||||
// Used for temporal beds like PUSHED
|
||||
public async Task ReturnPatientToAdmissions(ObjectId patientId, Admission adm)
|
||||
{
|
||||
var patient = await patientService.FindById(patientId);
|
||||
if (patient == null)
|
||||
{
|
||||
logger.LogError("Error returning the patient Id: {Id} to admission", patientId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var unit = await unitService.FindById(adm.UnitId);
|
||||
if (unit == null)
|
||||
{
|
||||
logger.LogError("Unit not found by Id. {Name}", patient.UnitId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (adm.PointOfCareId != null)
|
||||
{
|
||||
var poc = await pointOfCareService.FindById(adm.PointOfCareId.Value);
|
||||
Admission admission = new()
|
||||
{
|
||||
Nhc = patient.PatientNumber ?? string.Empty,
|
||||
PointOfCareId = poc?.Id,
|
||||
UnitId = unit.Id,
|
||||
Person = patient.Person ??
|
||||
new Person(), //No debería ser null en este punto, pero así quito el warning
|
||||
Origin = patient.Origin,
|
||||
OriginAux = patient.OriginAux,
|
||||
Diagnosis = patient.Diagnosis,
|
||||
DiagnosisAux = patient.DiagnosisAux,
|
||||
Allergies = patient.Allergies,
|
||||
PassiveSitting = patient.PassiveSitting,
|
||||
Insulation = patient.Insulation,
|
||||
LanguageBarrier = patient.LanguageBarrier,
|
||||
AdmissionDate = patient.AdmTime ?? DateTime.UtcNow,
|
||||
PatientLocation = patient.Location
|
||||
};
|
||||
|
||||
await InsertAdmission(admission);
|
||||
}
|
||||
|
||||
var dis = await dischargeService.GetDischargeByPatientId(patient.Id);
|
||||
if (dis != null) await dischargeService.DeleteDischargeByIdAsync(dis.Id);
|
||||
await patientService.ArchivePatient(patient);
|
||||
|
||||
pointOfCareService.CheckNextAdmission(patient.PointOfCareId);
|
||||
}
|
||||
|
||||
public async Task<List<Admission>> GetAdmissionByLocation(PatientLocation location)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await admissionRepository.FindByLocation(location);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error getting admission by patient location {Location} exception: {Ex}", location,
|
||||
e.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Admission>> GetAdmissionByPointOfCareId(ObjectId pocId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await admissionRepository.FindByPointOfCareId(pocId);
|
||||
foreach (var admission in result)
|
||||
{
|
||||
var poc = await pointOfCareService.GetInfo(pocId, null,false);
|
||||
if (poc != null) admission.PatientLocation = new PatientLocation(poc.UnitName, poc.Bed, poc.Room);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error getting admission by patient PocId {PocId} exception: {Ex}", pocId, e.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Admission>> GetAdmissionByPointOfCareIdAndLocale(ObjectId pocId, LocaleEnum locale)
|
||||
{
|
||||
var admissions = await GetAdmissionByPointOfCareId(pocId);
|
||||
|
||||
// Ejecutar todas las traducciones en paralelo
|
||||
var translatedAdmissions = await Task.WhenAll(
|
||||
admissions.Select(adm => GetAdmissionWithLocale(adm, locale))
|
||||
);
|
||||
|
||||
return translatedAdmissions.ToList();
|
||||
}
|
||||
|
||||
public async Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await admissionRepository.GetAdmissionByUnitIdWithOutPoC(unitId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error getting admission by unitId with out PoCId {UnitId} exception: {Ex}", unitId,
|
||||
e.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> CountAdmissionsByUnitId(ObjectId unitId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await admissionRepository.CountByUnitId(unitId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error getting admission by unitId with out PoCId {UnitId} exception: {Ex}", unitId,
|
||||
e.Message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PatientSearch?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
|
||||
{
|
||||
var patient = await patientService.FindByPatientNumber(patientNumber);
|
||||
if (patient != null)
|
||||
{
|
||||
if (patient.PointOfCareId.HasValue)
|
||||
patient.PointOfCare = await pointOfCareService.FindById(patient.PointOfCareId.Value);
|
||||
if (patient.UnitId.HasValue)
|
||||
{
|
||||
var uni = await unitService.FindById(patient.UnitId.Value);
|
||||
if (uni != null)
|
||||
patient.UnitString = uni.Name;
|
||||
}
|
||||
}
|
||||
|
||||
var archivePatient =
|
||||
await patientArchiveRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId);
|
||||
var admission = await admissionRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId);
|
||||
|
||||
var result = new PatientSearch(patient, archivePatient, admission,
|
||||
patient == null && archivePatient != null,
|
||||
patient != null || archivePatient != null || admission != null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task<Admission?> GetAdmissionByPatientNumber(string patientNumber)
|
||||
{
|
||||
return admissionRepository.FindByNhc(patientNumber);
|
||||
}
|
||||
|
||||
public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
|
||||
string typeName)
|
||||
{
|
||||
var unitIds = unitList.Select(x => x.Id).ToList();
|
||||
var oldAdmissionList = await admissionRepository.FindByUnitIds(unitIds);
|
||||
var admissionUpdatedList = await admissionRepository.UpdateMasterListOption(unitIds, opt, typeName);
|
||||
foreach (var admission in admissionUpdatedList)
|
||||
{
|
||||
var oldAdmission = oldAdmissionList.Find(adm => adm.Id == admission.Id);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission!, admission);
|
||||
SendAdmissionBroadcast(admission, OperationType.UpdateAdmission);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName)
|
||||
{
|
||||
var unitIds = unitList.Select(x => x.Id).ToList();
|
||||
var oldAdmissionList = await admissionRepository.FindByUnitIds(unitIds);
|
||||
var admissionUpdatedList = await admissionRepository.DeleteMasterListOption(unitIds, opt, typeName);
|
||||
foreach (var admission in admissionUpdatedList)
|
||||
{
|
||||
var admissionUpdated = await GetAdmissionByIdAsync(admission.Id);
|
||||
var oldAdmission = oldAdmissionList.Find(adm => adm.Id == admission.Id);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission!,
|
||||
admissionUpdated);
|
||||
if (admissionUpdated != null)
|
||||
SendAdmissionBroadcast(admissionUpdated, OperationType.UpdateAdmission);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveRequest(ApiRequest apiRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (apiRequest.Admission == null)
|
||||
return;
|
||||
|
||||
switch (apiRequest.Type)
|
||||
{
|
||||
case "NewAdmission":
|
||||
{
|
||||
if (string.IsNullOrEmpty(apiRequest.Admission.Nhc) ||
|
||||
apiRequest.Admission.Origin == null ||
|
||||
apiRequest.Admission.Diagnosis == null)
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Error saving admission api request. Some values are required. Admission: {Admission}",
|
||||
apiRequest.Admission);
|
||||
return;
|
||||
}
|
||||
|
||||
await InsertAdmission(apiRequest.Admission);
|
||||
break;
|
||||
}
|
||||
case "UpdateAdmission":
|
||||
{
|
||||
if (string.IsNullOrEmpty(apiRequest.Admission.Nhc) ||
|
||||
apiRequest.Admission.Origin == null ||
|
||||
apiRequest.Admission.Diagnosis == null)
|
||||
{
|
||||
logger.LogDebug(
|
||||
"Error updating admission api request. Some values are required. Admission: {Admission}",
|
||||
apiRequest.Admission);
|
||||
return;
|
||||
}
|
||||
|
||||
await UpdateAdmissionAsync(apiRequest.Admission);
|
||||
break;
|
||||
}
|
||||
case "DeleteAdmission":
|
||||
{
|
||||
await DeleteAdmissionAsync(apiRequest.Admission);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("Exception updating admission {Admission} . Exception: {Ex}", apiRequest.Admission,
|
||||
ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task SaveRequestAsync(ApiRequest apiRequest)
|
||||
{
|
||||
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
|
||||
}
|
||||
|
||||
private async Task HandlePointOfCareChange(Admission admission, Admission oldAdmission)
|
||||
{
|
||||
// Check if PointOfCare has changed.
|
||||
if (admission.PointOfCareId == oldAdmission.PointOfCareId) return;
|
||||
|
||||
// Disable new PointOfCare if it exists.
|
||||
if (admission.PointOfCareId.HasValue)
|
||||
{
|
||||
var newPoc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value);
|
||||
if (newPoc == null) return;
|
||||
|
||||
var patientOnNewPoc = await patientService.FindByPointOfCareId(newPoc.Id);
|
||||
if (newPoc.AdmissionId == null && newPoc.Status != StatusEnum.PointOfCare.Locked)
|
||||
{
|
||||
newPoc.Admission = admission;
|
||||
newPoc.AdmissionId = admission.Id;
|
||||
newPoc.Status = patientOnNewPoc != null
|
||||
? StatusEnum.PointOfCare.InUse
|
||||
: StatusEnum.PointOfCare.Reserved;
|
||||
|
||||
await pointOfCareService.Update(newPoc);
|
||||
}
|
||||
|
||||
if (newPoc.Status == StatusEnum.PointOfCare.Available)
|
||||
pointOfCareService.CheckNextAdmission(newPoc.Id);
|
||||
}
|
||||
|
||||
// Enable previous PointOfCare if it exists.
|
||||
if (oldAdmission.PointOfCareId.HasValue)
|
||||
{
|
||||
var oldPoc = await pointOfCareService.GetInfo(oldAdmission.PointOfCareId.Value);
|
||||
if (oldPoc != null)
|
||||
{
|
||||
var patientOnOldPoc = await patientService.FindByPointOfCareId(oldPoc.Id);
|
||||
if (oldPoc.AdmissionId == oldAdmission.Id && oldPoc.Status != StatusEnum.PointOfCare.Locked)
|
||||
{
|
||||
oldPoc.Admission = null;
|
||||
oldPoc.AdmissionId = null;
|
||||
oldPoc.Status = patientOnOldPoc != null
|
||||
? StatusEnum.PointOfCare.InUse
|
||||
: StatusEnum.PointOfCare.Available;
|
||||
|
||||
await pointOfCareService.Update(oldPoc);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldPoc is { Status: StatusEnum.PointOfCare.Available })
|
||||
pointOfCareService.CheckNextAdmission(oldPoc.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetPointOfCareStatus(ObjectId pointOfCareId, StatusEnum.PointOfCare status)
|
||||
{
|
||||
var pointOfCare = await pointOfCareService.FindById(pointOfCareId);
|
||||
if (pointOfCare == null) return;
|
||||
|
||||
await pointOfCareService.SetPointOfCareStatus(pointOfCareId, status);
|
||||
}
|
||||
|
||||
private async void SendAdmissionBroadcast(Admission admission, OperationType operation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (admission.PointOfCareId == null)
|
||||
await SendAdmissionByUnitId(admission, operation);
|
||||
else
|
||||
await SendAdmissionBroadcastByPoC(admission, operation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("Exception sending admission broadcast. Operation type: {Op}. Exception: {Ex}",
|
||||
operation.ToString(), ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAdmissionBroadcastByPoC(Admission admission, OperationType operation)
|
||||
{
|
||||
if (!admission.PointOfCareId.HasValue)
|
||||
{
|
||||
logger.LogError("Error sending admission broadcast. PointOfCare id {Id} not found",
|
||||
admission.PointOfCareId);
|
||||
return;
|
||||
}
|
||||
|
||||
var pointOfCare = await pointOfCareService.FindById(admission.PointOfCareId.Value);
|
||||
if (pointOfCare == null)
|
||||
{
|
||||
logger.LogError("Error sending admission broadcast. PointOfCare id {Id} not found",
|
||||
admission.PointOfCareId);
|
||||
return;
|
||||
}
|
||||
|
||||
var pocSubscribers = subscribersService.GetSubscribers().Where(s =>
|
||||
s.LocationIds.Contains(admission.PointOfCareId.Value)).GroupBy(h => h.Locale);
|
||||
|
||||
foreach (var group in pocSubscribers)
|
||||
{
|
||||
var locale = group.Key ?? LocaleEnum.Default;
|
||||
IEnumerable<WsSubscriber> subscribers = group;
|
||||
|
||||
foreach (var subscriber in subscribers)
|
||||
{
|
||||
var admissionWithLocale = await GetAdmissionWithLocale(admission, locale);
|
||||
_ = clientMessageService.SendAsync(subscriber.Id, operation, admissionWithLocale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAdmissionByUnitId(Admission admission, OperationType operation)
|
||||
{
|
||||
if (admission.UnitId == ObjectId.Empty)
|
||||
{
|
||||
logger.LogError("Error sending admission broadcast. UnitId is null or empty {Admission}", admission);
|
||||
return;
|
||||
}
|
||||
|
||||
var displays = await displayService.GetByUnitId(admission.UnitId);
|
||||
var displayIds = displays.Select(c => c.Id).ToList();
|
||||
var unitSubscribers = subscribersService.GetSubscribers().Where(s =>
|
||||
s.DisplayId != null && displayIds.Contains(s.DisplayId.Value)).GroupBy(h => h.Locale);
|
||||
foreach (var group in unitSubscribers)
|
||||
{
|
||||
var locale = group.Key ?? LocaleEnum.Default;
|
||||
IEnumerable<WsSubscriber> subscribers = group;
|
||||
|
||||
foreach (var subscriber in subscribers)
|
||||
{
|
||||
var admissionWithLocale = await GetAdmissionWithLocale(admission, locale);
|
||||
_ = clientMessageService.SendAsync(subscriber.Id, operation, admissionWithLocale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Admission> GetAdmissionWithLocale(Admission admission, LocaleEnum locale)
|
||||
{
|
||||
var unit = await unitService.FindById(admission.UnitId);
|
||||
if (unit == null) return admission;
|
||||
if (admission.Origin?.Name != null && unit.OriginListId.HasValue)
|
||||
{
|
||||
if (await masterListServiceFactory.GetMasterListById(MasterListType.OriginList,
|
||||
unit.OriginListId.Value, locale) is OriginList list)
|
||||
{
|
||||
var listOrigin = list.Options.FirstOrDefault(c => c.Id == admission.Origin.Id)?.Name;
|
||||
if (listOrigin != null)
|
||||
admission.Origin.Name = listOrigin;
|
||||
}
|
||||
}
|
||||
|
||||
if (admission.Diagnosis?.Name != null && unit.DiagnosisListId.HasValue)
|
||||
{
|
||||
if (await masterListServiceFactory.GetMasterListById(MasterListType.DiagnosisList,
|
||||
unit.DiagnosisListId.Value, locale) is DiagnosisList list)
|
||||
{
|
||||
var listDiagnosis = list.Options.FirstOrDefault(c => c.Id == admission.Diagnosis.Id)?.Name;
|
||||
if (listDiagnosis != null)
|
||||
admission.Diagnosis.Name = listDiagnosis;
|
||||
}
|
||||
}
|
||||
|
||||
if (admission.Insulation?.Name != null && unit.InsulationListId.HasValue)
|
||||
{
|
||||
if (await masterListServiceFactory.GetMasterListById(MasterListType.InsulationList,
|
||||
unit.InsulationListId.Value, locale) is InsulationList list)
|
||||
{
|
||||
var listInsulation = list.Options.FirstOrDefault(c => c.Id == admission.Insulation.Id)?.Name;
|
||||
if (listInsulation != null)
|
||||
admission.Insulation.Name = listInsulation;
|
||||
}
|
||||
}
|
||||
|
||||
return admission;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,969 @@
|
||||
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
|
||||
@@ -0,0 +1,17 @@
|
||||
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.MongoModels;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class AlertValuesService(IConfigObservationRepository alertValueRepository) : IAlertValuesService
|
||||
{
|
||||
public async Task<ConfigObservation?> FindByKey(ObjectId key)
|
||||
{
|
||||
return await alertValueRepository.FindById(key) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
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.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using Patient = adas_core.Domain.Models.MongoModels.Patient;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class AppointmentService(
|
||||
IAppointmentRepository appointmentRepository,
|
||||
IAppointmentArchiveRepository appointmentArchiveRepository,
|
||||
Lazy<IPatientService> patientService,
|
||||
Lazy<IObservationService> observationService,
|
||||
IDiagnosisService diagnosisService,
|
||||
IUnitService unitService,
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
IOptions<CacheSettings> cacheSettings,
|
||||
ILogger<AppointmentService> logger,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
IPointOfCareService pointOfCareService,
|
||||
ISubscribersService subscribersService,
|
||||
IClientMessageService clientMessageService,
|
||||
ICacheService cacheService)
|
||||
: IAppointmentService
|
||||
{
|
||||
private readonly bool _createPatientWithSiu = apiSettings.Value.CreatePatientWithSiu;
|
||||
private readonly CacheSettings? _cacheSettings = cacheSettings.Value ;
|
||||
|
||||
public async Task SaveRequest(ApiRequest apiRequest)
|
||||
{
|
||||
if (string.IsNullOrEmpty(apiRequest.PatientNumber))
|
||||
{
|
||||
logger.LogDebug("Patient is null");
|
||||
throw new ApiRequestException("Patient number is null");
|
||||
}
|
||||
|
||||
logger.LogDebug("patientNumber: {apiRequestPatientNumber} location: {apiRequestLocation}",
|
||||
apiRequest.PatientNumber, apiRequest.Location);
|
||||
logger.LogDebug("RequestType: {apiRequestType}", apiRequest.Type);
|
||||
|
||||
var patient = await patientService.Value.FindByPatientNumber(apiRequest.PatientNumber);
|
||||
if (patient == null)
|
||||
{
|
||||
// PATIENT NOT FOUND
|
||||
logger.LogWarning("Patient not Found. {patientNumber}", apiRequest.PatientNumber);
|
||||
if (!_createPatientWithSiu) return; // IGNORE
|
||||
patient = await patientService.Value.CreatePatientFromRequest(apiRequest, true);
|
||||
}
|
||||
|
||||
await ProcessApiRequest(apiRequest, patient);
|
||||
|
||||
if (patient != null)
|
||||
{
|
||||
if (apiRequest is { Observations: not null, ObservationData: not null })
|
||||
observationService.Value.ProcessObservations(apiRequest.Observations, patient,
|
||||
apiRequest.MessageTime, apiRequest.ObservationData);
|
||||
else
|
||||
logger.LogWarning("Observations not Found. {patientNumber} ", patient.PatientNumber);
|
||||
|
||||
if (apiRequest.Diagnosis != null)
|
||||
_ = diagnosisService.ProcessDiagnosis(apiRequest.Diagnosis, patient, apiRequest.MessageTime);
|
||||
else
|
||||
logger.LogWarning("Diagnosis not Found. {patientNumber}", patient.PatientNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessApiRequest(ApiRequest apiRequest, Patient? patient)
|
||||
{
|
||||
if (patient == null)
|
||||
return;
|
||||
|
||||
var unitConfig = await unitService.FindById(patient.UnitId);
|
||||
if (!Hl7Utils.ManageAutoAdt(unitConfig, null, logger, "ORU")) return;
|
||||
|
||||
apiRequest.Appointments ??= [];
|
||||
if (apiRequest.Appointment != null) apiRequest.Appointments.Add(apiRequest.Appointment);
|
||||
//apiRequest.appointments.ForEach(ap =>
|
||||
foreach (var ap in apiRequest.Appointments)
|
||||
{
|
||||
if (ap.VisitNumber == null || patient.Person == null) return;
|
||||
|
||||
ap.PatientId = patient.Id;
|
||||
ap.Patient = patient.Person;
|
||||
var apdb = ap.VisitNumber != null
|
||||
? await appointmentRepository.FindByPatientAndVisitNumber(ap.PatientId, ap.VisitNumber)
|
||||
: null;
|
||||
apdb ??= await appointmentRepository.FindByPatientAndReason(ap.PatientId, ap.AppointmentReason);
|
||||
|
||||
if (apdb != null && DateTime.Compare(apdb.UpdateTime, apiRequest.MessageTime) > 0) return;
|
||||
// TODO: Aux
|
||||
var oldAp = await auditService.DeepCopyAsync(ap);
|
||||
switch (apiRequest.Type)
|
||||
{
|
||||
//* SIU_S12 - Notification of new appointment booking
|
||||
//* SIU_S13 - Notification of Appointment Rescheduling
|
||||
//* SIU_S14 - Notification of Appointment Modification
|
||||
//* SIU_S18 - Notification of Addition of Service/Resource on Appointment
|
||||
//* SIU_S19 - Notification of Modification of Service/Resource on Appointment
|
||||
//* SIU_S20 - Notification of Cancellation of Service/Resource on Appointment
|
||||
//* SIU_S21 - Notification of Discontinuation of Service/Resource on Appointment
|
||||
//* SIU_S22 - Notification of Deletion of Service/Resource on Appointment
|
||||
|
||||
case "SIU_S12":
|
||||
case "SIU_S13":
|
||||
case "SIU_S14":
|
||||
case "SIU_S18":
|
||||
case "SIU_S19":
|
||||
case "SIU_S20":
|
||||
case "SIU_S21":
|
||||
case "SIU_S22":
|
||||
ap.UpdateTime = apiRequest.MessageTime;
|
||||
if (apdb != null)
|
||||
{
|
||||
ap.Id = apdb.Id;
|
||||
ap.CreateTime = apdb.CreateTime;
|
||||
ap.AppointmentOperationType = OperationType.UpdatedAppointment;
|
||||
ap.ApplyResourceGroups(apdb);
|
||||
await appointmentRepository.Update(ap);
|
||||
// Invalidar CACHE (colección completa)
|
||||
await cacheService.DeleteObjectAsync(CacheKeys.PatientAppointmentsToday(ap.PatientId));
|
||||
//SendBroadcast(ap, OperationType.updatedAppointment);
|
||||
}
|
||||
else
|
||||
{
|
||||
ap.CreateTime = apiRequest.MessageTime;
|
||||
ap.AppointmentOperationType = OperationType.NewAppointment;
|
||||
ap.ApplyResourceGroups();
|
||||
await appointmentRepository.InsertOneAsync(ap);
|
||||
//SendBroadcast(ap, OperationType.newAppointment);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
//* SIU_S15 - Notification of Appointment Cancellation
|
||||
//* SIU_S16 - Notification of Appointment Discontinuation
|
||||
//* SIU_S17 - Notification of Appointment Deletion
|
||||
|
||||
case "SIU_S15":
|
||||
case "SIU_S16":
|
||||
case "SIU_S17":
|
||||
ap.UpdateTime = apiRequest.MessageTime;
|
||||
if (apdb != null)
|
||||
{
|
||||
ap.Id = apdb.Id;
|
||||
ap.CreateTime = apdb.CreateTime;
|
||||
ap.AppointmentOperationType = OperationType.CanceledAppointment;
|
||||
ap.ApplyResourceGroups(apdb);
|
||||
await appointmentRepository.Update(ap);
|
||||
await cacheService.DeleteObjectAsync(CacheKeys.PatientAppointmentsToday(ap.PatientId));
|
||||
}
|
||||
else
|
||||
{
|
||||
ap.CreateTime = apiRequest.MessageTime;
|
||||
ap.AppointmentOperationType = OperationType.CanceledAppointment;
|
||||
ap.ApplyResourceGroups();
|
||||
await appointmentRepository.InsertOneAsync(ap);
|
||||
}
|
||||
|
||||
//SendBroadcast(ap, OperationType.canceledAppointment);
|
||||
break;
|
||||
|
||||
|
||||
//* SIU_S23 - Notification of Blocked Schedule Time Slot(S)
|
||||
//* SIU_S24 - Notification of Opened (un-blocked) Schedule Time Slot(s)
|
||||
//* SIU_S26 - Notification That Patient Did Not Show Up for Scheduled Appointment
|
||||
|
||||
default:
|
||||
|
||||
ap.UpdateTime = apiRequest.MessageTime;
|
||||
if (apdb != null)
|
||||
{
|
||||
ap.Id = apdb.Id;
|
||||
ap.CreateTime = apdb.CreateTime;
|
||||
//ap.appointmentOperationType = OperationType.updatedAppointment;
|
||||
ap.ApplyResourceGroups(apdb);
|
||||
await appointmentRepository.Update(ap);
|
||||
await cacheService.DeleteObjectAsync(CacheKeys.PatientAppointmentsToday(ap.PatientId));
|
||||
//SendBroadcast(ap, ap.appointmentOperationType);
|
||||
}
|
||||
else
|
||||
{
|
||||
ap.CreateTime = apiRequest.MessageTime;
|
||||
//ap.appointmentOperationType = OperationType.newAppointment;
|
||||
ap.ApplyResourceGroups();
|
||||
await appointmentRepository.InsertOneAsync(ap);
|
||||
//SendBroadcast(ap, ap.appointmentOperationType);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (apdb != null)
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, apdb, ap);
|
||||
|
||||
else await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, ap);
|
||||
|
||||
if (ap.AppointmentOperationType.HasValue)
|
||||
await SendBroadcast(ap, ap.AppointmentOperationType);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SaveRequestAsync(ApiRequest apiRequest)
|
||||
{
|
||||
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
|
||||
}
|
||||
|
||||
public async Task Archive(Patient patient)
|
||||
{
|
||||
await ArchiveByPatientId(patient.Id);
|
||||
}
|
||||
|
||||
public async Task ArchiveByPatientId(ObjectId id)
|
||||
{
|
||||
logger.LogDebug("Archive Appointments by patientId {id}", id);
|
||||
using (var cursor = await FindByPatientIdAsync(id))
|
||||
{
|
||||
while (await cursor.MoveNextAsync())
|
||||
foreach (var current in cursor.Current)
|
||||
await appointmentArchiveRepository.InsertOneAsync(current);
|
||||
}
|
||||
|
||||
await DeleteByPatientId(id);
|
||||
}
|
||||
|
||||
public async Task<List<PatientAppointment>> GetByPatient(ObjectId patientId)
|
||||
{
|
||||
return await appointmentRepository.GetByPatient(patientId);
|
||||
}
|
||||
|
||||
public async Task<List<PatientAppointment>> GetTodayByPatient(
|
||||
ObjectId patientId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// Obtener clave + TTL según CacheSettings
|
||||
var (key, ttl) = CacheKeys.PatientAppointmentsTodayKeyWithTtl(_cacheSettings, patientId);
|
||||
|
||||
// Cachear la lista RAW de citas del paciente (sin filtrar)
|
||||
var allAppointments = await cacheService.GetOrSetObjectAsync(
|
||||
key,
|
||||
async () =>
|
||||
{
|
||||
// 1 - Consultar todas las citas del paciente
|
||||
var list = await appointmentRepository.GetByPatient(patientId);
|
||||
|
||||
// 2 - Devuelve RAW (List<PatientAppointment>), nada filtrado
|
||||
return list;
|
||||
},
|
||||
ttl,
|
||||
ct);
|
||||
|
||||
// 3 - Ahora filtramos solo las de "hoy"
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
var todayAppointments = allAppointments
|
||||
.Where(a => a.Timings.Any(t =>
|
||||
t.StartTime.HasValue &&
|
||||
t.StartTime.Value.Date == today))
|
||||
.ToList();
|
||||
|
||||
return todayAppointments;
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<PatientAppointment>> GetTodayByPoc(
|
||||
ObjectId pocId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var poc = await pointOfCareService.FindById(pocId);
|
||||
if(poc == null) return [];
|
||||
|
||||
// Obtener clave + TTL según CacheSettings
|
||||
var (key, ttl) = CacheKeys.PocAppointmentsTodayKeyWithTtl(_cacheSettings, pocId);
|
||||
|
||||
// Cachear la lista RAW de citas del pointOfCare (sin filtrar)
|
||||
var allAppointments = await cacheService.GetOrSetObjectAsync(
|
||||
key,
|
||||
async () =>
|
||||
{
|
||||
// 1 - Consultar todas las citas del pointOfCare
|
||||
var list = await appointmentRepository.FindByPoC(poc);
|
||||
|
||||
// 2 - Devuelve RAW (List<PatientAppointment>), nada filtrado
|
||||
return list;
|
||||
},
|
||||
ttl,
|
||||
ct);
|
||||
|
||||
// 3 - Ahora filtramos solo las de "hoy"
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
var todayAppointments = allAppointments
|
||||
.Where(a => a.Timings.Any(t =>
|
||||
t.StartTime.HasValue &&
|
||||
t.StartTime.Value.Date == today))
|
||||
.ToList();
|
||||
|
||||
return todayAppointments;
|
||||
}
|
||||
|
||||
public Task<IAsyncCursor<PatientAppointment>> FindByPatientIdAsync(ObjectId patientId)
|
||||
{
|
||||
return appointmentRepository.FindByPatientIdAsync(patientId);
|
||||
}
|
||||
|
||||
public async Task<List<PatientAppointment>> FindByLocation(PatientLocation location)
|
||||
{
|
||||
return await appointmentRepository.FindByLocation(location);
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteByPatientId(ObjectId id)
|
||||
{
|
||||
var patientApp = await FindByPatientIdAsync(id);
|
||||
logger.LogDebug("Delete Appointments by Patient Id {id}", id);
|
||||
await appointmentRepository.DeleteByPatientId(id);
|
||||
|
||||
// Invalidar CACHE (colección completa)
|
||||
await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.Appointments));
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, patientApp, null);
|
||||
}
|
||||
|
||||
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
|
||||
{
|
||||
await appointmentRepository.UpdateManyObjectId(nameId, id, oldId);
|
||||
}
|
||||
|
||||
private async Task SendBroadcast(PatientAppointment appointment, OperationType? operationType)
|
||||
{
|
||||
//RECORRE LOS DIFERENTES LOCATIONS DE LA CITA
|
||||
foreach (var resourceGroup in appointment.ResourceGroups)
|
||||
if (resourceGroup.Locations != null)
|
||||
foreach (var location in resourceGroup.Locations)
|
||||
// TODO
|
||||
if (!string.IsNullOrEmpty(location.UnitName) && !string.IsNullOrEmpty(location.Bed))
|
||||
{
|
||||
var unit = await unitService.FindByName(location.UnitName);
|
||||
if (unit == null) continue;
|
||||
|
||||
var poc = await pointOfCareService.FindByBedAndUnitId(location.Bed, unit.Id);
|
||||
if (poc == null) continue;
|
||||
|
||||
var subscribers = subscribersService.GetSubscribers().Where(s =>
|
||||
!s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
|
||||
c == poc.Id
|
||||
)).ToList();
|
||||
|
||||
foreach (var subscriber in subscribers)
|
||||
_ = clientMessageService.SendAsync(subscriber.Id, operationType, appointment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ArchivePatientCarePlanService(
|
||||
IArchivePatientCarePlanRepository archivedPatientRepository,
|
||||
ILogger<ArchivePatientCarePlanService> logger,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService)
|
||||
: IArchivePatientCarePlanService
|
||||
{
|
||||
#region Create
|
||||
|
||||
public async Task<PatientCarePlan?> InsertOneAsync(PatientCarePlan patient)
|
||||
{
|
||||
await archivedPatientRepository.InsertOneAsync(patient);
|
||||
logger.LogInformation(
|
||||
"Inserted archived patient care plan for patientId: {PatientId}, patientNumber: {PatientNumber}",
|
||||
patient.PatientId, patient.PatientNumber);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, patient);
|
||||
return patient;
|
||||
}
|
||||
|
||||
public async Task InsertManyAsync(List<PatientCarePlan> patientCarePla)
|
||||
{
|
||||
await archivedPatientRepository.InsertManyAsync(patientCarePla);
|
||||
logger.LogInformation("Inserted {Count} archived patient care plans", patientCarePla.Count);
|
||||
foreach (var patient in patientCarePla)
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, patient);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
public async Task<List<PatientCarePlan>?> FindByPatientId(ObjectId patientId)
|
||||
{
|
||||
return await archivedPatientRepository.FindByPatientId(patientId);
|
||||
}
|
||||
|
||||
public async Task<List<PatientCarePlan>?> FindByPatientId(string patientId)
|
||||
{
|
||||
return await archivedPatientRepository.FindByPatientId(patientId);
|
||||
}
|
||||
|
||||
public async Task<List<PatientCarePlan>?> FindByPatientNumber(string patientId)
|
||||
{
|
||||
return await archivedPatientRepository.FindByPatientNumber(patientId);
|
||||
}
|
||||
|
||||
public Task<List<PatientCarePlan>> FindAll()
|
||||
{
|
||||
return archivedPatientRepository.FindAll();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ArchivePatientObservationsService(IObservationArchiveRepository archivedPatientObservationService)
|
||||
: IArchivedPatientObservationService
|
||||
{
|
||||
public async Task<List<PatientObservation>> FindArchivedPatientObservationsFromPatient(ObjectId patientId)
|
||||
{
|
||||
return await archivedPatientObservationService.FindAllFromPatient(patientId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ArchivedPatientService(IPatientArchiveRepository archivedPatientRepository) : IArchivedPatientService
|
||||
{
|
||||
public async Task<List<Patient>> FindAllPatients()
|
||||
{
|
||||
return await archivedPatientRepository.FindAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ArchivedPatientTreatmentService(ITreatmentArchiveRepository archivedPatientTreatmentService)
|
||||
: IArchivedPatientTreatmentService
|
||||
{
|
||||
public async Task<List<PatientTreatment>> FindAllPatientTreatmentsByPatient(ObjectId patientId)
|
||||
{
|
||||
return await archivedPatientTreatmentService.FindAllFromPatient(patientId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Text;
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.AppSettings;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using adas_core.Domain.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IAuthorityRepository _authorityRepository;
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly RecordingSettings _recordingSettings;
|
||||
|
||||
//private LoginResponse? _loginResponse;
|
||||
|
||||
public AuthService(IOptions<RecordingSettings> recordingSettings, ILogger<AuthService> logger,
|
||||
IAuthorityRepository authorityRepository)
|
||||
{
|
||||
_recordingSettings = recordingSettings.Value ??
|
||||
throw new Exception("RecordingSettings must be defined on appSettings");
|
||||
|
||||
_logger = logger;
|
||||
|
||||
_authorityRepository = authorityRepository;
|
||||
|
||||
_ = InstanceAuthUtils();
|
||||
}
|
||||
|
||||
public async Task<LoginResponse?> GetLoginResponse()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_recordingSettings.RecordingApiUrl.IsEmpty())
|
||||
{
|
||||
_logger.LogInformation("Url not defined to get token on AuthUtils...");
|
||||
return null;
|
||||
}
|
||||
|
||||
var user = new
|
||||
{
|
||||
Username = _recordingSettings.RecordingOrApiClientId,
|
||||
Password = _recordingSettings.RecordingOrApiClientSecret
|
||||
};
|
||||
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
|
||||
};
|
||||
|
||||
var client = new HttpClient(handler);
|
||||
var json = JsonConvert.SerializeObject(user);
|
||||
|
||||
var data = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
var response = await client.PostAsync($"{_recordingSettings.RecordingApiUrl}/users/login", data);
|
||||
var respToken = response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : null;
|
||||
if (respToken != null)
|
||||
{
|
||||
var ton = JsonConvert.DeserializeObject<LoginResponse>(respToken);
|
||||
if (ton != null && !ton.Token.IsEmpty())
|
||||
{
|
||||
AuthUtils.Instance.LoginResponse = ton;
|
||||
return ton;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error trying to get token: exception: {eMessage}", e.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetToken()
|
||||
{
|
||||
var loginResponse = AuthUtils.Instance.GetLoginResponse();
|
||||
if (!loginResponse.Token.IsEmptyOrWhiteSpace() && !loginResponse.IsExpired()) return loginResponse.Token;
|
||||
var resp = await GetLoginResponse();
|
||||
if (resp != null) return resp.Token;
|
||||
_logger.LogWarning("Failed Getting token check recordingSettings for user and pass");
|
||||
return "";
|
||||
}
|
||||
|
||||
public async Task<List<Authorization>> GetByUnitId(ObjectId unitId)
|
||||
{
|
||||
return await _authorityRepository.GetByUnitId(unitId);
|
||||
}
|
||||
|
||||
public async Task<List<Authorization>> GetUserAuthorities(ObjectId id)
|
||||
{
|
||||
return await _authorityRepository.GetUserAuthorities(id);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteByUnitId(ObjectId unitId)
|
||||
{
|
||||
return await _authorityRepository.DeleteAllAuthoritiesByUnit(unitId);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteByDisplayId(ObjectId displayId)
|
||||
{
|
||||
return await _authorityRepository.DeleteAllAuthoritiesByDisplay(displayId);
|
||||
}
|
||||
|
||||
private async Task InstanceAuthUtils()
|
||||
{
|
||||
if (_recordingSettings.RecordingApiUrl.IsEmpty())
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"RecordingOrApiUrl not defined on appsettings RecordingSettings AuthUtils not instanciated");
|
||||
return;
|
||||
}
|
||||
|
||||
if (AuthUtils.Instance.GetLoginResponse().Token.IsEmpty() || AuthUtils.Instance.GetLoginResponse().IsExpired())
|
||||
{
|
||||
_logger.LogInformation("Token is not generated or is expired sending request for new token");
|
||||
var newLoginResponse = await GetLoginResponse();
|
||||
if (newLoginResponse == null)
|
||||
{
|
||||
_logger.LogError("Token request is null check recordingSettings for user, pass and authorities");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("New Token generated at UTC DATE: {DateTime} exires at: {newLoginResponse}",
|
||||
DateTime.UtcNow, newLoginResponse.Expiration);
|
||||
AuthUtils.Instance.LoginResponse = newLoginResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.AppSettings;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using adas_core.Domain.Utils;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Orquestador de caché. Selecciona el backend (Redis, InMemory, None)
|
||||
/// según CacheSettings y la entidad del key.
|
||||
/// Implementa ICacheService y delega en el backend elegido.
|
||||
/// </summary>
|
||||
public class CacheDispatcher(
|
||||
RedisService redis,
|
||||
CacheService memory,
|
||||
NoCacheService noop,
|
||||
CacheSettings cacheSettings)
|
||||
: ICacheService
|
||||
{
|
||||
|
||||
// Selección de backend
|
||||
private ICacheService SelectBackend(string key)
|
||||
{
|
||||
var entity = CacheKeyClassifier.Classify(key);
|
||||
var mode = entity switch
|
||||
{
|
||||
CacheEnum.EntityType.Patients => cacheSettings.Patients,
|
||||
CacheEnum.EntityType.Displays => cacheSettings.Displays,
|
||||
CacheEnum.EntityType.PumpObservations => cacheSettings.PumpObservations,
|
||||
CacheEnum.EntityType.Appointments => cacheSettings.Appointments,
|
||||
CacheEnum.EntityType.GroupedObservations => cacheSettings.GroupedObservations,
|
||||
_ => CacheEnum.Mode.Cache
|
||||
};
|
||||
|
||||
return mode switch
|
||||
{
|
||||
CacheEnum.Mode.Redis => redis,
|
||||
CacheEnum.Mode.Cache => memory,
|
||||
_ => noop
|
||||
};
|
||||
}
|
||||
|
||||
// Para GroupedObservations generamos la misma clave compuesta que el resto de servicios,
|
||||
// de modo que el clasificador y la política de TTL funcionen igual.
|
||||
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
|
||||
=> $"GroupedObs:{patientId}:{gf.Name}";
|
||||
|
||||
private ICacheService SelectBackend(GroupedField groupedField, ObjectId patientId)
|
||||
=> SelectBackend(BuildGroupedKey(groupedField, patientId));
|
||||
|
||||
|
||||
// GetOrSet (KEY string)
|
||||
public Task<T> GetOrSetObjectAsync<T>(
|
||||
string key,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> SelectBackend(key).GetOrSetObjectAsync(key, factory, ttl, cancellationToken);
|
||||
|
||||
public Task<string?> GetOrSetValueAsync(
|
||||
string key,
|
||||
Func<Task<string>> loader,
|
||||
TimeSpan? ttl = null)
|
||||
=> SelectBackend(key).GetOrSetValueAsync(key, loader, ttl);
|
||||
|
||||
|
||||
// GetOrSet (GroupedField + PatientId)
|
||||
public Task<T> GetOrSetObjectAsync<T>(
|
||||
GroupedField groupedField,
|
||||
ObjectId patientId,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> SelectBackend(groupedField, patientId)
|
||||
.GetOrSetObjectAsync(groupedField, patientId, factory, ttl, cancellationToken);
|
||||
|
||||
|
||||
// Set/Get básicos
|
||||
public void SetValue(string key, string value)
|
||||
=> SelectBackend(key).SetValue(key, value);
|
||||
|
||||
public string? GetValue(string key)
|
||||
=> SelectBackend(key).GetValue(key);
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, bool upd = true)
|
||||
=> SelectBackend(key).GetObjectAsync<T>(key, upd);
|
||||
|
||||
public Task SetObjectAsync<T>(string key, T obj, bool upd = true)
|
||||
=> SelectBackend(key).SetObjectAsync(key, obj, upd);
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttl, bool upd)
|
||||
=> SelectBackend(key).GetObjectAsync<T>(key, ttl, upd);
|
||||
|
||||
public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttl, bool upd)
|
||||
=> SelectBackend(key).SetObjectAsync(key, obj, ttl, upd);
|
||||
|
||||
public Task DeleteObjectAsync(string key)
|
||||
=> SelectBackend(key).DeleteObjectAsync(key);
|
||||
|
||||
public async Task<long> DeleteByPatternAsync(string pattern)
|
||||
=> await redis.DeleteByPatternAsync(pattern) + await memory.DeleteByPatternAsync(pattern);
|
||||
|
||||
public void CleanCache()
|
||||
{
|
||||
memory.CleanCache();
|
||||
redis.CleanCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using MongoDB.Bson;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementación de caché en memoria con soporte de locking seguro
|
||||
/// mediante LockManagerService + InMemoryLockProvider.
|
||||
/// Compatible con la interfaz ICacheService incluyendo GetOrSet.
|
||||
/// </summary>
|
||||
public class CacheService(LockManagerService lockManager) : ICacheService
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> _mem = new();
|
||||
|
||||
// HELPERS
|
||||
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
|
||||
=> $"GroupedObs:{patientId}:{gf.Name}";
|
||||
|
||||
|
||||
// GET OR SET (string key)
|
||||
public async Task<T> GetOrSetObjectAsync<T>(
|
||||
string key,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// FAST PATH
|
||||
if (_mem.TryGetValue(key, out var existing))
|
||||
return (T)existing;
|
||||
|
||||
// LOCKED PATH
|
||||
return await lockManager.WithLockAsync(
|
||||
$"getorset:{key}",
|
||||
TimeSpan.FromSeconds(5),
|
||||
async () =>
|
||||
{
|
||||
if (_mem.TryGetValue(key, out var again))
|
||||
return (T)again;
|
||||
|
||||
var created = await factory();
|
||||
|
||||
if (created != null!)
|
||||
_mem[key] = created;
|
||||
|
||||
return created!;
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<string?> GetOrSetValueAsync(
|
||||
string key,
|
||||
Func<Task<string>> loader,
|
||||
TimeSpan? ttlOverride = null)
|
||||
{
|
||||
|
||||
var result = await GetOrSetObjectAsync(key, loader, ttlOverride);
|
||||
return (string?)result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// GET OR SET (GroupedField + patientId)
|
||||
public async Task<T> GetOrSetObjectAsync<T>(
|
||||
GroupedField groupedField,
|
||||
ObjectId patientId,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = BuildGroupedKey(groupedField, patientId);
|
||||
|
||||
if (_mem.TryGetValue(key, out var existing))
|
||||
return (T)existing;
|
||||
|
||||
return await lockManager.WithLockAsync(
|
||||
$"getorset:{key}",
|
||||
TimeSpan.FromSeconds(5),
|
||||
async () =>
|
||||
{
|
||||
if (_mem.TryGetValue(key, out var again))
|
||||
return (T)again;
|
||||
|
||||
var created = await factory();
|
||||
|
||||
if (created != null!)
|
||||
_mem[key] = created;
|
||||
|
||||
return created!;
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
// GET / SET
|
||||
public void SetValue(string key, string value)
|
||||
=> _mem[key] = value;
|
||||
|
||||
public string? GetValue(string key)
|
||||
=> _mem.TryGetValue(key, out var v) ? v.ToString() : null;
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
|
||||
{
|
||||
return Task.FromResult(
|
||||
_mem.TryGetValue(key, out var v) ? (T?)v : default
|
||||
);
|
||||
}
|
||||
|
||||
public Task SetObjectAsync<T>(string key, T obj, bool updateExpiration = true)
|
||||
{
|
||||
_mem[key] = obj!;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool upd)
|
||||
=> GetObjectAsync<T>(key, upd);
|
||||
|
||||
public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttlOverride, bool upd)
|
||||
=> SetObjectAsync(key, obj, upd);
|
||||
|
||||
|
||||
// DELETE / CLEAN
|
||||
public Task DeleteObjectAsync(string key)
|
||||
{
|
||||
_mem.TryRemove(key, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<long> DeleteByPatternAsync(string pattern)
|
||||
{
|
||||
var p = pattern.Replace("*", "");
|
||||
var keys = _mem.Keys.Where(k => k.Contains(p)).ToList();
|
||||
|
||||
long removed = 0;
|
||||
foreach (var k in keys)
|
||||
if (_mem.TryRemove(k, out _))
|
||||
removed++;
|
||||
|
||||
return Task.FromResult(removed);
|
||||
}
|
||||
|
||||
public void CleanCache() => _mem.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Concurrent;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementación local del sistema de locking por clave.
|
||||
/// Se basa en SemaphoreSlim y solo controla concurrencia DENTRO del proceso.
|
||||
/// Para CacheService (in-memory).
|
||||
/// </summary>
|
||||
public class InMemoryLockProvider : ILockProvider
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Crea u obtiene un semáforo asociado a la clave.
|
||||
/// </summary>
|
||||
private SemaphoreSlim GetOrCreate(string key)
|
||||
{
|
||||
return _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Intenta adquirir el lock por clave.
|
||||
/// </summary>
|
||||
public async Task<bool> AcquireAsync(string key, TimeSpan timeout)
|
||||
{
|
||||
var sem = GetOrCreate(key);
|
||||
|
||||
try
|
||||
{
|
||||
return await sem.WaitAsync(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Si el semáforo se eliminó entre medio, creamos uno nuevo.
|
||||
_locks.TryRemove(key, out _);
|
||||
return await GetOrCreate(key).WaitAsync(timeout).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Libera el lock (si existe y no está ya liberado).
|
||||
/// </summary>
|
||||
public Task ReleaseAsync(string key)
|
||||
{
|
||||
if (!_locks.TryGetValue(key, out var sem))
|
||||
return Task.CompletedTask;
|
||||
|
||||
try
|
||||
{
|
||||
sem.Release();
|
||||
}
|
||||
catch (SemaphoreFullException)
|
||||
{
|
||||
// Idempotencia: ignoramos exceso de releases
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Servicio que ejecuta acciones dentro de una sección crítica controlada por lock.
|
||||
/// CacheService lo usará para evitar condiciones de carrera en GetOrSet.
|
||||
/// Funciona igual para locks locales o distribuidos.
|
||||
/// </summary>
|
||||
public class LockManagerService(
|
||||
ILogger<LockManagerService> logger,
|
||||
ILockProvider provider)
|
||||
{
|
||||
/// <summary>
|
||||
/// Ejecuta una función que devuelve un valor bajo un lock por clave.
|
||||
/// </summary>
|
||||
public async Task<T> WithLockAsync<T>(
|
||||
string key,
|
||||
TimeSpan timeout,
|
||||
Func<Task<T>> action,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger.LogDebug(
|
||||
"LockManager — intentando adquirir lock para key={Key} timeout={TimeoutMs}ms",
|
||||
key, timeout.TotalMilliseconds);
|
||||
|
||||
var acquired = false;
|
||||
|
||||
try
|
||||
{
|
||||
acquired = await provider.AcquireAsync(key, timeout).ConfigureAwait(false);
|
||||
|
||||
if (!acquired)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"LockManager — timeout al adquirir lock para key={Key} tras {TimeoutMs}ms",
|
||||
key, timeout.TotalMilliseconds);
|
||||
throw new TimeoutException($"No se pudo adquirir el lock para '{key}'");
|
||||
}
|
||||
|
||||
logger.LogDebug(
|
||||
"LockManager — lock adquirido correctamente para key={Key}",
|
||||
key);
|
||||
|
||||
return await action().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (acquired)
|
||||
{
|
||||
await SafeReleaseAsync(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Version Task (sin valor).
|
||||
/// </summary>
|
||||
public Task WithLockAsync(
|
||||
string key,
|
||||
TimeSpan timeout,
|
||||
Func<Task> action,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return WithLockAsync<object?>(
|
||||
key,
|
||||
timeout,
|
||||
async () =>
|
||||
{
|
||||
await action();
|
||||
return null;
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Libera el lock y registra posibles errores sin interrumpir el flujo.
|
||||
/// </summary>
|
||||
private async Task SafeReleaseAsync(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
await provider.ReleaseAsync(key).ConfigureAwait(false);
|
||||
logger.LogDebug("LockManager — key={Key} lock deleted ", key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "LockManager — key={Key} error deleting lock", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementación nula de ICacheService.
|
||||
/// No almacena nada, no devuelve nada y no interfiere con el flujo.
|
||||
/// Se usa cuando el CacheMode es "None".
|
||||
/// </summary>
|
||||
public class NoCacheService : ICacheService
|
||||
{
|
||||
public void SetValue(string key, string value)
|
||||
{
|
||||
// No hacer nada
|
||||
}
|
||||
|
||||
public string? GetValue(string key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
|
||||
{
|
||||
return Task.FromResult<T?>(default);
|
||||
}
|
||||
|
||||
public Task SetObjectAsync<T>(string key, T obj, bool updateExpiration = true)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool updateExpiration)
|
||||
{
|
||||
return Task.FromResult<T?>(default);
|
||||
}
|
||||
|
||||
public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttlOverride, bool updateExpiration)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<long> DeleteByPatternAsync(string pattern)
|
||||
{
|
||||
return Task.FromResult(0L);
|
||||
}
|
||||
|
||||
public Task DeleteObjectAsync(string key)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void CleanCache()
|
||||
{
|
||||
// Nada que limpiar
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GET OR SET - STRING KEY
|
||||
// ============================================================
|
||||
|
||||
public async Task<T> GetOrSetObjectAsync<T>(
|
||||
string key,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// En modo NONE no hay caché → siempre ejecutar factory
|
||||
return await factory();
|
||||
}
|
||||
|
||||
public async Task<string?> GetOrSetValueAsync(
|
||||
string key,
|
||||
Func<Task<string>> loader,
|
||||
TimeSpan? ttl = null)
|
||||
{
|
||||
var result = await loader();
|
||||
return (string?)result;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GET OR SET - GroupedField + patientId
|
||||
// ============================================================
|
||||
|
||||
public async Task<T> GetOrSetObjectAsync<T>(
|
||||
GroupedField groupedField,
|
||||
ObjectId patientId,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Igual que arriba: en modo NONE no hay caché
|
||||
return await factory();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using StackExchange.Redis;
|
||||
using System.Collections.Concurrent;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Lock distribuido en Redis. Usa token por adquisición (owner)
|
||||
/// y liberación segura con script Lua: borra la key solo si el valor coincide.
|
||||
/// </summary>
|
||||
public class RedisLockProvider(Func<IDatabase?> getDatabase) : ILockProvider
|
||||
{
|
||||
private readonly string _prefix = "lock:";
|
||||
private readonly TimeSpan _ttl = TimeSpan.FromSeconds(5);
|
||||
private readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(50);
|
||||
|
||||
private static readonly string LuaReleaseScript = @"
|
||||
if redis.call('get', KEYS[1]) == ARGV[1] then
|
||||
return redis.call('del', KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end";
|
||||
|
||||
private readonly ConcurrentDictionary<string, string> _tokens =
|
||||
new(StringComparer.Ordinal);
|
||||
|
||||
public async Task<bool> AcquireAsync(string key, TimeSpan timeout)
|
||||
{
|
||||
var redis = getDatabase();
|
||||
if (redis is null) return false;
|
||||
|
||||
var redisKey = (RedisKey)(_prefix + key);
|
||||
var token = Guid.NewGuid().ToString("N");
|
||||
var end = DateTime.UtcNow.Add(timeout);
|
||||
|
||||
while (DateTime.UtcNow < end)
|
||||
{
|
||||
if (await redis.StringSetAsync(redisKey, token, _ttl, When.NotExists))
|
||||
{
|
||||
_tokens[key] = token;
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(_retryDelay);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task ReleaseAsync(string key)
|
||||
{
|
||||
if (!_tokens.TryRemove(key, out var token))
|
||||
return;
|
||||
|
||||
var redis = getDatabase();
|
||||
if (redis is null) return;
|
||||
|
||||
var redisKey = (RedisKey)(_prefix + key);
|
||||
|
||||
await redis.ScriptEvaluateAsync(
|
||||
LuaReleaseScript,
|
||||
[redisKey],
|
||||
[token]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.AppSettings;
|
||||
using adas_core.Domain.Utils;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using MongoDB.Bson;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace adas_core.Application.Services.Caching
|
||||
{
|
||||
public class RedisService : ICacheService
|
||||
{
|
||||
private readonly ILogger<RedisService> _logger;
|
||||
private readonly CacheSettings _cacheSettings;
|
||||
private readonly LockManagerService _lockManager;
|
||||
private ConnectionMultiplexer? _connection;
|
||||
private IDatabase? _database;
|
||||
private IServer? _server;
|
||||
|
||||
public IDatabase? Database => _database;
|
||||
private bool _isRedisAvailable;
|
||||
|
||||
public RedisService(
|
||||
IOptions<CacheSettings> options,
|
||||
ILogger<RedisService> logger,
|
||||
LockManagerService lockManager)
|
||||
{
|
||||
_cacheSettings = options.Value;
|
||||
_logger = logger;
|
||||
_lockManager = lockManager;
|
||||
|
||||
if (!string.IsNullOrEmpty(_cacheSettings.Redis.ConnectionString))
|
||||
_ = InitializeRedisConnectionAsync();
|
||||
}
|
||||
|
||||
|
||||
// GET OR SET (string key)
|
||||
public async Task<T> GetOrSetObjectAsync<T>(
|
||||
string key,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_isRedisAvailable)
|
||||
return await factory();
|
||||
|
||||
var direct = await GetObjectAsync<T>(key);
|
||||
|
||||
if (direct is not null)
|
||||
return direct;
|
||||
|
||||
return await _lockManager.WithLockAsync(
|
||||
$"getorset:{key}",
|
||||
TimeSpan.FromSeconds(5),
|
||||
async () =>
|
||||
{
|
||||
var again = await GetObjectAsync<T>(key);
|
||||
if (again is not null)
|
||||
return again;
|
||||
|
||||
var created = await factory();
|
||||
|
||||
if (created != null)
|
||||
await SetObjectAsync(key, created, ttl, true);
|
||||
|
||||
return created!;
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<string?> GetOrSetValueAsync(
|
||||
string key,
|
||||
Func<Task<string>> loader,
|
||||
TimeSpan? ttl = null)
|
||||
{
|
||||
if (!_isRedisAvailable)
|
||||
return await loader();
|
||||
|
||||
var direct = GetValue(key);
|
||||
if (direct is not null)
|
||||
return direct;
|
||||
|
||||
return await _lockManager.WithLockAsync(
|
||||
$"getorset:{key}",
|
||||
TimeSpan.FromSeconds(5),
|
||||
async () =>
|
||||
{
|
||||
var again = GetValue(key);
|
||||
if (again is not null)
|
||||
return again;
|
||||
|
||||
var created = await loader();
|
||||
SetValue(key, created);
|
||||
|
||||
return created;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// GET OR SET (GroupedField + patientId)
|
||||
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
|
||||
=> $"GroupedObs:{patientId}:{gf.Name}";
|
||||
|
||||
public async Task<T> GetOrSetObjectAsync<T>(
|
||||
GroupedField groupedField,
|
||||
ObjectId patientId,
|
||||
Func<Task<T>> factory,
|
||||
TimeSpan? ttl = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var key = BuildGroupedKey(groupedField, patientId);
|
||||
|
||||
if (!_isRedisAvailable)
|
||||
return await factory();
|
||||
|
||||
var direct = await GetObjectAsync<T>(key);
|
||||
if (direct is not null)
|
||||
return direct;
|
||||
|
||||
return await _lockManager.WithLockAsync(
|
||||
$"getorset:{key}",
|
||||
TimeSpan.FromSeconds(5),
|
||||
async () =>
|
||||
{
|
||||
var again = await GetObjectAsync<T>(key);
|
||||
if (again is not null)
|
||||
return again;
|
||||
|
||||
var created = await factory();
|
||||
|
||||
if (created != null)
|
||||
await SetObjectAsync(key, created, ttl, true);
|
||||
|
||||
return created!;
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
// BASIC OPERATIONS
|
||||
public void SetValue(string key, string value)
|
||||
=> _database?.StringSet(key, value, GetEntityTtl(key), true);
|
||||
|
||||
public string? GetValue(string key)
|
||||
{
|
||||
var val = _database?.StringGet(key);
|
||||
if (val.HasValue && ShouldRenewTtl(key))
|
||||
_database?.KeyExpire(key, GetEntityTtl(key));
|
||||
return val;
|
||||
}
|
||||
|
||||
public async Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
|
||||
{
|
||||
if (!_isRedisAvailable)
|
||||
return default;
|
||||
|
||||
var json = await _database!.StringGetAsync(key);
|
||||
if (json.IsNullOrEmpty)
|
||||
return default;
|
||||
|
||||
if (updateExpiration)
|
||||
_database!.KeyExpire(key, GetEntityTtl(key));
|
||||
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
Converters = new List<JsonConverter> { new StringEnumConverter(), new ObjectIdConverter() }
|
||||
};
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<T>(json!, settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error deserializing object in RedisService {e}",e.ToString());
|
||||
throw new Exception($"Error deserializing object in RedisService {e}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetObjectAsync<T>(
|
||||
string key,
|
||||
T obj,
|
||||
bool updateExpiration = true)
|
||||
=> await SetObjectAsync(key, obj, null, updateExpiration);
|
||||
|
||||
public async Task SetObjectAsync<T>(
|
||||
string key,
|
||||
T obj,
|
||||
TimeSpan? ttlOverride,
|
||||
bool updateExpiration)
|
||||
{
|
||||
if (!_isRedisAvailable)
|
||||
return;
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
Converters = new List<JsonConverter> { new StringEnumConverter(), new ObjectIdConverter() }
|
||||
};
|
||||
var json = JsonConvert.SerializeObject(obj, settings);
|
||||
await _database!.StringSetAsync(key, json, ttlOverride ?? GetEntityTtl(key), true);
|
||||
}
|
||||
|
||||
public async Task DeleteObjectAsync(string key)
|
||||
{
|
||||
if (_isRedisAvailable)
|
||||
await _database!.KeyDeleteAsync(key);
|
||||
}
|
||||
|
||||
public async Task<long> DeleteByPatternAsync(string pattern)
|
||||
{
|
||||
if (!_isRedisAvailable || _server == null)
|
||||
return 0;
|
||||
|
||||
var keys = _server.Keys(pattern: pattern).ToArray();
|
||||
|
||||
foreach (var key in keys)
|
||||
await _database!.KeyDeleteAsync(key);
|
||||
|
||||
return keys.Length;
|
||||
}
|
||||
|
||||
public void CleanCache()
|
||||
=> _server?.FlushDatabase();
|
||||
|
||||
|
||||
// TTL
|
||||
private TimeSpan? GetEntityTtl(string key)
|
||||
{
|
||||
var entity = CacheKeyClassifier.Classify(key);
|
||||
var ttl = _cacheSettings.Redis.Ttl;
|
||||
|
||||
int? seconds = entity switch
|
||||
{
|
||||
CacheEnum.EntityType.Patients => ttl.PatientsSeconds,
|
||||
CacheEnum.EntityType.Displays => ttl.DisplaysSeconds,
|
||||
_ => ttl.GlobalSeconds
|
||||
};
|
||||
|
||||
return seconds > 0 ? TimeSpan.FromSeconds(seconds.Value) : null;
|
||||
}
|
||||
|
||||
private bool ShouldRenewTtl(string key)
|
||||
=> GetEntityTtl(key) != null;
|
||||
|
||||
|
||||
// INITIALIZATION
|
||||
private async Task InitializeRedisConnectionAsync()
|
||||
{
|
||||
_isRedisAvailable = false;
|
||||
try
|
||||
{
|
||||
if (_cacheSettings.Redis.ConnectionString == null)
|
||||
return;
|
||||
|
||||
_connection = await ConnectionMultiplexer.ConnectAsync(_cacheSettings.Redis.ConnectionString);
|
||||
_database = _connection.GetDatabase();
|
||||
_server = _connection.GetServer(_cacheSettings.Redis.ConnectionString);
|
||||
|
||||
_isRedisAvailable = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError( "Error Initializing Redis Connection. Exception:{ex}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool updateExpiration)
|
||||
=> GetObjectAsync<T>(key, updateExpiration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.AppSettings;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class CalculatedObservationsService : ICalculatedObservationsService
|
||||
{
|
||||
private static ICalculatedObservations? _service;
|
||||
private readonly ILogger<CalculatedObservationsService>? _logger;
|
||||
|
||||
|
||||
public CalculatedObservationsService(
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<CalculatedObservationsService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
var customize = apiSettings.Value.Customize;
|
||||
if (customize == null)
|
||||
{
|
||||
_logger.LogWarning("Not customization specified");
|
||||
_service = new DefaultCalculatedObservations();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var calculatedObservations = "adas_core.Application.Customizations." + customize + ".CalculatedObservations";
|
||||
|
||||
if (string.IsNullOrEmpty(customize))
|
||||
{
|
||||
_service = new DefaultCalculatedObservations();
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = Type.GetType(calculatedObservations);
|
||||
if (type == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
$"Type {calculatedObservations} not found for specification. Using DefaultCalculatedObservations");
|
||||
_service = new DefaultCalculatedObservations();
|
||||
return;
|
||||
}
|
||||
|
||||
var ctor = Type.GetType(calculatedObservations)?.GetConstructor([typeof(IServiceProvider)]);
|
||||
if (ctor == null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
$"Constructor not found for type {calculatedObservations} accepts one parameter with type IServiceProvider. Using DefaultCalculatedObservations");
|
||||
_service = new DefaultCalculatedObservations();
|
||||
return;
|
||||
}
|
||||
|
||||
_service = (ICalculatedObservations)ctor.Invoke([serviceProvider]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual async Task<PatientObservation?> Map(PatientObservation obs, bool onlyByName = false)
|
||||
{
|
||||
if (_service == null) return obs;
|
||||
var result = await _service.Map(obs, onlyByName);
|
||||
if (result == null)
|
||||
_logger?.LogDebug("Mapping calculated via service {serviceFullName}. {obs} Ignored",
|
||||
_service.GetType().FullName, obs);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual async Task<PatientObservationAlarm?> Map(PatientObservationAlarm obs, bool onlyByName = false)
|
||||
{
|
||||
if (_service == null) return obs;
|
||||
var result = await _service.Map(obs, onlyByName);
|
||||
if (result == null)
|
||||
_logger?.LogDebug("Mapping calculated via service {serviceFullName}. {obs} Ignored",
|
||||
_service.GetType().FullName, obs);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual async Task<PumpObservation?> Map(PumpObservation obs)
|
||||
{
|
||||
if (_service == null) return null;
|
||||
var result = await _service.Map(obs);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<PatientRecordingAlert?> Map(PatientRecordingAlert obs)
|
||||
{
|
||||
if (_service == null) return obs;
|
||||
var result = await _service.Map(obs);
|
||||
if (result == null)
|
||||
_logger?.LogDebug("Mapping calculated via service {serviceFullName}. {obs} Ignored",
|
||||
_service.GetType().FullName, obs);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<PatientTreatment?> Map(PatientTreatment treatment)
|
||||
{
|
||||
if (_service == null) return treatment;
|
||||
var result = await _service.Map(treatment);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual async Task<PatientDiagnosis?> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
if (_service == null) return diagnosis;
|
||||
var result = await _service.Map(diagnosis);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual async Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
if (_service != null) await _service.CalculateMedicineObservation(activeMedicines, patientId);
|
||||
}
|
||||
|
||||
public async Task CalculateBolusOpiates(ObjectId patientId)
|
||||
{
|
||||
if (_service != null) await _service.CalculateActiveBolus(patientId);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
if (_service != null) return await _service.GetActiveTreatmentsByPatient(id);
|
||||
return new List<PatientTreatment?>();
|
||||
}
|
||||
|
||||
public virtual async Task<List<PatientObservation>> MapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
if (_service != null) return await _service.PreMapList(listToInsert);
|
||||
return [];
|
||||
}
|
||||
|
||||
public virtual async Task<PatientObservation> MapSourceAlarm(PatientObservation observation,
|
||||
PatientObservationAlarm observationAlarm)
|
||||
{
|
||||
if (_service != null) return await _service.MapSourceAlarm(observation, observationAlarm);
|
||||
return observation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class CameraService(ILogger<CameraService> logger, ICameraRepository cameraRepository, IPointOfCareService pointOfCareService) : ICameraService
|
||||
{
|
||||
private ICameraRepository _cameraRepository = cameraRepository;
|
||||
public Task<Camera?> GetById(ObjectId relayId)
|
||||
{
|
||||
return _cameraRepository.GetById(relayId);
|
||||
}
|
||||
public List<Camera> GetCameraInList(List<ObjectId> configurationRelayList)
|
||||
{
|
||||
return _cameraRepository.GetCameraInList(configurationRelayList);
|
||||
}
|
||||
|
||||
public async Task<PaginationResponse<Camera>> GetPaginatedCameras(PaginationFilter filter)
|
||||
{
|
||||
var usedCameraIds = await pointOfCareService.FindAllIdCamerasInUse();
|
||||
|
||||
var fluentQuery = _cameraRepository.GetPaginatedCameras(filter);
|
||||
|
||||
if (filter.FilteredRequest?.InUse != null)
|
||||
{
|
||||
bool filterInUse = filter.FilteredRequest.InUse.Value;
|
||||
var filterBuilder = Builders<Camera>.Filter;
|
||||
|
||||
var idFilter = filterInUse
|
||||
? filterBuilder.In(c => c.Id, usedCameraIds)
|
||||
: filterBuilder.Not(filterBuilder.In(c => c.Id, usedCameraIds));
|
||||
|
||||
fluentQuery.Filter = filterBuilder.And(fluentQuery.Filter, idFilter);
|
||||
}
|
||||
|
||||
var count = await fluentQuery.CountDocumentsAsync();
|
||||
var data = await fluentQuery
|
||||
.Skip((filter.PageNumber - 1) * filter.PageSize)
|
||||
.Limit(filter.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
if(data == null) return new PaginationResponse<Camera>([], filter.PageNumber, filter.PageSize, count);
|
||||
|
||||
foreach (var camera in data)
|
||||
{
|
||||
if (camera == null) continue;
|
||||
|
||||
bool isInUse = usedCameraIds.Contains(camera.Id);
|
||||
|
||||
// Asignación mediante reflexión para el private set
|
||||
camera.GetType().GetProperty(nameof(Camera.InUse))
|
||||
?.SetValue(camera, isInUse);
|
||||
}
|
||||
|
||||
return new PaginationResponse<Camera>(data, filter.PageNumber, filter.PageSize, count);
|
||||
}
|
||||
|
||||
public async Task<Camera?> InsertCamera(Camera camera)
|
||||
{
|
||||
if (camera.Name == null) throw new Exception("Camera name cannot be null");
|
||||
var cameraFound = await _cameraRepository.GetByName(camera.Name);
|
||||
if(cameraFound != null) throw new Exception($"Camera with name {camera.Name} already exists");
|
||||
return await _cameraRepository.InsertOneCamera(camera);
|
||||
}
|
||||
|
||||
public async Task<Camera?> UpdateCameraById(ObjectId objectId, Camera camera)
|
||||
{
|
||||
return await _cameraRepository.UpdateCameraAsync(objectId, camera);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCamera(ObjectId objectId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cameraToDelete = await _cameraRepository.GetById(objectId);
|
||||
if(cameraToDelete == null) return false;
|
||||
|
||||
await _cameraRepository.DeleteAsync(cameraToDelete.Id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, e.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<List<Camera>> GetSearchByNameCameras(string textToSearch)
|
||||
{
|
||||
return await _cameraRepository.GetSearchByNameCameras(textToSearch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,642 @@
|
||||
using System.Collections.Concurrent;
|
||||
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.DTO;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.GroupedObservations;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using adas_core.Domain.Models.Responses;
|
||||
using adas_core.Domain.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ConfigObservationService : IConfigObservationService
|
||||
{
|
||||
private static readonly ConcurrentDictionary<ObjectId, ConfigObservationCached> CachedConfigObservations = new();
|
||||
|
||||
private static readonly ConcurrentDictionary<string, ConfigObservationKeyCached>
|
||||
CachedConfigObservationKeys = new();
|
||||
|
||||
private readonly IOptions<ApiSettings> _apiSettings;
|
||||
private readonly ILocalAuditService _auditService;
|
||||
private readonly IConfigObservationRepository _configObservationRepository;
|
||||
private readonly RetentionPolicy _defaultRetentionPolicy;
|
||||
private readonly int _defaultRetentionPolicyValue;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly bool _ignoreUnknownTreatment;
|
||||
private readonly ILogger<ConfigObservationService> _logger;
|
||||
private readonly int? _refreshTimeout;
|
||||
private readonly IUnitService _unitService;
|
||||
private readonly ICacheService _cacheService;
|
||||
private readonly CacheSettings? _cacheSettings;
|
||||
|
||||
private bool IgnoreUnknownObservation =>
|
||||
_apiSettings.Value.ConfigObservation?.IgnoreUnknownObservation ?? false;
|
||||
|
||||
public ConfigObservationService(
|
||||
IConfigObservationRepository configObservationRepository,
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
IOptions<CacheSettings> cacheSettings,
|
||||
ILogger<ConfigObservationService> logger,
|
||||
IUnitService unitService,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
ICacheService cacheService
|
||||
)
|
||||
{
|
||||
_cacheService = cacheService;
|
||||
_cacheSettings = cacheSettings.Value;
|
||||
_configObservationRepository = configObservationRepository;
|
||||
_apiSettings = apiSettings;
|
||||
_logger = logger;
|
||||
_unitService = unitService;
|
||||
|
||||
_refreshTimeout = _apiSettings.Value.ConfigObservation?.Refresh;
|
||||
_ignoreUnknownTreatment = _apiSettings.Value.ConfigObservation?.IgnoreUnknownTreatment ?? false;
|
||||
|
||||
_defaultRetentionPolicyValue = _apiSettings.Value.RetentionPolicyValue;
|
||||
|
||||
_defaultRetentionPolicy = RetentionPolicy.NoDelete;
|
||||
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_auditService = auditService;
|
||||
}
|
||||
|
||||
public async Task<ICollection<ConfigObservation>> GetAllConfigs(CancellationToken ct = default)
|
||||
{
|
||||
|
||||
var (key, ttl) = CacheKeys.ConfigObservationsAllKeyWithTtl(_cacheSettings);
|
||||
|
||||
var result = await _cacheService.GetOrSetObjectAsync(
|
||||
key,
|
||||
async () => await _configObservationRepository.FindAll(),
|
||||
ttl,
|
||||
ct);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public async Task<ConfigObservationDto> GetAllCompact()
|
||||
{
|
||||
var count = await _configObservationRepository.Count();
|
||||
return new ConfigObservationDto { ItemCount = count };
|
||||
}
|
||||
|
||||
public async Task<PaginationResponse<ConfigObservation>> GetPaginatedItems(PaginationFilter filter)
|
||||
{
|
||||
var result = await _configObservationRepository.GetPaginatedItems(filter);
|
||||
var count = await _configObservationRepository.Count();
|
||||
return new PaginationResponse<ConfigObservation>(result.ToList(), filter.PageNumber, filter.PageSize,
|
||||
count);
|
||||
}
|
||||
|
||||
|
||||
public async Task<ConfigObservation?> GetConfigById(ObjectId id)
|
||||
{
|
||||
return await _configObservationRepository.FindById(id) ?? null;
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetConfigNames(string id)
|
||||
{
|
||||
return await _configObservationRepository.GetConfigNames(id);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetConfigNames()
|
||||
{
|
||||
return await _configObservationRepository.GetConfigNames();
|
||||
}
|
||||
|
||||
|
||||
public async Task<ObservatitonRetentionResult?> RetentionActions<T>(T obs) where T : BasePatientObservation
|
||||
{
|
||||
var conf = await Get(obs);
|
||||
return conf is { RetentionPolicy: not null } ?
|
||||
new ObservatitonRetentionResult(conf.RetentionPolicy.Value, conf.RetentionPolicyValue) :
|
||||
new ObservatitonRetentionResult(RetentionPolicy.NoDelete, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<StatusEnum.Type> GroupedObservationStatus(GroupedField groupedField,
|
||||
GroupedObservationEnum.Result result,
|
||||
string name, object value, double? min, double? max)
|
||||
{
|
||||
var conf = await Get(name);
|
||||
if (conf == null) return StatusEnum.Type.Ok;
|
||||
PatientObservation? mapObs;
|
||||
if (conf.Grouped != null && groupedField.Group != null &&
|
||||
conf.Grouped.TryGetValue(groupedField.Group, out var grp))
|
||||
{
|
||||
mapObs = await MapConf(new PatientObservation { Name = name, Value = value, Min = min, Max = max }, grp);
|
||||
if (mapObs != null) return mapObs.Status;
|
||||
}
|
||||
|
||||
if (conf.Grouped != null && conf.Grouped.ContainsKey(result.ToString()))
|
||||
{
|
||||
mapObs = await MapConf(new PatientObservation { Name = name, Value = value, Min = min, Max = max },
|
||||
conf.Grouped[result.ToString()]);
|
||||
if (mapObs != null) return mapObs.Status;
|
||||
}
|
||||
|
||||
mapObs = await MapConf(new PatientObservation { Name = name, Value = value, Min = min, Max = max }, conf);
|
||||
if (mapObs != null) return mapObs.Status;
|
||||
|
||||
return StatusEnum.Type.Ok;
|
||||
}
|
||||
|
||||
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
|
||||
{
|
||||
var conf = onlyByName ? await Get(obs, onlyByName) : await Get(obs);
|
||||
if (conf == null)
|
||||
{
|
||||
_logger.LogDebug("Ignore Unknown Observation. {Name} {Code} {CodingSystem}", obs.Name, obs.Code,
|
||||
obs.CodingSystem);
|
||||
return IgnoreUnknownObservation ? null : obs;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(conf.Name))
|
||||
{
|
||||
_logger.LogError("Config Name is null or empty. {conf}", conf);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await MapConf(obs, conf);
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> RemoveConfigItem(ObjectId id)
|
||||
{
|
||||
var item = await _configObservationRepository.FindById(id);
|
||||
if (item == null) return null;
|
||||
|
||||
var deleted = await _configObservationRepository.Delete(id);
|
||||
|
||||
// Invalidar CACHE (colección completa)
|
||||
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
|
||||
|
||||
return deleted;
|
||||
|
||||
}
|
||||
|
||||
public async Task<PatientTreatment?> Map(PatientTreatment treatment)
|
||||
{
|
||||
ConfigObservation? conf = null;
|
||||
|
||||
foreach (var requestGiveCode in treatment.RequestedGiveCodes)
|
||||
conf = string.IsNullOrEmpty(requestGiveCode.CodingSystem) &&
|
||||
string.IsNullOrEmpty(requestGiveCode.Identifier)
|
||||
? await Get(requestGiveCode.Text)
|
||||
: await GetByCodeSysAndCode(requestGiveCode.CodingSystem, requestGiveCode.Identifier);
|
||||
if (conf == null) return _ignoreUnknownTreatment ? null : treatment;
|
||||
|
||||
return conf.Name == null ? null : treatment;
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> Get<T>(T obs, bool onlyByName = false)
|
||||
where T : BasePatientObservation
|
||||
{
|
||||
var items = await GetAllConfigs();
|
||||
if (items.Count == 0) return null;
|
||||
|
||||
if (onlyByName)
|
||||
{
|
||||
var configItem = items.FirstOrDefault(i => i.Name == obs.Name);
|
||||
if (configItem != null) return await Process(configItem);
|
||||
_logger.LogError("Error getting Config observation Item. Observation: {obs}", obs);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
ConfigObservation? item = null;
|
||||
if ((!string.IsNullOrEmpty(obs.Code) && !string.IsNullOrEmpty(obs.CodingSystem)) ||
|
||||
obs.ParentData is { Code: not null, CodingSystem: not null })
|
||||
foreach (var obsConfig in items)
|
||||
{
|
||||
if (obs.Code != null && obs.Code != obsConfig.Code)
|
||||
continue;
|
||||
if (obsConfig.CodingSystem != null && obs.CodingSystem != obsConfig.CodingSystem)
|
||||
continue;
|
||||
if (obsConfig.OriginalName != null && obs.Name != obsConfig.OriginalName)
|
||||
continue;
|
||||
if (obsConfig.ParentCode != null && obs.ParentData?.Code != obsConfig.ParentCode)
|
||||
continue;
|
||||
if (obsConfig.ParentCodingSystem != null &&
|
||||
obs.ParentData?.CodingSystem != obsConfig.ParentCodingSystem)
|
||||
continue;
|
||||
if (obsConfig.ParentName != null && obs.ParentData?.Name != obsConfig.ParentName)
|
||||
continue;
|
||||
|
||||
if (obsConfig.OriginalName == null && obsConfig.Name != null && obs.Name != null &&
|
||||
obs.Name.Contains("Alarm") && obs.Name != obsConfig.Name)
|
||||
continue;
|
||||
|
||||
if (obsConfig.Code == null && obsConfig.CodingSystem == null && obsConfig.ParentCode == null &&
|
||||
obsConfig.ParentCodingSystem == null && obs.Name != null && !obs.Name.Contains("Alarm"))
|
||||
continue;
|
||||
if (obsConfig.Code == null &&
|
||||
obsConfig is { CodingSystem: not null, ParentCode: null, ParentCodingSystem: null } &&
|
||||
obs.Name != null && !obs.Name.Contains("Alarm"))
|
||||
continue;
|
||||
if (obsConfig.Code == null && obsConfig.CodingSystem == null && obsConfig.ParentCode == null &&
|
||||
obsConfig.ParentCodingSystem != null && obs.Name != null && !obs.Name.Contains("Alarm"))
|
||||
continue;
|
||||
if (obsConfig.Code == null &&
|
||||
obsConfig is { CodingSystem: not null, ParentCode: null, ParentCodingSystem: not null } &&
|
||||
obs.Name != null && !obs.Name.Contains("Alarm"))
|
||||
continue;
|
||||
|
||||
item = obsConfig;
|
||||
break;
|
||||
}
|
||||
else item = items.FirstOrDefault(i => i.Name == obs.Name);
|
||||
|
||||
return item != null ? await Process(item) : null;
|
||||
}
|
||||
|
||||
// public async Task<List<ConfigObservation>> GetAlarmWithRecordingConfig(ObjectId patientId)
|
||||
// {
|
||||
// var configObservationId = await GetConfigObservationKeyFromPatientId(patientId);
|
||||
// var configObservation = await GetConfig(configObservationId);
|
||||
// return configObservation?.Items
|
||||
// .Where(i => i is { CodingSystem: "ADAS_ALARM", Alarm.Recording.Enabled: true })
|
||||
// .ToList() ?? [];
|
||||
// }
|
||||
|
||||
public async Task<ConfigObservation?> GetByCodeSysAndCode(string? codingSystem, string? code)
|
||||
{
|
||||
var filteredResult = await _configObservationRepository.GetByCodeSysAndCode(codingSystem, code);
|
||||
if (filteredResult != null) return await Process(filteredResult);
|
||||
_logger.LogWarning("Config observation item not found. CodingSystem: {codingSystem} code: {code} ",
|
||||
codingSystem ?? "null", code ?? "null");
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> Get(string? name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return null;
|
||||
var result = await GetConfigByName(name);
|
||||
if (result == null)
|
||||
{
|
||||
// _logger.LogWarning(
|
||||
// "Config observation item not found. name: {name} configObservationId: {configObservationId} ",
|
||||
// name, name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Process(result);
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> UpdateConfig(ConfigObservation configObservationItem)
|
||||
{
|
||||
// if (!configObservationItem.Id.HasValue)
|
||||
// return await _configObservationRepository.InsertOneAsyncAndReturn(configObservationItem);
|
||||
var configObservation = await _configObservationRepository.FindById(configObservationItem.Id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
|
||||
var updatedConfig = await _configObservationRepository.Update(configObservation);
|
||||
|
||||
// Invalidar CACHE (colección completa)
|
||||
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
|
||||
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, configObservation,
|
||||
updatedConfig!);
|
||||
return updatedConfig;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConfigObservation>?> GetConfigObservationItem(string name)
|
||||
{
|
||||
var configObservationItems = await _configObservationRepository.FindAllByName(name);
|
||||
if (configObservationItems.Count != 0)
|
||||
return configObservationItems;
|
||||
_logger.LogWarning("Cant get config item, config not found, name: {name} ", name);
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem,
|
||||
string? name, string? originalName)
|
||||
{
|
||||
var matchingItem =
|
||||
await _configObservationRepository.GetSingleConfigObservationItem(code, codingSystem, name, originalName);
|
||||
|
||||
if (matchingItem == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundNoMatches);
|
||||
|
||||
return matchingItem;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteSingleConfigObservationItem(ConfigObservation configObservationItem)
|
||||
{
|
||||
var configObservation = await _configObservationRepository.FindById(configObservationItem.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
|
||||
var auxConfigObservation = await _auditService.DeepCopyAsync(configObservation);
|
||||
|
||||
_ = await _configObservationRepository.DeleteAsync(configObservation.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
|
||||
// Invalidar CACHE (colección completa)
|
||||
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
|
||||
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxConfigObservation,
|
||||
configObservation);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConfigObservation>> GetConfigObservationItemsByName(string name)
|
||||
{
|
||||
return await _configObservationRepository.FindAllByName(name);
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> CreateConfig(ConfigObservation configObservation)
|
||||
{
|
||||
|
||||
var existing = await _configObservationRepository.FindById(configObservation.Id);
|
||||
if (existing != null)
|
||||
throw new BadRequestException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
|
||||
await _configObservationRepository.InsertOneAsyncAndReturn(configObservation);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, configObservation);
|
||||
return configObservation;
|
||||
}
|
||||
|
||||
|
||||
public async Task<ConfigObservation?> RemoveConfigItem(string itemName)
|
||||
{
|
||||
var configObservation = await GetConfigByName(itemName) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
var auxConfigObservation = await GetConfigByName(itemName) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxConfigObservation,
|
||||
configObservation);
|
||||
|
||||
var result = await _configObservationRepository.Delete(configObservation.Id!);
|
||||
|
||||
// Invalidar CACHE (colección completa)
|
||||
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> GetConfig(ObjectId configObservationId)
|
||||
{
|
||||
RefreshCachedConfigObservations();
|
||||
|
||||
if (CachedConfigObservations.TryGetValue(configObservationId, out var cached)
|
||||
&& DateTime.Now <= cached.NextRefresh)
|
||||
return cached.ConfigObservation;
|
||||
|
||||
var config = await _configObservationRepository.FindById(configObservationId);
|
||||
cached = new ConfigObservationCached
|
||||
{
|
||||
ConfigObservation = config ?? new ConfigObservation(),
|
||||
NextRefresh = _refreshTimeout.HasValue ? DateTime.Now.AddSeconds(_refreshTimeout.Value) : DateTime.MinValue
|
||||
};
|
||||
|
||||
CachedConfigObservations[configObservationId] = cached;
|
||||
|
||||
return cached.ConfigObservation;
|
||||
}
|
||||
|
||||
public async Task<ConfigObservation?> GetConfigByName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) return null;
|
||||
|
||||
RefreshCachedConfigObservations();
|
||||
|
||||
var cachedItem = CachedConfigObservations.Values
|
||||
.FirstOrDefault(cached =>
|
||||
DateTime.Now <= cached.NextRefresh &&
|
||||
cached.ConfigObservation is { Name: not null } &&
|
||||
cached.ConfigObservation.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (cachedItem != null) return cachedItem.ConfigObservation;
|
||||
var config = await _configObservationRepository.FindByName(name);
|
||||
if (config == null) return null;
|
||||
var newCachedItem = new ConfigObservationCached
|
||||
{
|
||||
ConfigObservation = config,
|
||||
NextRefresh = _refreshTimeout.HasValue
|
||||
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
|
||||
: DateTime.MinValue
|
||||
};
|
||||
|
||||
CachedConfigObservations[config.Id!] = newCachedItem;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private void RefreshCachedConfigObservations()
|
||||
{
|
||||
if (!_refreshTimeout.HasValue) return;
|
||||
|
||||
// Use ConcurrentDictionary's thread-safe features to identify and remove expired keys
|
||||
var keysToRemove = CachedConfigObservations
|
||||
.Where(k => DateTime.Now > k.Value.NextRefresh)
|
||||
.Select(k => k.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var key in keysToRemove) CachedConfigObservations.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
private Task<ConfigObservation> Process(ConfigObservation confItem)
|
||||
{
|
||||
confItem.RetentionPolicy ??= _defaultRetentionPolicy;
|
||||
if (confItem.RetentionPolicy != RetentionPolicy.NoDelete && !confItem.RetentionPolicyValue.HasValue)
|
||||
{
|
||||
confItem.RetentionPolicyValue = _defaultRetentionPolicyValue;
|
||||
if (confItem.RetentionPolicyValue <= 0) confItem.RetentionPolicyValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
confItem.RetentionPolicyValue = null;
|
||||
}
|
||||
|
||||
return Task.FromResult(confItem);
|
||||
}
|
||||
|
||||
private void RefreshCachedConfigObservationKeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_refreshTimeout.HasValue) return;
|
||||
|
||||
// Use ConcurrentDictionary's thread-safe features to identify and remove expired keys
|
||||
var keysToRemove = CachedConfigObservationKeys
|
||||
.Where(k => DateTime.Now > k.Value.NextRefresh)
|
||||
.Select(k => k.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var key in keysToRemove) CachedConfigObservationKeys.TryRemove(key, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error refreshing cached config observation keys. Exception: {ex}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T?> MapConf<T>(T obs, ConfigObservation conf) where T : BasePatientObservation
|
||||
{
|
||||
_logger.LogTrace("Mapping observation: {obs}", obs);
|
||||
|
||||
obs.Name = conf.Name;
|
||||
if (obs is PatientObservation)
|
||||
{
|
||||
var noCalculateStatusWithCodingSystem = _apiSettings.Value.NoCalculateStatusWithCodingSystem;
|
||||
if (noCalculateStatusWithCodingSystem != null &&
|
||||
obs.CodingSystem == noCalculateStatusWithCodingSystem) return obs;
|
||||
if (obs is not PatientObservation pobs)
|
||||
return obs;
|
||||
|
||||
if (conf.MaxAlert != null && (conf.ForceAlert || !pobs.Max.HasValue)) pobs.Max = conf.MaxAlert;
|
||||
|
||||
if (conf.MinAlert.HasValue && (conf.ForceAlert || !pobs.Min.HasValue)) pobs.Min = conf.MinAlert;
|
||||
|
||||
if (conf.MaxWarn.HasValue && (conf.ForceWarn || !pobs.MaxWarn.HasValue)) pobs.MaxWarn = conf.MaxWarn;
|
||||
|
||||
if (conf.MinWarn.HasValue && (conf.ForceWarn || !pobs.MinWarn.HasValue)) pobs.MinWarn = conf.MinWarn;
|
||||
|
||||
if (conf.LevelCondition != null)
|
||||
try
|
||||
{
|
||||
var evalCondition = await CSharpScript.EvaluateAsync(conf.LevelCondition, globals: pobs);
|
||||
if (int.TryParse(evalCondition.ToString(), out var evalInt))
|
||||
pobs.Level = evalInt;
|
||||
else
|
||||
_logger.LogError("Error evaluating condition: {evalCondition} for obs: {pobs}",
|
||||
evalCondition, pobs);
|
||||
}
|
||||
catch (CompilationErrorException e)
|
||||
{
|
||||
_logger.LogError("Error evaluating expression for obs: {obs} error: {diagnostics}", obs,
|
||||
e.Diagnostics);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error evaluating expression for obs: {obs} error: {e}", obs, e);
|
||||
}
|
||||
|
||||
pobs.Status = StatusEnum.Type.Ok;
|
||||
if (pobs.Value.IsNumber())
|
||||
{
|
||||
if (pobs.Min.HasValue && pobs.Min > pobs.Value.ToDouble() &&
|
||||
conf.Alert is null or '<')
|
||||
{
|
||||
pobs.Status = StatusEnum.Type.Alert;
|
||||
pobs.AlertColor = conf.AlertColor;
|
||||
}
|
||||
else if (pobs.Max.HasValue && pobs.Max < pobs.Value.ToDouble() &&
|
||||
conf.Alert is null or '>')
|
||||
{
|
||||
pobs.Status = StatusEnum.Type.Alert;
|
||||
pobs.AlertColor = conf.AlertColor;
|
||||
}
|
||||
else if (pobs.MinWarn.HasValue && pobs.MinWarn > pobs.Value.ToDouble() &&
|
||||
conf.Alert is null or '<')
|
||||
{
|
||||
pobs.Status = StatusEnum.Type.Warning;
|
||||
pobs.WarnColor = conf.WarnColor;
|
||||
}
|
||||
else if (pobs.MaxWarn.HasValue && pobs.MaxWarn < pobs.Value.ToDouble() &&
|
||||
conf.Alert is null or '>')
|
||||
{
|
||||
pobs.Status = StatusEnum.Type.Warning;
|
||||
pobs.WarnColor = conf.WarnColor;
|
||||
}
|
||||
}
|
||||
else if (pobs.Value is string)
|
||||
{
|
||||
if (conf.AlertValues != null && conf.AlertValues.Any() && conf.AlertValues.Contains(pobs.Value))
|
||||
pobs.Status = StatusEnum.Type.Alert;
|
||||
else if (conf.WarningValues != null && conf.WarningValues.Any() &&
|
||||
conf.WarningValues.Contains(pobs.Value)) pobs.Status = StatusEnum.Type.Warning;
|
||||
}
|
||||
|
||||
if (conf.Expires is > 0)
|
||||
{
|
||||
pobs.Expires = conf.Expires;
|
||||
//check if already is expired
|
||||
var expireTime = obs.Time.ToLocalTime().AddSeconds(Convert.ToDouble(conf.Expires));
|
||||
pobs.Expired = DateTime.Now.CompareTo(expireTime) > 0;
|
||||
}
|
||||
|
||||
pobs.ShowOnExpired = conf.ShowOnExpired;
|
||||
|
||||
if (conf.ColorOnExpired != null) pobs.ColorOnExpired = conf.ColorOnExpired;
|
||||
|
||||
if (conf.Persist != null)
|
||||
pobs.Persist = conf.Persist;
|
||||
|
||||
if (conf.UiConfiguration != null && conf.UiConfiguration.Any()) pobs.UiConfiguration = conf.UiConfiguration;
|
||||
|
||||
if (conf.Alarm != null) pobs.Alarm = conf.Alarm;
|
||||
|
||||
if (conf.TimeFromMessageTime)
|
||||
if (pobs.MessageTime.CompareTo(DateTime.MinValue) != 0)
|
||||
pobs.Time = pobs.MessageTime;
|
||||
|
||||
if (pobs.Units == null || conf.ForceUnits) pobs.Units = conf.Units;
|
||||
|
||||
if (conf.CreateObservation != null) pobs.CreateObservation = conf.CreateObservation;
|
||||
|
||||
pobs.CheckObservations = conf.CheckObservations;
|
||||
}
|
||||
|
||||
if (obs is PatientObservationAlarm)
|
||||
{
|
||||
if (obs is not PatientObservationAlarm pobs)
|
||||
return obs;
|
||||
|
||||
if (conf.Expires is > 0)
|
||||
{
|
||||
pobs.Expires = conf.Expires;
|
||||
//check if already is expired
|
||||
var expireTime = obs.Time.ToLocalTime().AddSeconds(Convert.ToDouble(conf.Expires));
|
||||
pobs.Expired = DateTime.Now.CompareTo(expireTime) > 0;
|
||||
}
|
||||
|
||||
if (conf.Persist != null)
|
||||
pobs.Persist = conf.Persist;
|
||||
|
||||
if (conf.Alarm != null) pobs.AlarmConfig = conf.Alarm;
|
||||
|
||||
if (conf.AlertColor != null) pobs.AlertColor = conf.AlertColor;
|
||||
|
||||
if (conf.TimeFromMessageTime)
|
||||
if (pobs.MessageTime.CompareTo(DateTime.MinValue) != 0)
|
||||
pobs.Time = pobs.MessageTime;
|
||||
|
||||
if (pobs.Units == null || conf.ForceUnits) pobs.Units = conf.Units;
|
||||
|
||||
if (conf.CreateObservation != null) pobs.CreateObservation = conf.CreateObservation;
|
||||
|
||||
pobs.CheckObservations = conf.CheckObservations;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Observation mapped: {obs}", obs);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
private class ConfigObservationCached
|
||||
{
|
||||
public DateTime NextRefresh { get; set; }
|
||||
public ConfigObservation? ConfigObservation { get; set; }
|
||||
}
|
||||
|
||||
private class ConfigObservationKeyCached
|
||||
{
|
||||
public DateTime NextRefresh { get; set; }
|
||||
public string? Key { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
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.MongoModels;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ConfigPumpsService(
|
||||
IConfigPumpsRepository configPumpsRepository,
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
ILogger<ConfigPumpsService> logger,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService)
|
||||
: IConfigPumpsService
|
||||
{
|
||||
private static ConfigPumps? _config;
|
||||
private static DateTime _nextRefresh = DateTime.MinValue;
|
||||
|
||||
private readonly bool _configPumpsRequired = apiSettings.Value.ConfigPumpsRequired;
|
||||
private readonly string _key = apiSettings.Value.ConfigPumpsKey ?? "PV1";
|
||||
private readonly int? _refreshTimeout = apiSettings.Value.ConfigObservation?.Refresh;
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation obs)
|
||||
{
|
||||
if (!_configPumpsRequired) return obs;
|
||||
|
||||
var conf = !string.IsNullOrEmpty(obs.AlarmType.ToString())
|
||||
? await Get(obs.AlarmType.ToString() ?? string.Empty)
|
||||
: null;
|
||||
|
||||
if (conf == null) return obs;
|
||||
|
||||
return await MapConf(obs, conf);
|
||||
}
|
||||
|
||||
public async Task<List<ConfigPumps>?> GetAllPumpConfigs()
|
||||
{
|
||||
return await configPumpsRepository.GetAllConfigs();
|
||||
}
|
||||
|
||||
public async Task<ConfigPumps?> GetPumpConfigById(string id)
|
||||
{
|
||||
return await configPumpsRepository.FindById(id);
|
||||
}
|
||||
|
||||
public async Task<List<ConfigPumpItem>?> GetConfigItems(string id)
|
||||
{
|
||||
var result = await configPumpsRepository.FindById(id);
|
||||
return result?.Items;
|
||||
}
|
||||
|
||||
public async Task<ConfigPumps?> UpdatePumpConfig(ConfigPumps pumpConfig)
|
||||
{
|
||||
var oldPumpConfig = configPumpsRepository.FindById(pumpConfig.Id);
|
||||
var newPumpConfig = await configPumpsRepository.UpdateConfig(pumpConfig) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPumpConfig, newPumpConfig);
|
||||
return newPumpConfig;
|
||||
}
|
||||
|
||||
public async Task<ConfigPumps?> InsertPumpConfig(ConfigPumps pumpConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
await configPumpsRepository.InsertOneAsync(pumpConfig);
|
||||
var newPumpConfig = await configPumpsRepository.FindById(pumpConfig.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newPumpConfig);
|
||||
return newPumpConfig;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("ERROR inserting config_pumps: {key}. Exception: {exMessage} ", pumpConfig.Id, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeletePumpConfig(ConfigPumps config)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await configPumpsRepository.DeleteConfig(config);
|
||||
|
||||
if (result)
|
||||
{
|
||||
logger.LogError("ERROR deleting config_pumps: {key}. ", config.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, config, null);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("ERROR deleting config_pumps: {key}. Exception: {exMessage} ", config.Id, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ObservatitonRetentionResult?> RetentionActions(PumpObservation obs)
|
||||
{
|
||||
//TODO sacarlo de la configuración específica de Bombas
|
||||
var conf = await Get(obs);
|
||||
return conf is { RetentionPolicy: not null } ?
|
||||
new ObservatitonRetentionResult(conf.RetentionPolicy.Value, conf.RetentionPolicyValue) :
|
||||
new ObservatitonRetentionResult(RetentionPolicy.NoDelete, null);
|
||||
}
|
||||
|
||||
private static Task<PumpObservation> MapConf(PumpObservation obs, ConfigPumpItem conf)
|
||||
{
|
||||
if (conf.UiConfiguration != null && conf.UiConfiguration.Count != 0)
|
||||
obs.UiConfiguration = conf.UiConfiguration;
|
||||
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
private async Task<ConfigPumpItem?> Get(string alarmType)
|
||||
{
|
||||
if (!Enum.TryParse(alarmType, out PumpEnum.AlarmType alarmTypeParsed))
|
||||
return null;
|
||||
var result = await GetConfig();
|
||||
return result?.Items?.FirstOrDefault(i => i.AlarmType == alarmTypeParsed);
|
||||
}
|
||||
|
||||
private async Task<ConfigPumpItem?> Get(PumpObservation pobs)
|
||||
{
|
||||
var config = await GetConfig();
|
||||
return config?.Items?.FirstOrDefault(i => i.Type == pobs.MessageType);
|
||||
}
|
||||
public async Task<List<ConfigPumpItem>?> Get()
|
||||
{
|
||||
var result = await GetConfig();
|
||||
return result?.Items;
|
||||
}
|
||||
|
||||
|
||||
private async Task<ConfigPumps?> GetConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_config != null && DateTime.Now <= _nextRefresh)
|
||||
return _config;
|
||||
_config = await configPumpsRepository.FindById(_key);
|
||||
_nextRefresh = _refreshTimeout.HasValue
|
||||
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
|
||||
: DateTime.MinValue;
|
||||
|
||||
return _config;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError("ERROR READ config_pumps: {key}. Exception: {exMessage} ", _key, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System.Reflection;
|
||||
using adas_core.Application.Repositories.Interfaces;
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.AppSettings;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class ConfigUnitsService(
|
||||
IConfigUnitsRepository configUnitsRepository,
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
ILogger<ConfigUnitsService> logger)
|
||||
: IConfigUnitsService
|
||||
{
|
||||
private static ConfigUnits? _config;
|
||||
private static DateTime _nextRefresh = DateTime.MinValue;
|
||||
|
||||
private readonly bool _configUnitsRequired = apiSettings.Value.ConfigUnitsRequired;
|
||||
|
||||
private readonly string _key = apiSettings.Value.ConfigUnitsKey ?? "PV1";
|
||||
private readonly int? _refreshTimeout = apiSettings.Value.ConfigObservation?.Refresh;
|
||||
|
||||
public async Task<T> Map<T>(T obs) where T : BasePatientObservation
|
||||
{
|
||||
if (!_configUnitsRequired) return obs;
|
||||
|
||||
var conf = !string.IsNullOrEmpty(obs.Units) ? await Get(obs.Units) : null;
|
||||
|
||||
return conf == null ? obs : MapConf(obs, conf);
|
||||
}
|
||||
|
||||
public async Task<PumpObservation> Map(PumpObservation obs)
|
||||
{
|
||||
if (!_configUnitsRequired) return obs;
|
||||
|
||||
//Para cada propiedad de la observación que sea del tipo PumpValue llama a GetByCodeSysAndCode(PumpValue)
|
||||
await MapPumpValues(obs);
|
||||
|
||||
// var conf = !string.IsNullOrEmpty(obs.Units) ? await Get(obs.Units) : null;
|
||||
// return conf == null ? obs : MapConf(obs, conf);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recorre recursivamente las propiedades del objeto para encontrar y convertir PumpValues.
|
||||
/// </summary>
|
||||
private async Task MapPumpValues(object? targetObject)
|
||||
{
|
||||
if (targetObject == null) return;
|
||||
|
||||
var properties = targetObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var propertyValue = property.GetValue(targetObject);
|
||||
|
||||
if (propertyValue == null) continue;
|
||||
|
||||
|
||||
if (property.PropertyType == typeof(CommonPumpTypes.PumpValue))
|
||||
{
|
||||
var pumpValue = (CommonPumpTypes.PumpValue)propertyValue;
|
||||
|
||||
if (string.IsNullOrEmpty(pumpValue.Units)) continue;
|
||||
|
||||
var conf = await Get(pumpValue.Units);
|
||||
if (conf != null) pumpValue.Units = conf.Value;
|
||||
}
|
||||
// En este caso Syringe es una clase que tienen un pumpValue
|
||||
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
|
||||
{
|
||||
// Llamada recursiva para inspeccionar las propiedades anidadas
|
||||
await MapPumpValues(propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private T MapConf<T>(T obs, ConfigUnitItem conf) where T : BasePatientObservation
|
||||
{
|
||||
if (obs is not PatientObservation pobs) return obs;
|
||||
|
||||
logger.LogDebug("Mapping config Unit obs: {obs} to units: {conf}", obs, conf.Value);
|
||||
pobs.Units = conf.Value;
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
public async Task<ConfigUnitItem?> Get(string code)
|
||||
{
|
||||
var result = await GetConfig();
|
||||
|
||||
return result?.Items?.FirstOrDefault(i => i.Code == code);
|
||||
}
|
||||
|
||||
private async Task<ConfigUnits?> GetConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_config != null && DateTime.Now <= _nextRefresh) return _config;
|
||||
|
||||
_config = await configUnitsRepository.FindById(_key);
|
||||
_nextRefresh = _refreshTimeout.HasValue
|
||||
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
|
||||
: DateTime.MinValue;
|
||||
return _config;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "ERROR READ config_units: {_key}: {ex}", _key, ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models;
|
||||
using adas_core.Domain.Models.Pumps;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class DefaultCalculatedObservations : ICalculatedObservations
|
||||
{
|
||||
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<T?> Map<T>(T obs, bool onlyByName) where T : BasePatientObservation
|
||||
{
|
||||
return Task.FromResult(obs)!;
|
||||
}
|
||||
|
||||
public Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
return Task.FromResult(treatment);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<PatientTreatment?>>(new List<PatientTreatment?>());
|
||||
}
|
||||
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
return Task.FromResult(diagnosis);
|
||||
}
|
||||
|
||||
public Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
return Task.FromResult<PatientObservation?>(newObservation);
|
||||
}
|
||||
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
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.DTO;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class DeviceService : IDeviceService
|
||||
{
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly IObservationService _observationService;
|
||||
private readonly IAlarmService _alarmService;
|
||||
private readonly IConfigObservationService _configObservationService;
|
||||
private readonly IPointOfCareService _pointOfCareService;
|
||||
private readonly ILogger<DeviceService> _logger;
|
||||
|
||||
public DeviceService(
|
||||
IDeviceRepository deviceRepository,
|
||||
IPointOfCareService pointOfCareService,
|
||||
ILogger<DeviceService> logger,
|
||||
IObservationService observationService,
|
||||
IConfigObservationService configObservationService,
|
||||
IAlarmService alarmService)
|
||||
{
|
||||
_deviceRepository = deviceRepository;
|
||||
_pointOfCareService = pointOfCareService;
|
||||
_logger = logger;
|
||||
_observationService = observationService;
|
||||
_configObservationService = configObservationService;
|
||||
_alarmService = alarmService;
|
||||
}
|
||||
|
||||
public Device ToEntity(DeviceDto dto)
|
||||
{
|
||||
return new Device()
|
||||
{
|
||||
DeviceType = dto.DeviceType,
|
||||
MacAddr = dto.MacAddr,
|
||||
SerialNumber = dto.SerialNumber,
|
||||
Name = dto.Name,
|
||||
Battery = dto.Battery,
|
||||
Color = dto.Color,
|
||||
Connected = dto.Connected,
|
||||
Ready = dto.Ready,
|
||||
Uuid = dto.Uuid,
|
||||
Key = dto.Key,
|
||||
CreatedAt = dto.CreatedAt,
|
||||
UpdatedAt = dto.UpdatedAt,
|
||||
PointOfCareIds = dto.PointOfCareIds ?? new List<ObjectId>(),
|
||||
Settings = dto.Settings ?? new DeviceSettings()
|
||||
};
|
||||
}
|
||||
public async Task<Device?> Create(DeviceDto deviceDto)
|
||||
{
|
||||
var device = ToEntity(deviceDto);
|
||||
device.CreatedAt = DateTime.UtcNow;
|
||||
device.UpdatedAt = DateTime.UtcNow;
|
||||
await _deviceRepository.InsertOneAsync(device);
|
||||
return device;
|
||||
}
|
||||
|
||||
public async Task<bool> Delete(ObjectId objectId)
|
||||
{
|
||||
return await _deviceRepository.DeleteAsync(objectId) != null;
|
||||
}
|
||||
|
||||
public async Task<Device?> Update(DeviceDto deviceDto)
|
||||
{
|
||||
var device = ToEntity(deviceDto);
|
||||
device.UpdatedAt = DateTime.UtcNow;
|
||||
await _deviceRepository.UpdateOneAsync(device.Id, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
public async Task<Device?> ReceiveEvent(DeviceDto deviceDto)
|
||||
{
|
||||
Device? deviceExist = null;
|
||||
if (!string.IsNullOrEmpty(deviceDto.MacAddr))
|
||||
{
|
||||
deviceExist = await _deviceRepository.FindByMacAddr(deviceDto.MacAddr);
|
||||
}
|
||||
|
||||
if (deviceExist == null && deviceDto.SerialNumber != null)
|
||||
{
|
||||
deviceExist = await _deviceRepository.FindBySerialNumber(deviceDto.SerialNumber);
|
||||
}
|
||||
|
||||
if (deviceExist == null && deviceDto.Uuid != null)
|
||||
{
|
||||
deviceExist = await _deviceRepository.FindByUuid(deviceDto.Uuid);
|
||||
}
|
||||
|
||||
if (deviceExist == null && deviceDto.Key != null)
|
||||
{
|
||||
deviceExist = await _deviceRepository.FindByKey(deviceDto.Key);
|
||||
}
|
||||
|
||||
if (deviceExist == null)
|
||||
{
|
||||
deviceExist = ToEntity(deviceDto);
|
||||
deviceExist.CreatedAt = DateTime.UtcNow;
|
||||
deviceExist.UpdatedAt = DateTime.UtcNow;
|
||||
await _deviceRepository.InsertOneAsync(deviceExist);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _deviceRepository.UpdateDeviceStats(deviceExist.Id, deviceDto);
|
||||
}
|
||||
|
||||
switch (deviceDto.DeviceType)
|
||||
{
|
||||
case DeviceType.Unknown:
|
||||
break;
|
||||
case DeviceType.Button:
|
||||
await ManageDeviceButton(deviceExist, deviceDto);
|
||||
break;
|
||||
}
|
||||
|
||||
return deviceExist;
|
||||
}
|
||||
|
||||
private async Task ManageDeviceButton(Device deviceExist, DeviceDto deviceDto)
|
||||
{
|
||||
if (deviceExist.Settings?.Action?.Type != null && deviceDto.Event?.ClickType != null)
|
||||
{
|
||||
switch (deviceExist.Settings.Action.Type)
|
||||
{
|
||||
case DeviceActionType.SendObs:
|
||||
await SendObservationOnAction(deviceExist.Settings.Action, deviceDto.Event.ClickType.Value, deviceExist.PointOfCareIds);
|
||||
break;
|
||||
case DeviceActionType.SendAlarm:
|
||||
await SendAlarmOnAction(deviceExist.Settings.Action, deviceDto.Event.ClickType.Value, deviceExist.PointOfCareIds);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendAlarmOnAction(
|
||||
DeviceAction settingsAction,
|
||||
ClickType eventClickType,
|
||||
List<ObjectId> deviceExistPointOfCareIds)
|
||||
{
|
||||
var configObs = await _configObservationService.GetConfigById(settingsAction.ConfigObservationId);
|
||||
if(configObs == null) return;
|
||||
var obsData = new ObservationData
|
||||
{
|
||||
Code = configObs.Code,
|
||||
CodingSystem = configObs.CodingSystem,
|
||||
Time = DateTime.UtcNow,
|
||||
Text = configObs.Name,
|
||||
};
|
||||
var obs = new PatientObservationAlarm
|
||||
{
|
||||
InactivationState = new InactivationState{ Visual = AlarmEnum.AudioVideoState.Enabled, Audio = AlarmEnum.AudioVideoState.Enabled},
|
||||
MessageTime = DateTime.UtcNow,
|
||||
Persist = true,
|
||||
Code = obsData.Code,
|
||||
CodingSystem = obsData.CodingSystem,
|
||||
Name = configObs.Name,
|
||||
Time = DateTime.UtcNow
|
||||
};
|
||||
foreach (var pocId in deviceExistPointOfCareIds)
|
||||
{
|
||||
var data = await _pointOfCareService.GetInfo(pocId, null, true);
|
||||
if (data != null && data.Patient?.Id != null)
|
||||
{
|
||||
obs.PatientId = data.Patient.Id;
|
||||
obs.Patient = data.Patient;
|
||||
switch (eventClickType)
|
||||
{
|
||||
case ClickType.SingleClick:
|
||||
if(settingsAction.ValueOnSingleClick == null) return;
|
||||
obs.Value = settingsAction.ValueOnSingleClick;
|
||||
break;
|
||||
case ClickType.DoubleClick:
|
||||
if(settingsAction.ValueOnDoubleClick == null) return;
|
||||
obs.Value = settingsAction.ValueOnDoubleClick;
|
||||
break;
|
||||
case ClickType.Hold:
|
||||
if(settingsAction.ValueOnHoldClick == null) return;
|
||||
obs.Value = settingsAction.ValueOnHoldClick;
|
||||
break;
|
||||
}
|
||||
// Process Obs on service
|
||||
await _alarmService.ProcessAlarmObservations(new List<PatientObservationAlarm>{obs},new List<PatientObservation>(){new (){Name = configObs.Name, Value = obs.Value, Code = obsData.Code, CodingSystem = obsData.CodingSystem}}, data.Patient, DateTime.UtcNow, obsData);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendObservationOnAction(
|
||||
DeviceAction settingsAction,
|
||||
ClickType eventClickType,
|
||||
List<ObjectId> deviceExistPointOfCareIds)
|
||||
{
|
||||
var configObs = await _configObservationService.GetConfigById(settingsAction.ConfigObservationId);
|
||||
if(configObs == null) return;
|
||||
var obsData = new ObservationData
|
||||
{
|
||||
Code = configObs.Code,
|
||||
CodingSystem = configObs.CodingSystem,
|
||||
Time = DateTime.UtcNow,
|
||||
Text = configObs.Name,
|
||||
};
|
||||
var obs = new PatientObservation
|
||||
{
|
||||
MessageTime = DateTime.UtcNow,
|
||||
Persist = true,
|
||||
Code = obsData.Code,
|
||||
CodingSystem = obsData.CodingSystem,
|
||||
Name = configObs.Name,
|
||||
Time = DateTime.UtcNow
|
||||
};
|
||||
foreach (var pocId in deviceExistPointOfCareIds)
|
||||
{
|
||||
var data = await _pointOfCareService.GetInfo(pocId, null, true);
|
||||
if (data != null && data.Patient?.Id != null)
|
||||
{
|
||||
obs.PatientId = data.Patient.Id;
|
||||
obs.Patient = data.Patient;
|
||||
switch (eventClickType)
|
||||
{
|
||||
case ClickType.SingleClick:
|
||||
if(settingsAction.ValueOnSingleClick == null) return;
|
||||
obs.Value = settingsAction.ValueOnSingleClick;
|
||||
break;
|
||||
case ClickType.DoubleClick:
|
||||
if(settingsAction.ValueOnDoubleClick == null) return;
|
||||
obs.Value = settingsAction.ValueOnDoubleClick;
|
||||
break;
|
||||
case ClickType.Hold:
|
||||
if(settingsAction.ValueOnHoldClick == null) return;
|
||||
obs.Value = settingsAction.ValueOnHoldClick;
|
||||
break;
|
||||
}
|
||||
// Process Obs on service
|
||||
_observationService.ProcessObservations(new List<PatientObservation>{obs}, data.Patient, DateTime.UtcNow, obsData);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
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.MongoModels;
|
||||
using adas_core.Domain.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class DiagnosisService : IDiagnosisService
|
||||
{
|
||||
private readonly ILocalAuditService _auditService;
|
||||
private readonly Lazy<ICalculatedObservationsService> _calculatedObservations;
|
||||
private readonly IClientMessageService _clientMessageService;
|
||||
private readonly IDiagnosisArchiveRepository _diagnosisArchiveRepository;
|
||||
|
||||
private readonly List<string> _diagnosisCode = [];
|
||||
private readonly IDiagnosisRepository _diagnosisRepository;
|
||||
|
||||
private readonly string _diagnosisSystem;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<DiagnosisService> _logger;
|
||||
private readonly Lazy<IPatientService> _patientService;
|
||||
private readonly ISubscribersService _subscribersService;
|
||||
private readonly IUnitService _unitService;
|
||||
|
||||
|
||||
public DiagnosisService(
|
||||
Lazy<IPatientService> patientService,
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
IDiagnosisRepository diagnosisRepository,
|
||||
IDiagnosisArchiveRepository diagnosisArchiveRepository,
|
||||
ILogger<DiagnosisService> logger,
|
||||
IClientMessageService clientMessageService,
|
||||
ISubscribersService subscribersService,
|
||||
Lazy<ICalculatedObservationsService> calculatedObservations,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
IUnitService unitService)
|
||||
{
|
||||
_patientService = patientService;
|
||||
_diagnosisRepository = diagnosisRepository;
|
||||
_diagnosisArchiveRepository = diagnosisArchiveRepository;
|
||||
_logger = logger;
|
||||
_clientMessageService = clientMessageService;
|
||||
_subscribersService = subscribersService;
|
||||
_calculatedObservations = calculatedObservations;
|
||||
_unitService = unitService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_auditService = auditService;
|
||||
|
||||
|
||||
_diagnosisSystem = apiSettings.Value.DiagnosisSystem ?? "CUSTOM";
|
||||
|
||||
if (apiSettings.Value.DiagnosisCode.Any())
|
||||
_diagnosisCode = apiSettings.Value.DiagnosisCode;
|
||||
}
|
||||
|
||||
// private async Task SendBroadcast(PatientDiagnosis diagnosis)
|
||||
// {
|
||||
// var patient = await _patientService.Value.FindById(diagnosis.PatientId);
|
||||
// if (patient == null) return;
|
||||
//
|
||||
//
|
||||
// var subscribers = _subscribersService.GetSubscribers().Where(s =>
|
||||
// (s.SubscriptionType == SubscriptionType.Box && s.Box == patient.Bed &&
|
||||
// s.Section == patient.UnitString) ||
|
||||
// (s.SubscriptionType == SubscriptionType.Section && s.Section == patient.UnitString)).ToList();
|
||||
// subscribers.ForEach(Action);
|
||||
// return;
|
||||
//
|
||||
// async void Action(WsSubscriber subscriber) =>
|
||||
// await _clientMessageService.SendAsync(subscriber.Id, OperationType.Diagnosis, diagnosis);
|
||||
// }
|
||||
|
||||
public async Task Archive(Patient patient)
|
||||
{
|
||||
await ArchiveByPatientId(patient.Id);
|
||||
}
|
||||
|
||||
public async Task ArchiveByPatientId(ObjectId id)
|
||||
{
|
||||
_logger.LogDebug("Archive Diagnoses by Patient Id {id}", id);
|
||||
using (var cursor = await FindByPatientIdAsync(id))
|
||||
{
|
||||
while (await cursor.MoveNextAsync())
|
||||
foreach (var current in cursor.Current)
|
||||
await _diagnosisArchiveRepository.InsertOneAsync(current);
|
||||
}
|
||||
|
||||
await DeleteByPatientId(id);
|
||||
}
|
||||
|
||||
public async Task DeleteByPatientId(ObjectId id)
|
||||
{
|
||||
_logger.LogDebug("Delete Diagnoses by Patient Id {id}", id);
|
||||
var oldPatient = await _diagnosisRepository.GetByPatient(id);
|
||||
await _diagnosisRepository.DeleteByPatientId(id);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, null);
|
||||
}
|
||||
|
||||
public async Task<List<PatientDiagnosis>> GetByPatientId(ObjectId patientId)
|
||||
{
|
||||
var diagnosis = await _diagnosisRepository.GetByPatient(patientId);
|
||||
|
||||
return diagnosis;
|
||||
}
|
||||
|
||||
|
||||
public Task SaveRequest(ApiRequest apiRequest)
|
||||
{
|
||||
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest, null); }));
|
||||
}
|
||||
|
||||
public Task SaveRequestAsync(ApiRequest apiRequest)
|
||||
{
|
||||
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest, null); }));
|
||||
}
|
||||
|
||||
public async Task ProcessDiagnosisObservation(ApiRequest apiRequest, Patient patient)
|
||||
{
|
||||
_logger.LogDebug("INSERT DiagnosisObservation from obsservation");
|
||||
|
||||
var time = apiRequest.ObservationData?.Time;
|
||||
if (time == null)
|
||||
_logger.LogWarning("Processing diagnosis without time. Set Datetime now {DateTimeNow}. ", DateTime.Now);
|
||||
|
||||
var obs = new PatientDiagnosis
|
||||
{
|
||||
CodingSystem = _diagnosisSystem,
|
||||
Time = time ?? DateTime.Now,
|
||||
PatientId = patient.Id,
|
||||
MessageTime = apiRequest.MessageTime
|
||||
};
|
||||
|
||||
if (apiRequest.Observations == null)
|
||||
{
|
||||
_logger.LogError("ApiRequest Observations null. ");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i <= apiRequest.Observations.Count - 1; i++)
|
||||
{
|
||||
var value = apiRequest.Observations[i].Value;
|
||||
|
||||
var strValue = value.ToString() ?? "null";
|
||||
|
||||
switch (apiRequest.Observations[i].Code)
|
||||
{
|
||||
case "272099008":
|
||||
obs.Description = strValue;
|
||||
break;
|
||||
|
||||
case "1000000013":
|
||||
obs.Label = strValue;
|
||||
break;
|
||||
|
||||
case "1000000014":
|
||||
obs.Code = strValue;
|
||||
break;
|
||||
|
||||
case "394731006":
|
||||
obs.State = strValue;
|
||||
break;
|
||||
|
||||
case "272125009":
|
||||
obs.Category = strValue;
|
||||
break;
|
||||
|
||||
case "398201009":
|
||||
if (DateTime.TryParse(apiRequest.Observations[i].Value.ToString(), out var startTime))
|
||||
obs.StartTime = startTime;
|
||||
break;
|
||||
|
||||
case "397898000":
|
||||
if (DateTime.TryParse(apiRequest.Observations[i].Value.ToString(), out var endTime))
|
||||
obs.EndTime = endTime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await InsertDiagnosis(obs);
|
||||
}
|
||||
|
||||
public async Task SaveRequest(ApiRequest apiRequest, Patient? patient)
|
||||
{
|
||||
if (patient == null)
|
||||
{
|
||||
_logger.LogDebug("message:ApiRequest Diagnosis");
|
||||
if (string.IsNullOrEmpty(apiRequest.PatientNumber) &&
|
||||
string.IsNullOrEmpty(apiRequest.Location?.UnitName))
|
||||
{
|
||||
_logger.LogDebug("person and PointOfCare are nulls");
|
||||
throw new ApiRequestException("person and PointOfCare are nulls");
|
||||
}
|
||||
|
||||
_logger.LogDebug("patientNumber: {apiRequestpatientNumber} location: {apiRequestlocation}",
|
||||
apiRequest.PatientNumber, apiRequest.Location);
|
||||
_logger.LogDebug("RequestType: {apiRequesttype}", apiRequest.Type);
|
||||
|
||||
patient = await _patientService.Value.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 Diagnosis")) return;
|
||||
|
||||
switch (apiRequest.Type)
|
||||
{
|
||||
//* ORU_R01 - Unsolicited transmission of an observation message
|
||||
//* ORU_R40 - Unsolicited transmission of an alert observation message
|
||||
|
||||
case "ORU_R01":
|
||||
case "ORU_R40":
|
||||
|
||||
// OBSERVATIONS
|
||||
if (apiRequest.Observation != null && apiRequest.Observations?.FirstOrDefault() == null)
|
||||
apiRequest.Observations = [apiRequest.Observation];
|
||||
|
||||
if (apiRequest.Observations != null)
|
||||
{
|
||||
var obrcode = apiRequest.ObservationData?.Code;
|
||||
|
||||
if (apiRequest.ObservationData?.Value != null)
|
||||
apiRequest.Observations.Add(new PatientObservation
|
||||
{ Value = apiRequest.ObservationData.Value });
|
||||
|
||||
if (obrcode != null && _diagnosisCode.Contains(obrcode))
|
||||
_ = ProcessDiagnosisObservation(apiRequest, patient);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
_logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Diagnosis",
|
||||
apiRequest.Type);
|
||||
throw new ApiRequestException("ApiRequest type " + apiRequest.Type +
|
||||
" is not valid for Diagnosis");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessDiagnosis(List<PatientDiagnosis> diagnosis, Patient patient, DateTime messageTime)
|
||||
{
|
||||
_logger.LogDebug("INSERT DiagnosisObservation from diagnosis");
|
||||
|
||||
foreach (var d in diagnosis)
|
||||
{
|
||||
d.PatientId = patient.Id;
|
||||
d.Time = messageTime;
|
||||
await InsertDiagnosis(d);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
|
||||
{
|
||||
await _diagnosisRepository.UpdateManyObjectId(nameId, id, oldId);
|
||||
}
|
||||
|
||||
public async Task<List<PatientDiagnosis>> GetByPatient(ObjectId id)
|
||||
{
|
||||
return await _diagnosisRepository.GetByPatient(id);
|
||||
}
|
||||
|
||||
public async Task Insert(PatientDiagnosis diagnosis)
|
||||
{
|
||||
_logger.LogDebug("Insert {diagnosis}", diagnosis);
|
||||
var diag = await MapDiagnosis(diagnosis);
|
||||
if (diag != null)
|
||||
{
|
||||
await _diagnosisRepository.InsertOneAsync(diag);
|
||||
await SendBroadcast(diag);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<PatientDiagnosis?> MapDiagnosis(PatientDiagnosis diagnosis)
|
||||
{
|
||||
var diagnosis2 = await _calculatedObservations.Value.Map(diagnosis);
|
||||
if (diagnosis2 == null) _logger.LogDebug("Mapping {diagnosis}: Ignored", diagnosis2);
|
||||
|
||||
return diagnosis2;
|
||||
}
|
||||
|
||||
private async Task SendBroadcast(PatientDiagnosis diagnosis)
|
||||
{
|
||||
var patient = await _patientService.Value.FindById(diagnosis.PatientId);
|
||||
if (patient == null) return;
|
||||
|
||||
var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
|
||||
!s.Locations.IsNullOrEmpty() && s.Locations.Any(c =>
|
||||
c.UnitName == patient.Location.UnitName &&
|
||||
c.Bed == patient.Location.Bed &&
|
||||
c.Room == patient.Location.Room
|
||||
)).ToList();
|
||||
|
||||
displaySubscribers.ForEach(Action);
|
||||
return;
|
||||
|
||||
void Action(WsSubscriber subscriber)
|
||||
{
|
||||
_clientMessageService.SendAsync(subscriber.Id, OperationType.Diagnosis, diagnosis);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId)
|
||||
{
|
||||
return _diagnosisRepository.FindByPatientIdAsync(patientId);
|
||||
}
|
||||
|
||||
public async Task InsertDiagnosis(PatientDiagnosis diagnosis)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Insert {diagnosis}", diagnosis);
|
||||
var diag = await MapDiagnosis(diagnosis);
|
||||
if (diag == null)
|
||||
{
|
||||
_logger.LogError("Mepped diagnosis is null. Diagnosis: {diagnosis}", diagnosis);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dgdb = await _diagnosisRepository.FindByPatientIdAndCode(diag.PatientId, diag.Code,
|
||||
diag.CodingSystem);
|
||||
|
||||
if (dgdb != null)
|
||||
{
|
||||
var auxDgdb = dgdb;
|
||||
diag.Id = dgdb.Id;
|
||||
diag.Time = dgdb.Time;
|
||||
diag.UpdateDate = diagnosis.Time;
|
||||
await _diagnosisRepository.UpdateOneAsync(diag.Id, diag);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxDgdb, dgdb);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _diagnosisRepository.InsertOneAsync(diagnosis);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, diagnosis);
|
||||
}
|
||||
|
||||
_ = SendBroadcast(diagnosis);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error inserting diagnosis. Diagnosis: {diagnosis}. Exception:{ex}", diagnosis, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
using System.Reflection;
|
||||
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.DTO;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
using adas_core.Domain.Models.MongoModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class DischargeService : IDischargeService
|
||||
{
|
||||
private readonly ILocalAuditService _auditService;
|
||||
private readonly IClientMessageService _clientMessageService;
|
||||
private readonly IDischargeRepository _dischargeRepository;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly ILogger<DischargeService> _logger;
|
||||
private readonly IMasterListServiceFactory _masterListServiceFactory;
|
||||
private readonly Lazy<IPatientService> _patientServiceLazy;
|
||||
private readonly IPointOfCareService _pointOfCareService;
|
||||
private readonly ISubscribersService _subscribersService;
|
||||
private readonly IUnitService _unitService;
|
||||
|
||||
public DischargeService(ILogger<DischargeService> logger,
|
||||
ISubscribersService subscribersService,
|
||||
IDischargeRepository dischargeRepository,
|
||||
Lazy<IPatientService> patientServiceLazy,
|
||||
IClientMessageService clientMessageService,
|
||||
IPointOfCareService pointOfCareService,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
IUnitService unitService,
|
||||
IMasterListServiceFactory masterListServiceFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_subscribersService = subscribersService;
|
||||
_dischargeRepository = dischargeRepository;
|
||||
_patientServiceLazy = patientServiceLazy;
|
||||
_clientMessageService = clientMessageService;
|
||||
_pointOfCareService = pointOfCareService;
|
||||
_pointOfCareService = pointOfCareService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_auditService = auditService;
|
||||
_unitService = unitService;
|
||||
_masterListServiceFactory = masterListServiceFactory;
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteDischargeAsync(Discharge discharge)
|
||||
{
|
||||
//var patient = await _patientServiceLazy.Value.FindById(discharge.PatientId);
|
||||
//if (!discharge.MedicalDischarge.HasValue || !discharge.AdminDischarge.HasValue
|
||||
// // || DischargeStatusType.NoAltable.ToString().Equals(patient?.DischargeStatus?.OptionType)
|
||||
// )
|
||||
//{
|
||||
// _logger.LogError("The patient cannot be discharged");
|
||||
// return;
|
||||
//}
|
||||
await DeleteDischargeByIdAsync(discharge.Id);
|
||||
}
|
||||
|
||||
public async Task DeleteDischargeByIdAsync(ObjectId dischargeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dischargeAux = await _dischargeRepository.FindById(dischargeId);
|
||||
if (dischargeAux == null)
|
||||
{
|
||||
_logger.LogError("Error Discharge not found, id: {discharge} ", dischargeId);
|
||||
return;
|
||||
}
|
||||
|
||||
await _dischargeRepository.Delete(dischargeId);
|
||||
|
||||
_logger.LogInformation("Discharge id: {dischargeId} DELETED ", dischargeId);
|
||||
|
||||
//var patient = await _patientServiceLazy.Value.FindById(dischargeAux.PatientId);
|
||||
// if (patient != null)
|
||||
// await _patientServiceLazy.Value.ArchivePatient(patient);
|
||||
// else
|
||||
// _logger.LogError("Patient not found on Discharge id: {discharge} ", dischargeId);
|
||||
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, dischargeAux, null);
|
||||
SendDischargeBroadcast(dischargeAux, OperationType.DeleteDischarge);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception deleting discharge id:{admission} . Exception: {ex}", dischargeId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> CountDischargesByUnitId(ObjectId unitId)
|
||||
{
|
||||
return await _dischargeRepository.CountByUnitId(unitId);
|
||||
}
|
||||
|
||||
public async Task<Discharge?> GetDischargeByIdAsync(ObjectId dischargeId)
|
||||
{
|
||||
var result = await _dischargeRepository.FindById(dischargeId) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
if (result.PointOfCareId == null)
|
||||
return result;
|
||||
|
||||
var poc = await _pointOfCareService.GetInfo(result.PointOfCareId.Value, null,false);
|
||||
result.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Discharge>> GetDischargesAsync()
|
||||
{
|
||||
return await _dischargeRepository.FindAll();
|
||||
}
|
||||
|
||||
public async Task<Discharge?> GetDischargeByPatientId(ObjectId patientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discharge = await _dischargeRepository.GetByPatientId(patientId);
|
||||
if (discharge == null)
|
||||
_logger.LogError("Discharge not found by patient Id {id}", patientId);
|
||||
if (discharge is { PointOfCareId: not null })
|
||||
{
|
||||
var poc = await _pointOfCareService.GetInfo(discharge.PointOfCareId.Value, null,false);
|
||||
discharge.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
|
||||
}
|
||||
|
||||
return discharge;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception getting discharge by patient id: {id} . Exception: {ex}", patientId, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Discharge?> InsertDischarge(Discharge discharge)
|
||||
{
|
||||
await _dischargeRepository.InsertOneAsync(discharge);
|
||||
|
||||
var dischargeAux = await _dischargeRepository.FindById(discharge.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
_logger.LogInformation("Discharge: {discharge} INSERTED", discharge);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, dischargeAux);
|
||||
SendDischargeBroadcast(discharge, OperationType.NewDischarge);
|
||||
return dischargeAux;
|
||||
}
|
||||
|
||||
public async Task<Discharge?> GetDischargeByLocation(PatientLocation location)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _dischargeRepository.GetDischargeByLocation(location);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError($"Unable to get discharge by location on service Exception: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Discharge?> GetDischargeByPointOfCareId(ObjectId poc)
|
||||
{
|
||||
try
|
||||
{
|
||||
var discharge = await _dischargeRepository.GetDischargeByPointOfCareId(poc);
|
||||
if (discharge == null)
|
||||
_logger.LogInformation("Discharge not found by PointOfCareId {id}", poc);
|
||||
if (discharge is { PointOfCareId: not null })
|
||||
{
|
||||
var pocInfo = await _pointOfCareService.GetInfo(discharge.PointOfCareId.Value, null,false);
|
||||
discharge.PatientLocation = new PatientLocation(pocInfo?.UnitName, pocInfo?.Bed, pocInfo?.Room);
|
||||
}
|
||||
|
||||
return discharge;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError($"Unable to get discharge by location on service Exception: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Discharge?> GetDischargeByPointOfCareIdWithLocale(ObjectId location, LocaleEnum dataLocale)
|
||||
{
|
||||
var discharge = await GetDischargeByPointOfCareId(location);
|
||||
if (discharge == null) return null;
|
||||
var unit = await _unitService.FindById(discharge.UnitId);
|
||||
if (unit == null) return discharge;
|
||||
var dischargeWithLocale = await GetDischargeWithLocale(unit, discharge, dataLocale);
|
||||
return dischargeWithLocale;
|
||||
}
|
||||
|
||||
public async Task UpdateDischargeAsync(Discharge discharge)
|
||||
{
|
||||
var oldDischarge = await GetDischargeByIdAsync(discharge.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
await _dischargeRepository.Update(discharge);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldDischarge, discharge);
|
||||
SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
|
||||
_logger.LogInformation("Discharge: {discharge} UPDATED", discharge);
|
||||
}
|
||||
|
||||
|
||||
public async Task SaveRequest(ApiRequest apiRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (apiRequest.Discharge?.Patient == null) return;
|
||||
|
||||
var patientId = apiRequest.Discharge.Patient.Id;
|
||||
|
||||
var patient = await _patientServiceLazy.Value.FindById(patientId);
|
||||
if (patient == null)
|
||||
{
|
||||
_logger.LogError("Error discharging patient id: {id} NOT FOUND", patientId);
|
||||
return;
|
||||
}
|
||||
|
||||
await _patientServiceLazy.Value.Update(apiRequest.Discharge.Patient);
|
||||
|
||||
switch (apiRequest.Type)
|
||||
{
|
||||
case "NewDischarge":
|
||||
{
|
||||
//TODO: ver qué tipos llegan
|
||||
if (!"Altable".Equals(apiRequest.Discharge.Patient.DischargeStatus?.OptionType))
|
||||
{
|
||||
_logger.LogError("Error discharging. Patient not altable: {patient}", patient);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await _dischargeRepository.InsertOneAsync(apiRequest.Discharge);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null,
|
||||
apiRequest.Discharge);
|
||||
SendDischargeBroadcast(apiRequest.Discharge, OperationType.NewDischarge);
|
||||
|
||||
break;
|
||||
}
|
||||
case "UpdateDischarge":
|
||||
{
|
||||
await GetDischargeByIdAsync(apiRequest.Discharge.Id);
|
||||
await UpdateDischargeAsync(apiRequest.Discharge);
|
||||
break;
|
||||
}
|
||||
case "DeleteDischarge":
|
||||
{
|
||||
if (StatusEnum.Discharge.NoAltable.ToString().Equals(patient.DischargeStatus?.OptionType))
|
||||
{
|
||||
_logger.LogError("Error deleting discharge. Patient altable: {patient}", patient);
|
||||
return;
|
||||
}
|
||||
|
||||
await DeleteDischargeAsync(apiRequest.Discharge);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception processing discharge api request {discharge} . Exception: {ex}",
|
||||
apiRequest.Discharge, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SaveRequestAsync(ApiRequest apiRequest)
|
||||
{
|
||||
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
|
||||
}
|
||||
|
||||
// Revisar
|
||||
public async void SendDischargeBroadcast(Discharge discharge, OperationType operation)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (discharge.PointOfCareId == null)
|
||||
{
|
||||
_logger.LogError("Error sending discharge broadcast. Unit name is null or empty {discharge} .",
|
||||
discharge);
|
||||
return;
|
||||
}
|
||||
|
||||
var subscribersGroup = _subscribersService.GetSubscribers()
|
||||
.Where(s => s.LocationIds.Any(c => c == discharge.PointOfCareId)).GroupBy(h => h.Locale)
|
||||
.ToList();
|
||||
|
||||
var unit = await _unitService.FindById(discharge.UnitId);
|
||||
foreach (var group in subscribersGroup)
|
||||
{
|
||||
var locale = group.Key ?? LocaleEnum.Default;
|
||||
IEnumerable<WsSubscriber> subscribers = group;
|
||||
|
||||
foreach (var subscriber in subscribers)
|
||||
{
|
||||
var dischargeWithLocale = await GetDischargeWithLocale(unit, discharge, locale);
|
||||
_ = _clientMessageService.SendAsync(subscriber.Id, operation, dischargeWithLocale);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception sending discharge broadcast. Operation type: {op}. Exception: {ex}",
|
||||
operation.ToString(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
|
||||
string typeName)
|
||||
{
|
||||
var unitIds = unitList.Select(x => x.Id).ToList();
|
||||
await _dischargeRepository.GetDischargesByUnitIds(unitIds);
|
||||
var dischargeUpdatedList = await _dischargeRepository.UpdateMasterListOption(unitIds, opt, typeName);
|
||||
var updatedList = dischargeUpdatedList as Discharge[] ?? dischargeUpdatedList.ToArray();
|
||||
foreach (var discharge in updatedList)
|
||||
{
|
||||
var oldDischarge = updatedList.FirstOrDefault(dis => dis.Id == discharge.Id);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldDischarge!, discharge);
|
||||
SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName)
|
||||
{
|
||||
var unitIds = unitList.Select(x => x.Id).ToList();
|
||||
var patientUpdatedList = await _dischargeRepository.DeleteMasterListOption(unitIds, opt, typeName);
|
||||
foreach (var discharge in patientUpdatedList)
|
||||
{
|
||||
var dischargeUpdated = await GetDischargeByIdAsync(discharge.Id);
|
||||
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, discharge, null);
|
||||
if (dischargeUpdated != null)
|
||||
SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDischargesByUnitId(ObjectId unitId)
|
||||
{
|
||||
await _dischargeRepository.DeleteByUnitId(unitId);
|
||||
}
|
||||
|
||||
private async Task<Discharge> GetDischargeWithLocale(Unit? unit, Discharge discharge, LocaleEnum locale)
|
||||
{
|
||||
if (unit == null)
|
||||
return discharge;
|
||||
|
||||
if (locale == LocaleEnum.Default)
|
||||
return discharge;
|
||||
|
||||
// Campos del discharge que deben traducirse
|
||||
var listMap = new List<(string field, ObjectId? listId, MasterListType type)>
|
||||
{
|
||||
("serviceOption", unit.ServiceListId, MasterListType.ServiceList),
|
||||
("destinationOption", unit.DestinationListId, MasterListType.DestinationList)
|
||||
};
|
||||
|
||||
foreach (var (field, listId, masterListType) in listMap)
|
||||
{
|
||||
if (listId == null)
|
||||
continue;
|
||||
|
||||
// Obtener propiedad desde DISCHARGE, no Patient
|
||||
var prop = typeof(Discharge).GetProperty(
|
||||
field,
|
||||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||
|
||||
if (prop == null)
|
||||
continue;
|
||||
|
||||
var propValue = prop.GetValue(discharge);
|
||||
if (propValue == null)
|
||||
continue;
|
||||
|
||||
// Cargar master list traducida según locale
|
||||
var listObj = await _masterListServiceFactory
|
||||
.GetMasterListById(masterListType, listId.Value, locale);
|
||||
|
||||
if (listObj == null)
|
||||
continue;
|
||||
|
||||
var master = listObj as dynamic;
|
||||
|
||||
IEnumerable<OptionList> masterOptions = master.Options;
|
||||
|
||||
// El campo puede ser OptionList simple
|
||||
if (propValue is OptionList { Id: not null } option)
|
||||
{
|
||||
var translated = masterOptions.FirstOrDefault(o => o.Id == option.Id);
|
||||
if (translated != null) option.Name = translated.Name; // Solo traducimos el nombre
|
||||
}
|
||||
}
|
||||
|
||||
return discharge;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
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.DTO.Display;
|
||||
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 MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using DisplayConfig = adas_core.Domain.Models.MongoModels.DisplayConfig;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class DisplayConfigService(
|
||||
IDisplayConfigRepository displayConfigRepository,
|
||||
Lazy<IDisplayService> displayService,
|
||||
ISubscribersService subscribersService,
|
||||
IClientMessageService clientMessageService,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
IMasterListServiceFactory masterListServiceFactory,
|
||||
IDisplayCardConfigRepository displayCardConfigRepository,
|
||||
IDisplayDetailConfigRepository displayDetailConfigRepository,
|
||||
IDisplayChartConfigRepository displayChartRepository)
|
||||
: IDisplayConfigService
|
||||
{
|
||||
public async Task<List<DisplayConfig>> GetAll()
|
||||
{
|
||||
var result = await displayConfigRepository.GetAll();
|
||||
var resultToReturn = new List<DisplayConfig>();
|
||||
foreach (var config in result)
|
||||
{
|
||||
var c = await AddDisplaySectionMinimal(config);
|
||||
if (c != null) resultToReturn.Add(c);
|
||||
}
|
||||
|
||||
return resultToReturn;
|
||||
}
|
||||
|
||||
public async Task<PaginationResponse<DisplayConfigMinimalResponse>> GetAllCompactPaginated(PaginationFilter filter)
|
||||
{
|
||||
var result = displayConfigRepository.GetAllPaginated(filter);
|
||||
var count = await result.CountDocumentsAsync();
|
||||
var resultToReturn = new List<DisplayConfigMinimalResponse>();
|
||||
|
||||
var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
|
||||
.Limit(filter.PageSize)
|
||||
.ToCursorAsync();
|
||||
|
||||
var dataList = await data.ToListAsync();
|
||||
foreach (var config in dataList)
|
||||
{
|
||||
var isInUse = await displayService.Value.IsDisplayConfigInUse(config.Id);
|
||||
resultToReturn.Add(new DisplayConfigMinimalResponse(config, isInUse));
|
||||
}
|
||||
|
||||
|
||||
return new PaginationResponse<DisplayConfigMinimalResponse>(resultToReturn, filter.PageNumber, filter.PageSize,
|
||||
count);
|
||||
}
|
||||
|
||||
public async Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type)
|
||||
{
|
||||
var result = await displayConfigRepository.GetByType(type);
|
||||
var resultToReturn = new List<DisplayConfig>();
|
||||
foreach (var config in result)
|
||||
{
|
||||
var c = await AddDisplaySectionMinimal(config);
|
||||
if (c != null) resultToReturn.Add(c);
|
||||
}
|
||||
|
||||
return resultToReturn;
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig> GetById(ObjectId id)
|
||||
{
|
||||
return await AddDisplaySectionMinimal(await displayConfigRepository.GetById(id)) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig?> GetById(ObjectId? configId, ObjectId unitId,
|
||||
DisplayConfigEnums.DisplayType displayType)
|
||||
{
|
||||
if (configId.HasValue)
|
||||
{
|
||||
DisplayConfig? currentConfig;
|
||||
DisplayConfig? defaultConfig;
|
||||
try
|
||||
{
|
||||
currentConfig = await GetById(configId.Value);
|
||||
}
|
||||
catch (NotFoundException)
|
||||
{
|
||||
currentConfig = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
defaultConfig = await GetDefaultByUnitIdAndType(unitId, displayType);
|
||||
}
|
||||
catch (NotFoundException)
|
||||
{
|
||||
defaultConfig = null;
|
||||
}
|
||||
|
||||
if (defaultConfig != null && currentConfig != null) return currentConfig.MergeConfig(defaultConfig);
|
||||
|
||||
return defaultConfig ?? currentConfig;
|
||||
}
|
||||
|
||||
return await GetDefaultByUnitIdAndType(unitId, displayType);
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig?> InsertOne(DisplayConfig config)
|
||||
{
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, config);
|
||||
return await displayConfigRepository.InsertOneAsyncAndReturn(config);
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig?> InsertOneMinimal(CreateDisplayConfigDto config)
|
||||
{
|
||||
DisplayConfig newDisplayConfig;
|
||||
|
||||
if (config.Type == DisplayConfigEnums.DisplayType.DisplayNurse)
|
||||
newDisplayConfig = new DisplayNurse
|
||||
{
|
||||
Hospital = config.Hospital,
|
||||
Type = config.Type
|
||||
};
|
||||
else
|
||||
newDisplayConfig = new DisplayConfig
|
||||
{
|
||||
Hospital = config.Hospital,
|
||||
Type = config.Type
|
||||
};
|
||||
newDisplayConfig.Id = ObjectId.GenerateNewId();
|
||||
|
||||
var result = await displayConfigRepository.InsertOneAsyncAndReturn(newDisplayConfig);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newDisplayConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig> InsertOneTest()
|
||||
{
|
||||
var d = new DisplayNurse
|
||||
{
|
||||
Type = DisplayConfigEnums.DisplayType.DisplayNurse
|
||||
};
|
||||
await displayConfigRepository.InsertOneAsyncAndReturn(d);
|
||||
var e = new SmartDisplay
|
||||
{
|
||||
Type = DisplayConfigEnums.DisplayType.SmartDisplay
|
||||
};
|
||||
await displayConfigRepository.InsertOneAsyncAndReturn(e);
|
||||
return d;
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig?> UpdateConfig(ObjectId displayConfigId, object newDisplayConfig)
|
||||
{
|
||||
var baseType = JsonConvert.DeserializeObject<DisplayConfigDto>(newDisplayConfig.ToString()!);
|
||||
// var cardConfigUpdate = await UpdateDisplayCardConfig(displayConfigId, baseType);
|
||||
var oldDisplayConfig = await displayConfigRepository.GetById(displayConfigId);
|
||||
switch (baseType!.Type)
|
||||
{
|
||||
case DisplayConfigEnums.DisplayType.SmartDisplay:
|
||||
|
||||
var smartConfigToReturn = await displayConfigRepository.UpdateSmartDisplay(displayConfigId,
|
||||
JsonConvert.DeserializeObject<SmartDisplay>(newDisplayConfig.ToString()!));
|
||||
if (smartConfigToReturn != null)
|
||||
{
|
||||
// smartConfigToReturn.CardConfig = cardConfigUpdate;
|
||||
SendSmartDisplayConfigBroadcast(displayConfigId, smartConfigToReturn,
|
||||
oldDisplayConfig as SmartDisplay);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
newDisplayConfig);
|
||||
return smartConfigToReturn;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DisplayConfigEnums.DisplayType.DisplayNurse:
|
||||
var obsNurseList = masterListServiceFactory.StringNurseObs();
|
||||
var displayConfigUpdate =
|
||||
JsonConvert.DeserializeObject<DisplayNurseDto>(newDisplayConfig.ToString()!);
|
||||
var nurseConfigToReturn =
|
||||
await displayConfigRepository.UpdateDisplayNurse(displayConfigId, displayConfigUpdate,
|
||||
obsNurseList);
|
||||
if (nurseConfigToReturn != null)
|
||||
{
|
||||
// nurseConfigToReturn.CardConfig = cardConfigUpdate;
|
||||
SendDisplayConfigBroadcast(displayConfigId, OperationType.UpdateDisplayConfig, nurseConfigToReturn);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
nurseConfigToReturn);
|
||||
return nurseConfigToReturn;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundNoMatches);
|
||||
}
|
||||
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields)
|
||||
{
|
||||
return displayConfigRepository.UpdateFieldList(objectIdConfigDisplay, fields);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfig)
|
||||
{
|
||||
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
if (oldDisplayConfig.ColorConfig == null)
|
||||
{
|
||||
oldDisplayConfig.ColorConfig = new ColorConfig();
|
||||
await displayConfigRepository.UpdateOneAsync(objectIdConfigDisplay, oldDisplayConfig);
|
||||
}
|
||||
|
||||
var result = await displayConfigRepository.UpdateConfigColor(objectIdConfigDisplay, colorConfig);
|
||||
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
newDisplayConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig)
|
||||
{
|
||||
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var result = await displayConfigRepository.UpdateHeaderConfig(objectIdConfigDisplay, headerConfig);
|
||||
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
newDisplayConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems)
|
||||
{
|
||||
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var result = await displayConfigRepository.UpdateSetHomeBanner(objectIdConfigDisplay, bannerItems);
|
||||
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
newDisplayConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateBaseConfig(DisplayConfig baseConfig)
|
||||
{
|
||||
var oldDisplayConfig = await displayConfigRepository.GetById(baseConfig.Id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var result = await displayConfigRepository.UpdateBaseConfig(baseConfig.Id, baseConfig);
|
||||
var newDisplayConfig = await displayConfigRepository.GetById(baseConfig.Id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
newDisplayConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name)
|
||||
{
|
||||
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var result = await displayConfigRepository.UpdateDisplayConfigHospitalName(objectIdConfigDisplay, name);
|
||||
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
|
||||
newDisplayConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteDisplayConfig(ObjectId objectIdConfigDisplay)
|
||||
{
|
||||
var displays = await displayService.Value.GetByConfigId(objectIdConfigDisplay);
|
||||
if (displays.Count > 0)
|
||||
{
|
||||
var type = displays.First().Type;
|
||||
var defaultConfig = await GetDefaultConfig(type) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
foreach (var display in displays)
|
||||
_ = await displayService.Value.UpdateConfigId(display, defaultConfig.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
|
||||
}
|
||||
|
||||
var result = await displayConfigRepository.DeleteDisplayConfig(objectIdConfigDisplay);
|
||||
if (result != null)
|
||||
{
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId objectIdConfigDisplay)
|
||||
{
|
||||
return await displayService.Value.GetDisplayConfigLocations(objectIdConfigDisplay);
|
||||
}
|
||||
|
||||
public async Task<List<DisplayConfigMinimalResponse>> GetAllCompact()
|
||||
{
|
||||
return await displayConfigRepository.GetAllCompact();
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig?> InsertOneWithTemplate(string objectId,
|
||||
DisplayConfigEnums.DisplayType configType, string? configHospital)
|
||||
{
|
||||
if (ObjectId.TryParse(objectId, out var objectIdConfigDisplay))
|
||||
{
|
||||
var template = await GetById(objectIdConfigDisplay);
|
||||
switch (configType)
|
||||
{
|
||||
case DisplayConfigEnums.DisplayType.StandarDisplay:
|
||||
if (template is StandarDisplay standardTemplate)
|
||||
{
|
||||
var standarConfigg = new StandarDisplay
|
||||
{ Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.StandarDisplay };
|
||||
standarConfigg.MergeConfig(standardTemplate);
|
||||
await InsertOne(standarConfigg);
|
||||
return standarConfigg;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DisplayConfigEnums.DisplayType.DisplayNurse:
|
||||
if (template is DisplayNurse nurseTemplate)
|
||||
{
|
||||
var nurseConfig = new DisplayNurse
|
||||
{ Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.DisplayNurse };
|
||||
nurseConfig.MergeConfig(nurseTemplate);
|
||||
await InsertOne(nurseConfig);
|
||||
return nurseConfig;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DisplayConfigEnums.DisplayType.SmartDisplay:
|
||||
if (template is SmartDisplay smartTemplate)
|
||||
{
|
||||
var smartConfig = new SmartDisplay
|
||||
{ Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.DisplayNurse };
|
||||
smartConfig.MergeConfig(smartTemplate);
|
||||
await InsertOne(smartConfig);
|
||||
return smartConfig;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateCardConfig(CardConfig baseConfig)
|
||||
{
|
||||
var result = await displayCardConfigRepository.UpdateOne(baseConfig);
|
||||
|
||||
if (result.Changes > 0)
|
||||
{
|
||||
var displayConfigs = await displayConfigRepository.GetAllByCardConfigIdAndRotating(baseConfig.Id);
|
||||
foreach (var displayConfig in displayConfigs)
|
||||
{
|
||||
await displayConfigRepository.UpdateDisplayNurse(displayConfig,
|
||||
new DisplayNurseDto() { CardConfig = result.Data }, masterListServiceFactory.StringNurseObs());
|
||||
}
|
||||
|
||||
var displays = await displayConfigRepository.GetAllByCardConfigId(baseConfig.Id);
|
||||
foreach (var display in displays)
|
||||
SendDisplayConfigBroadcast(display, OperationType.UpdateCardDisplayConfig, result.Data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateDetailConfig(CardDetailsConfig baseConfig)
|
||||
{
|
||||
var result = await displayDetailConfigRepository.UpdateOne(baseConfig);
|
||||
var displays = await displayConfigRepository.GetAllByCardDetailConfigId(baseConfig.Id);
|
||||
foreach (var display in displays)
|
||||
SendDisplayConfigBroadcast(display, OperationType.UpdateDetailDisplayConfig, result.Data);
|
||||
if (result.Changes > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateChartConfig(ChartConfig baseConfig)
|
||||
{
|
||||
var result = await displayChartRepository.UpdateOne(baseConfig);
|
||||
if (result.Changes > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteChartConfig(ObjectId objectIdConfigDisplay)
|
||||
{
|
||||
var res = await displayChartRepository.DeleteOne(objectIdConfigDisplay);
|
||||
if (res != null)
|
||||
{
|
||||
await UpdateDeletedChartConfig(objectIdConfigDisplay);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<ChartConfig?> GetChartConfig(ObjectId objectIdConfigChart)
|
||||
{
|
||||
return await displayChartRepository.GetById(objectIdConfigChart);
|
||||
}
|
||||
|
||||
public async Task<CardDetailsConfig?> InsertCardDetailConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
|
||||
{
|
||||
if (updateDisplayConfigNameDto.DetailConfig == null) return null;
|
||||
var result =
|
||||
await displayDetailConfigRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.DetailConfig);
|
||||
if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
|
||||
{
|
||||
var res = await UpdateDetailConfigId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
|
||||
if (res)
|
||||
SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
|
||||
OperationType.UpdateDetailDisplayConfig, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<ChartConfig?> InsertChartConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
|
||||
{
|
||||
if (updateDisplayConfigNameDto.ChartConfig == null) return null;
|
||||
var result = await displayChartRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.ChartConfig);
|
||||
if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
|
||||
{
|
||||
var res = await AddChartId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
|
||||
if (res)
|
||||
SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
|
||||
OperationType.UpdateChartConfig, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<CardConfig?> InsertCardConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
|
||||
{
|
||||
if(updateDisplayConfigNameDto.CardConfig == null) return null;
|
||||
var result = await displayCardConfigRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.CardConfig);
|
||||
if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
|
||||
{
|
||||
var res = await UpdateCardConfigId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
|
||||
if (res)
|
||||
SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
|
||||
OperationType.UpdateDetailDisplayConfig, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<CardConfig>> GetCardConfigAll()
|
||||
{
|
||||
return await displayCardConfigRepository.GetAll();
|
||||
}
|
||||
|
||||
public async Task<CardConfig?> GetCardConfigById(ObjectId id)
|
||||
{
|
||||
return await displayCardConfigRepository.GetById(id);
|
||||
}
|
||||
|
||||
public async Task<DisplayConfig?> GetDefaultConfig(DisplayConfigEnums.DisplayType type)
|
||||
{
|
||||
return await displayConfigRepository.GetDefault(type);
|
||||
}
|
||||
|
||||
private async Task<DisplayConfig?> GetDefaultByUnitIdAndType(ObjectId unitId,
|
||||
DisplayConfigEnums.DisplayType displayType)
|
||||
{
|
||||
return
|
||||
await displayConfigRepository.GetDefaultByUnitIdAndType(unitId,
|
||||
displayType); //?? throw new NotFoundException(ErrorMessage.NotFound_ResourceMissing);
|
||||
}
|
||||
|
||||
private async Task<DisplayConfig?> AddDisplaySectionMinimal(DisplayConfig? displayConfig)
|
||||
{
|
||||
if (displayConfig == null) return null;
|
||||
var displayConfigAux = displayConfigRepository.GetById(displayConfig.Id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
if (displayConfig.Type == DisplayConfigEnums.DisplayType.SmartDisplay &&
|
||||
!displayConfig.DisplaySectionIdList.IsNullOrEmpty())
|
||||
{
|
||||
foreach (var displayId in displayConfig.DisplaySectionIdList)
|
||||
{
|
||||
var d = await displayService.Value.GetById(displayId);
|
||||
if (d != null)
|
||||
{
|
||||
var minimalDisplay = new MinimalDisplaySection
|
||||
{
|
||||
Id = displayId,
|
||||
Name = d.Name
|
||||
};
|
||||
displayConfig.DisplaySectionList.Add(minimalDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, displayConfigAux,
|
||||
displayConfig);
|
||||
}
|
||||
|
||||
return displayConfig;
|
||||
}
|
||||
|
||||
private async Task UpdateDeletedChartConfig(ObjectId deletedId)
|
||||
{
|
||||
await displayConfigRepository.UpdateDeletedChartConfig(deletedId);
|
||||
}
|
||||
|
||||
private async Task<bool> AddChartId(ObjectId? displayConfigId, ObjectId resultId)
|
||||
{
|
||||
var result = await displayConfigRepository.AddChartId(displayConfigId, resultId);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId)
|
||||
{
|
||||
var result = await displayConfigRepository.UpdateCardConfigId(displayConfigId, resultId);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateDetailConfigId(ObjectId? displayConfigId, ObjectId? resultId)
|
||||
{
|
||||
var result = await displayConfigRepository.UpdateDetailConfigId(displayConfigId, resultId);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async void SendDisplayConfigBroadcast(ObjectId displayConfigId, OperationType operationType,
|
||||
object? newDisplayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listDisplayId = await displayService.Value.GetByConfigId(displayConfigId);
|
||||
var listDisplayIdList = listDisplayId.Select(c => c.Id).ToList();
|
||||
var subscribers = subscribersService.GetSubscribers().Where(s =>
|
||||
s.DisplayId != null && listDisplayIdList.Contains((ObjectId)s.DisplayId)).ToList();
|
||||
|
||||
foreach (var sub in subscribers)
|
||||
_ = clientMessageService.SendAsync(sub.Id, operationType,
|
||||
newDisplayConfig);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Error sending update for DisplayNurse config: {message}", e.Message);
|
||||
//throw new ConflictException(ErrorMessage.Conflict_UpdateFailed, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SendSmartDisplayConfigBroadcast(ObjectId displayConfigId, SmartDisplay? newDisplayConfig,
|
||||
SmartDisplay? oldDisplayConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listDisplayId = await displayService.Value.GetByConfigId(displayConfigId);
|
||||
var listDisplayIdList = listDisplayId.Select(c => c.Id).ToList();
|
||||
var subscribers = subscribersService.GetSubscribers().Where(s =>
|
||||
s.DisplayId != null && listDisplayIdList.Contains((ObjectId)s.DisplayId)).ToList();
|
||||
|
||||
if (newDisplayConfig != null && oldDisplayConfig != null)
|
||||
SendSmartDisplayConfigUpdate(subscribers, oldDisplayConfig, newDisplayConfig);
|
||||
else Log.Error("Error sending update for SmartDisplay config config is null");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Error sending update for SmartDisplay config: {message}", e.Message);
|
||||
//throw new ConflictException(ErrorMessage.Conflict_UpdateFailed, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Enviar un mensaje a todos los clientes para que actualicen la configuraci�n del display
|
||||
var displayConfig = await displayConfigRepository.GetById(displayConfigId);
|
||||
if (displayConfig != null)
|
||||
_ = clientMessageService.SendToAllAsync(OperationType.UpdateDisplayConfig, displayConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendSmartDisplayConfigUpdate(List<WsSubscriber> subscribers,
|
||||
SmartDisplay? oldDisplayDisplayConfig, SmartDisplay? newDisplayDisplayConfig)
|
||||
{
|
||||
// Obtener las propiedades que han cambiado
|
||||
var differentProperties = oldDisplayDisplayConfig?.GetDifferentProperties(newDisplayDisplayConfig);
|
||||
|
||||
// Enviar un mensaje a los clientes por cada propiedad que haya cambiado
|
||||
if (differentProperties != null)
|
||||
foreach (var property in differentProperties)
|
||||
foreach (var sub in subscribers)
|
||||
_ = clientMessageService.SendAsync(sub.Id, OperationType.UpdateDisplayConfig,
|
||||
newDisplayDisplayConfig?.GetType().GetProperty(property)?.GetValue(newDisplayDisplayConfig));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
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.AppSettings;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using adas_core.Domain.Models.DTO.Display;
|
||||
using adas_core.Domain.Models.Filter;
|
||||
using adas_core.Domain.Models.Masters;
|
||||
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;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class DisplayService(
|
||||
IDisplayRepository displayRepository,
|
||||
IPointOfCareService pointOfCareService,
|
||||
Lazy<IUnitService> unitService,
|
||||
IDisplayConfigService displayConfigService,
|
||||
ISubscribersService subscribersService,
|
||||
IClientMessageService clientMessageService,
|
||||
IUserRepository userRepository,
|
||||
IAuthService authorityService,
|
||||
ILogger<DisplayService> logger,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService,
|
||||
Lazy<IPermissionService> permissionService,
|
||||
ICacheService cacheService,
|
||||
IOptions<CacheSettings> cacheSettings)
|
||||
: IDisplayService
|
||||
{
|
||||
private readonly CacheSettings? _cacheSettings = cacheSettings.Value ;
|
||||
|
||||
#region Methods
|
||||
|
||||
#region Create
|
||||
|
||||
public async Task<Display> InsertOne(Display display)
|
||||
{
|
||||
var defaultConfig = await displayConfigService.GetDefaultConfig(display.Type) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
display.DisplayConfigId = defaultConfig.Id;
|
||||
await displayRepository.InsertOneAsync(display);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, display);
|
||||
return display;
|
||||
}
|
||||
|
||||
public async Task<Display> InsertOneTest()
|
||||
{
|
||||
var d = new Display
|
||||
{
|
||||
Name = "DisplayTEST",
|
||||
UnitId = new ObjectId("65ba5f89d5ba8e273cf9cd96"),
|
||||
DisplayConfigId = new ObjectId("45ba5f89d5ba8e273cf9cd96")
|
||||
};
|
||||
await displayRepository.InsertOneAsync(d);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, d);
|
||||
return d;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
public async Task<List<DisplayMinimalDto>> GetAllCompact()
|
||||
{
|
||||
var result = await displayRepository.GetAll();
|
||||
List<DisplayMinimalDto> listToReturn = [];
|
||||
foreach (var res in result) listToReturn.Add(new DisplayMinimalDto(res));
|
||||
return listToReturn;
|
||||
}
|
||||
|
||||
public Task<List<Display>> GetAll(string? userName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<PaginationResponse<Display>> GetPaginatedDisplays(PaginationFilter filter)
|
||||
{
|
||||
var result = displayRepository.GetPaginatedDisplays(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<Display>(dataList, filter.PageNumber, filter.PageSize, count);
|
||||
}
|
||||
|
||||
public async Task<List<DisplayWithPermissionsDto>> GetAllByUser(string? userName)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
var listToReturn = new List<DisplayWithPermissionsDto>();
|
||||
if (userName == null) return listToReturn;
|
||||
var user = await userRepository.GetByUserAndAuthoritesName(userName);
|
||||
if (user == null) return listToReturn;
|
||||
if (user.Authorization == null || user.Authorization.Count == 0)
|
||||
user.Authorization = await authorityService.GetUserAuthorities(user.Id);
|
||||
|
||||
if (user.Authorization == null)
|
||||
return listToReturn;
|
||||
|
||||
foreach (var e in user.Authorization)
|
||||
if (e.UnitId != null)
|
||||
{
|
||||
var isParsed = ObjectId.TryParse(e.UnitId, out var dId);
|
||||
if (isParsed)
|
||||
{
|
||||
var dis = await displayRepository.GetByUnitId(dId);
|
||||
foreach (var display in dis)
|
||||
{
|
||||
var toAdd = await GetInfo(display.Id, userName, user.Authorization,null, false, false, false, false);
|
||||
|
||||
if (toAdd != null)
|
||||
{
|
||||
var newDto = new DisplayWithPermissionsDto
|
||||
{
|
||||
Display = toAdd,
|
||||
Permissions =
|
||||
await permissionService.Value.GetPermissionsForUnit(dId.ToString(), user) ??
|
||||
throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission)
|
||||
};
|
||||
listToReturn.Add(newDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.DisplayId != null && !FindDisplayInPerms(e.DisplayId, listToReturn))
|
||||
{
|
||||
var isParsed = ObjectId.TryParse(e.DisplayId, out var dId);
|
||||
if (isParsed)
|
||||
{
|
||||
var toAdd = await GetInfo(dId, userName, user.Authorization, null, false, false, false, false);
|
||||
|
||||
if (toAdd != null)
|
||||
{
|
||||
var newDto = new DisplayWithPermissionsDto
|
||||
{
|
||||
Display = toAdd,
|
||||
Permissions = await permissionService.Value.GetPermissionsForDisplay(toAdd, user)
|
||||
};
|
||||
listToReturn.Add(newDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var end = DateTime.Now;
|
||||
logger.LogDebug("Finished GetAllByUser Displays for user {user} in {TotalSeconds:F1} seconds", userName,
|
||||
(end - start).TotalSeconds);
|
||||
return listToReturn;
|
||||
}
|
||||
|
||||
private static bool FindDisplayInPerms(string displayId, List<DisplayWithPermissionsDto> perms)
|
||||
{
|
||||
return perms.Any(p => p.Display != null && p.Display.Id.ToString() == displayId);
|
||||
}
|
||||
|
||||
public async Task<List<Display>> GetByType(DisplayConfigEnums.DisplayType type)
|
||||
{
|
||||
var configs = await displayConfigService.GetByType(type);
|
||||
var listToReturn = new List<Display>();
|
||||
foreach (var config in configs)
|
||||
{
|
||||
var displayToAdd = await displayRepository.GetByConfigId(config.Id);
|
||||
displayToAdd.ForEach(c => c.DisplayConfig = config);
|
||||
if (!displayToAdd.IsNullOrEmpty()) listToReturn.AddRange(displayToAdd);
|
||||
}
|
||||
|
||||
return listToReturn;
|
||||
}
|
||||
|
||||
public async Task<List<Display>> GetByPointOfCare(PointOfCare pointOfCare)
|
||||
{
|
||||
return await displayRepository.GetByPointOfCare(pointOfCare);
|
||||
}
|
||||
|
||||
public Task<List<Display>> GetByConfigId(ObjectId configId)
|
||||
{
|
||||
return displayRepository.GetByConfigId(configId);
|
||||
}
|
||||
|
||||
public Task<List<Display>> GetByCardConfigId(ObjectId configId)
|
||||
{
|
||||
return displayRepository.GetByCardConfigId(configId);
|
||||
}
|
||||
|
||||
public async Task<Display?> GetByName(string name)
|
||||
{
|
||||
return await displayRepository.GetByName(name) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
}
|
||||
|
||||
public async Task<Display?> GetById(ObjectId id)
|
||||
{
|
||||
return await displayRepository.GetById(id);
|
||||
}
|
||||
|
||||
public async Task<DisplayWithPermissionsDto> GetByIdWithPermissions(ObjectId id, LocaleEnum localeEnum)
|
||||
{
|
||||
var username = JwtHelper.GetUsernameFromPrincipal(httpContextAccessor.HttpContext?.User!) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var user = await userRepository.GetByUserName(username);
|
||||
var display = await GetById(id);
|
||||
|
||||
if (display == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
|
||||
foreach (var poc in display.PointOfCareIdList)
|
||||
{
|
||||
var c = await pointOfCareService.GetInfo(poc, localeEnum);
|
||||
if (c != null) display.PointOfCares.Add(c);
|
||||
}
|
||||
|
||||
display.DisplayConfig =
|
||||
await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, display.Type);
|
||||
display.DisplayConfig!.DisplaySectionList = [];
|
||||
try
|
||||
{
|
||||
display.DisplayConfig!.DisplaySectionList =
|
||||
await GetDisplaySectionByUser(display.Type, id, username, user?.Authorization);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
return new DisplayWithPermissionsDto
|
||||
{
|
||||
Display = display,
|
||||
Permissions = await permissionService.Value.GetPermissionsForDisplay(display, user!)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<long> CountDisplaysByUnitId(ObjectId unitId)
|
||||
{
|
||||
return await displayRepository.CountByUnitId(unitId);
|
||||
}
|
||||
|
||||
public async Task<Display?> GetInfo(ObjectId id,
|
||||
string? userName,
|
||||
List<Authorization>? authorizations,
|
||||
LocaleEnum? locale,
|
||||
bool fillPointOfCare = true,
|
||||
bool fillPatientData = false,
|
||||
bool fillDisplayList = true,
|
||||
bool fillDisplayConfig = true,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
|
||||
Display? display;
|
||||
if (fillDisplayConfig)
|
||||
{
|
||||
|
||||
// Clave: display con configuración
|
||||
var (key, ttl) = CacheKeys.DisplayWithConfigKeyWithTtl(_cacheSettings, id);
|
||||
|
||||
display = await cacheService.GetOrSetObjectAsync(
|
||||
key,
|
||||
async () => await BuildDisplayWithConfig(id, ct),
|
||||
ttl,
|
||||
ct);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clave: display base
|
||||
var (key, ttl) = CacheKeys.DisplayBaseKeyWithTtl(_cacheSettings, id);
|
||||
|
||||
display = await cacheService.GetOrSetObjectAsync(
|
||||
key,
|
||||
async () => await displayRepository.GetById(id),
|
||||
ttl,
|
||||
ct);
|
||||
|
||||
}
|
||||
if (display == null) return null;
|
||||
|
||||
// PointOfCare (cacheado en su propio servicio)
|
||||
if (fillPointOfCare)
|
||||
foreach (var poc in display.PointOfCareIdList)
|
||||
{
|
||||
var c = await pointOfCareService.GetInfo(poc, locale, fillPatientData);
|
||||
if (c != null) display.PointOfCares.Add(c);
|
||||
}
|
||||
|
||||
if (authorizations == null && userName != null)
|
||||
{
|
||||
var c = await userRepository.GetByUserAndAuthoritesName(userName);
|
||||
authorizations = c?.Authorization;
|
||||
}
|
||||
|
||||
// DisplayList (depende de autorizaciones → NO cacheable)
|
||||
if (fillDisplayList)
|
||||
if (display.DisplayConfig != null)
|
||||
display.DisplayConfig.DisplaySectionList =
|
||||
await GetDisplaySectionByUser(display.Type, id, userName, authorizations);
|
||||
else
|
||||
logger.LogError("DISPLAY CONFIG NULL on fill display list");
|
||||
|
||||
var end = DateTime.Now;
|
||||
|
||||
logger.LogDebug("Finished GetInfo Displays for user {user} in {TotalSeconds:F1} seconds", userName,
|
||||
(end - start).TotalSeconds);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
private async Task<Display?> BuildDisplayWithConfig(ObjectId id, CancellationToken ct)
|
||||
{
|
||||
var display = await displayRepository.GetById(id);
|
||||
if (display == null) return null;
|
||||
|
||||
var type = display.Type;
|
||||
|
||||
// DisplayConfig
|
||||
display.DisplayConfig =
|
||||
await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, type);
|
||||
|
||||
if (type != DisplayConfigEnums.DisplayType.SmartDisplay ||
|
||||
display.DisplayConfig is not SmartDisplay { CardRotatingLayout: not null } smart)
|
||||
return display;
|
||||
|
||||
if(smart.CardRotatingLayout== null)
|
||||
return display;
|
||||
|
||||
foreach (var card in smart.CardRotatingLayout)
|
||||
card.Data = await displayConfigService.GetCardConfigById(card.DataId)
|
||||
?? new CardConfig();
|
||||
|
||||
return display;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<MinimalDisplaySection>> GetDisplaySectionByUser(
|
||||
DisplayConfigEnums.DisplayType type,
|
||||
ObjectId? currentDisplay,
|
||||
string? userName,
|
||||
List<Authorization>? authorizations)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listToReturn = new List<MinimalDisplaySection>();
|
||||
if (userName != null)
|
||||
{
|
||||
var user = await userRepository.GetByUserName(userName);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var displayIdByAuthorities = authorizations ?? await authorityService.GetUserAuthorities(user.Id);
|
||||
|
||||
foreach (var e in displayIdByAuthorities)
|
||||
{
|
||||
var isParsed = ObjectId.TryParse(e.DisplayId, out var dId);
|
||||
if (isParsed)
|
||||
{
|
||||
if (listToReturn.All(c => c.Id != dId))
|
||||
{
|
||||
var toAdd = await GetById(dId);
|
||||
if (toAdd != null)
|
||||
if (type == toAdd.Type)
|
||||
{
|
||||
var minDisSec = new MinimalDisplaySection
|
||||
{
|
||||
Name = toAdd.Name,
|
||||
Id = toAdd.Id,
|
||||
IsSelected = currentDisplay != null && toAdd.Id == currentDisplay
|
||||
};
|
||||
listToReturn.Add(minDisSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var isParsedUnitId = ObjectId.TryParse(e.UnitId, out var uId);
|
||||
if (isParsedUnitId)
|
||||
{
|
||||
var unitDisplays = await GetByUnitId(uId);
|
||||
foreach (var disp in unitDisplays)
|
||||
if (type == disp.Type && listToReturn.All(c => c.Id != dId))
|
||||
{
|
||||
var minDisSec = new MinimalDisplaySection
|
||||
{
|
||||
Name = disp.Name,
|
||||
Id = disp.Id,
|
||||
IsSelected = currentDisplay != null && disp.Id == currentDisplay
|
||||
};
|
||||
listToReturn.Add(minDisSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return listToReturn;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MinimalDisplayListDto> GetAllDisplaySection()
|
||||
{
|
||||
var minimalDisplayListDto = new MinimalDisplayListDto();
|
||||
var listDisplayNurse = await GetByType(DisplayConfigEnums.DisplayType.DisplayNurse);
|
||||
foreach (var displayForAdmin in listDisplayNurse)
|
||||
{
|
||||
var minDisSec = new MinimalDisplaySection
|
||||
{
|
||||
Name = displayForAdmin.Name,
|
||||
Id = displayForAdmin.Id
|
||||
};
|
||||
minimalDisplayListDto.DisplayNurse.Add(minDisSec);
|
||||
}
|
||||
|
||||
var listDisplaySmart = await GetByType(DisplayConfigEnums.DisplayType.SmartDisplay);
|
||||
foreach (var displayForAdmin in listDisplaySmart)
|
||||
{
|
||||
var minDisSec = new MinimalDisplaySection
|
||||
{
|
||||
Name = displayForAdmin.Name,
|
||||
Id = displayForAdmin.Id
|
||||
};
|
||||
minimalDisplayListDto.SmartDisplay.Add(minDisSec);
|
||||
}
|
||||
|
||||
return minimalDisplayListDto;
|
||||
}
|
||||
|
||||
public async Task<List<Display>> GetByUnitId(ObjectId unitId)
|
||||
{
|
||||
return await displayRepository.GetByUnitId(unitId);
|
||||
}
|
||||
|
||||
public async Task<PocAndUnitDto> GetAllAvailablePoc(List<string> displayIds, bool excludeVirtual = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var listToReturn = new PocAndUnitDto();
|
||||
var listObjectId = new List<ObjectId>();
|
||||
foreach (var displayId in displayIds)
|
||||
{
|
||||
var isParsed = ObjectId.TryParse(displayId, out var dId);
|
||||
if (isParsed)
|
||||
{
|
||||
var diplay = await displayRepository.GetById(dId);
|
||||
if (diplay != null)
|
||||
listObjectId.Add(diplay.UnitId);
|
||||
}
|
||||
}
|
||||
|
||||
var distinctObjectIds = listObjectId.Distinct().ToList();
|
||||
foreach (var distinctObjectId in distinctObjectIds)
|
||||
{
|
||||
var unit = await unitService.Value.FindById(distinctObjectId) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var pocList = await pointOfCareService.FindByUnitAndStatus(distinctObjectId,
|
||||
StatusEnum.PointOfCare.Available, excludeVirtual);
|
||||
foreach (var pointOfCare in pocList)
|
||||
{
|
||||
var pocAv = new MinimalPocAndUnitDto
|
||||
{
|
||||
PocId = pointOfCare.Id,
|
||||
PocName = pointOfCare.Bed,
|
||||
UnitId = pointOfCare.UnitId,
|
||||
UnitName = unit.Name
|
||||
};
|
||||
listToReturn.PocList.Add(pocAv);
|
||||
}
|
||||
}
|
||||
|
||||
return listToReturn;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error GetAllAvailablePoc {Error}", e.Message);
|
||||
return new PocAndUnitDto();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<PointOfCare>> GetAllPocsByDisplayId(ObjectId id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pocList = new List<PointOfCare>();
|
||||
var display = await displayRepository.GetById(id);
|
||||
if (display == null)
|
||||
return pocList;
|
||||
|
||||
foreach (var pocId in display.PointOfCareIdList)
|
||||
{
|
||||
var poc = await pointOfCareService.FindById(pocId);
|
||||
if (poc != null) pocList.Add(poc);
|
||||
}
|
||||
|
||||
return pocList;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Error GetAllAvailablePoc {Error}", e.Message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId displayConfigId)
|
||||
{
|
||||
var displays = await GetByConfigId(displayConfigId);
|
||||
var locations = new List<DisplayConfigLocationDto>();
|
||||
foreach (var display in displays)
|
||||
{
|
||||
var unit = await unitService.Value.FindById(display.UnitId);
|
||||
DisplayConfigLocationDto newLocation = new()
|
||||
{
|
||||
DisplayName = display.Name,
|
||||
UnitName = unit?.Name,
|
||||
};
|
||||
locations.Add(newLocation);
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
public async Task<bool> IsDisplayConfigInUse(ObjectId displayConfigId)
|
||||
{
|
||||
return await displayRepository.IsDisplayConfigInUse(displayConfigId) > 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
|
||||
/*
|
||||
* En esta actualización se espera una resubscipción al id del display ya que actualizar los PoC conlleva actualizar
|
||||
* subscrioptor y locations para las observaciones
|
||||
*/
|
||||
public async Task<Display?> UpdatePointOfCareList(ObjectId objectId, List<ObjectId> listPocObId)
|
||||
{
|
||||
var oldDisplay = await displayRepository.GetById(objectId) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var displayToReturn = await displayRepository.UpdatePointOfCareList(objectId, listPocObId) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
|
||||
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectId));
|
||||
|
||||
SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayPoC);
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn);
|
||||
return displayToReturn;
|
||||
}
|
||||
|
||||
public async Task<Display?> UpdateConfig(Display oldDisplay, DisplayConfig? newDisplayConfig)
|
||||
{
|
||||
var newDisplayConfigCast = new DisplayConfig();
|
||||
switch (newDisplayConfig?.Type)
|
||||
{
|
||||
case DisplayConfigEnums.DisplayType.DisplayNurse:
|
||||
newDisplayConfigCast = newDisplayConfig as DisplayNurse;
|
||||
break;
|
||||
case DisplayConfigEnums.DisplayType.SmartDisplay:
|
||||
newDisplayConfigCast = newDisplayConfig as SmartDisplay;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newDisplayConfigCast != null)
|
||||
{
|
||||
var displayToReturn = await displayRepository.UpdateConfig(oldDisplay.Id, newDisplayConfigCast);
|
||||
if (displayToReturn != null)
|
||||
{
|
||||
SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay,
|
||||
displayToReturn);
|
||||
}
|
||||
|
||||
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id));
|
||||
|
||||
return displayToReturn;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<Display?> UpdateConfigId(Display oldDisplay, ObjectId configId)
|
||||
{
|
||||
var displayToReturn = await displayRepository.UpdateConfigId(oldDisplay.Id, configId);
|
||||
if (displayToReturn != null)
|
||||
{
|
||||
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id));
|
||||
SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn);
|
||||
}
|
||||
|
||||
return displayToReturn;
|
||||
}
|
||||
|
||||
public async Task<Display?> UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay)
|
||||
{
|
||||
var oldConfig = await displayConfigService.GetById(objectIdConfigDisplay);
|
||||
var result = await displayRepository.UpdateConfigPreset(objectIdDisplay, objectIdConfigDisplay);
|
||||
|
||||
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectIdDisplay));
|
||||
|
||||
var config = await displayConfigService.GetById(objectIdConfigDisplay);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldConfig, config);
|
||||
if (result == null || config == null)
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
|
||||
var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == objectIdDisplay).ToList();
|
||||
switch (config.Type)
|
||||
{
|
||||
case DisplayConfigEnums.DisplayType.DisplayNurse:
|
||||
SendNurseDisplayBroadcast(subscribers, config as DisplayNurse);
|
||||
break;
|
||||
case DisplayConfigEnums.DisplayType.SmartDisplay:
|
||||
SendSmartDisplayBroadcast(subscribers, config as SmartDisplay);
|
||||
break;
|
||||
case DisplayConfigEnums.DisplayType.Unknown:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestIncorrectType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Display?> UpdateName(ObjectId id, string name)
|
||||
{
|
||||
var display = await displayRepository.GetById(id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
var newDisplay = await displayRepository.UpdateName(display, name);
|
||||
|
||||
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id));
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, newDisplay);
|
||||
return newDisplay;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete
|
||||
|
||||
public async Task<bool> DeleteDisplay(ObjectId id)
|
||||
{
|
||||
var display = await displayRepository.GetById(id) ??
|
||||
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
|
||||
await displayRepository.DeleteAsync(id);
|
||||
|
||||
// Invalidar CACHE (colección completa)
|
||||
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id));
|
||||
|
||||
await authorityService.DeleteByDisplayId(id);
|
||||
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task DeleteDisplaysByUnitId(ObjectId unitId)
|
||||
{
|
||||
await displayRepository.DeleteManyByUnitId(unitId);
|
||||
// Invalidar CACHE (colección completa)
|
||||
await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.Displays));
|
||||
|
||||
await authorityService.DeleteByUnitId(unitId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Send Notification
|
||||
|
||||
private void SendSmartDisplayBroadcast(List<WsSubscriber> subscribers, SmartDisplay? config)
|
||||
{
|
||||
foreach (var subscriber in subscribers)
|
||||
_ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateSmartDisplayConfig, config);
|
||||
}
|
||||
|
||||
private void SendNurseDisplayBroadcast(List<WsSubscriber> subscribers, DisplayNurse? config)
|
||||
{
|
||||
foreach (var subscriber in subscribers)
|
||||
_ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateNurseDisplayConfig, config);
|
||||
}
|
||||
|
||||
private void SendDisplayBroadcast(Display display, OperationType operation)
|
||||
{
|
||||
var subscribers = subscribersService.GetSubscribers().Where(s =>
|
||||
s.DisplayId == display.Id).ToList();
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case OperationType.UpdateDisplayPoC:
|
||||
foreach (var subscriber in subscribers)
|
||||
_ = clientMessageService.SendAsync(subscriber.Id, operation, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//using Microsoft.AspNetCore.Http;
|
||||
|
||||
using adas_core.Application.Services.Interfaces;
|
||||
using adas_core.Domain.Enums;
|
||||
using adas_core.Domain.Models.AppSettings;
|
||||
using adas_core.Domain.Models.DTO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class FileService : IFileService
|
||||
{
|
||||
private readonly string? _assetsDirectory;
|
||||
|
||||
private readonly ILogger<FileService> _logger;
|
||||
private readonly string? _updateDirectory;
|
||||
|
||||
public FileService(
|
||||
IOptions<ApiSettings> apiSettings,
|
||||
ILogger<FileService> logger
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
if (apiSettings.Value.PathUpdateFiles != null)
|
||||
_updateDirectory = Path.Combine(apiSettings.Value.PathUpdateFiles);
|
||||
if (apiSettings.Value.PathToDisplayAssets != null)
|
||||
_assetsDirectory = Path.Combine(apiSettings.Value.PathToDisplayAssets);
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> CopyUpdateFiles(ICollection<IFormFile> files)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_updateDirectory)) return false;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
await using var stream = new FileStream(Path.Combine(_updateDirectory, file.FileName), FileMode.Create);
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UploadAssetFiles(ICollection<IFormFile> files, FileEnum.AssetTheme themeParse)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_assetsDirectory)) return false;
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(_assetsDirectory, themeParse.ToString()));
|
||||
if (!directoryInfo.Exists) directoryInfo.Create();
|
||||
foreach (var file in files)
|
||||
{
|
||||
await using var stream =
|
||||
new FileStream(Path.Combine(_assetsDirectory, themeParse.ToString(), file.FileName), FileMode.Create);
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<AssetDto> GetAllAssetFile(FileEnum.AssetTheme themeParse)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_assetsDirectory)) return [];
|
||||
var listToReturn = new List<AssetDto>();
|
||||
var directoryInfo = new DirectoryInfo(Path.Combine(_assetsDirectory, themeParse.ToString()));
|
||||
|
||||
if (!directoryInfo.Exists) directoryInfo.Create();
|
||||
|
||||
var files = directoryInfo.GetFiles(); // Obtener todos los archivos en el directorio
|
||||
foreach (var file in files)
|
||||
{
|
||||
var assetDto = new AssetDto
|
||||
{
|
||||
Name = file.Name,
|
||||
Extension = file.Extension,
|
||||
Path = file.FullName // Obtener la ruta completa del archivo
|
||||
};
|
||||
listToReturn.Add(assetDto);
|
||||
}
|
||||
|
||||
return listToReturn;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError("Error while retrieving assets: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetFilesInDirectory(string directoryPath)
|
||||
{
|
||||
List<string> fileList = [];
|
||||
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(directoryPath))
|
||||
// Obtiene todos los archivos en el directorio
|
||||
fileList.AddRange(Directory.GetFiles(directoryPath));
|
||||
else
|
||||
Log.Warning("La ruta proporcionada no existe: {directoryPath}", directoryPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Ocurrió un error al buscar archivos: {exMessage}", ex.Message);
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
||||
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.MongoModels;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace adas_core.Application.Services;
|
||||
|
||||
public class HistoricalConfigChangesService(
|
||||
IHistoricalConfigChangesRepository historicalConfigChangesRepository,
|
||||
ILogger<HistoricalConfigChangesService> logger,
|
||||
IHttpContextAccessor httpContextAccessor,
|
||||
ILocalAuditService auditService)
|
||||
: IHistoricalConfigChangesService
|
||||
{
|
||||
private readonly ILogger<HistoricalConfigChangesService> _logger = logger;
|
||||
|
||||
public async Task DeleteHistoricalConfigChange(ObjectId id)
|
||||
{
|
||||
var filter = Builders<HistoricalConfigChanges>.Filter.Eq("_id", id);
|
||||
var result = historicalConfigChangesRepository.FindById(id);
|
||||
await historicalConfigChangesRepository.Collection.DeleteOneAsync(filter);
|
||||
_logger.LogInformation("Deleted historicalConfigChanges with id: {id}", id);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
|
||||
}
|
||||
|
||||
public async Task<HistoricalConfigChanges?> FindLastConfigChanges(DisplayConfigEnums.ConfigTypes configType)
|
||||
{
|
||||
var result = await historicalConfigChangesRepository.FindLastHistoricalConfigChangesByType(configType);
|
||||
|
||||
return result.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<HistoricalConfigChanges?> Get(ObjectId id)
|
||||
{
|
||||
return await historicalConfigChangesRepository.FindById(id);
|
||||
}
|
||||
|
||||
public async Task<ICollection<HistoricalConfigChanges>> GetAll()
|
||||
{
|
||||
return await historicalConfigChangesRepository.FindAll();
|
||||
}
|
||||
|
||||
public async Task<ICollection<HistoricalConfigChanges>> GetByType(DisplayConfigEnums.ConfigTypes type, int num = 10)
|
||||
{
|
||||
return await historicalConfigChangesRepository.FindLastHistoricalConfigChangesByType(type, num);
|
||||
}
|
||||
|
||||
|
||||
public async Task<ICollection<HistoricalConfigChanges>> GetByUser(string user,
|
||||
DisplayConfigEnums.ConfigTypes? configTypes = null, int num = 10)
|
||||
{
|
||||
return await historicalConfigChangesRepository.FindLastHistoricalConfigChangesByUser(user, configTypes, num);
|
||||
}
|
||||
|
||||
public async Task<HistoricalConfigChanges?> InsertOne(HistoricalConfigChanges historicalConfigChanges)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await historicalConfigChangesRepository.InsertOneAsync(historicalConfigChanges) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, result);
|
||||
return await historicalConfigChangesRepository.InsertOneAsync(historicalConfigChanges);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception inserting historicalConfigChanges {changes} exception:{e} ",
|
||||
historicalConfigChanges.ToJson(), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HistoricalConfigChanges?> UpdateHistoricalConfigChange(
|
||||
HistoricalConfigChanges historicalConfigChanges)
|
||||
{
|
||||
try
|
||||
{
|
||||
var oldHistorical = await historicalConfigChangesRepository.FindById(historicalConfigChanges.Id) ??
|
||||
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
|
||||
var result = await historicalConfigChangesRepository.Update(historicalConfigChanges);
|
||||
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldHistorical, result);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Exception updating historicalConfigChanges {changes} exception:{e} ",
|
||||
historicalConfigChanges.ToJson(), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LogConfigChanged(string user, DisplayConfigEnums.ConfigTypes configType, string newConfig,
|
||||
string oldConfig)
|
||||
{
|
||||
HistoricalConfigChanges historicalConfigChanges = new()
|
||||
{
|
||||
ConfigType = configType,
|
||||
Time = DateTime.Now,
|
||||
Username = user,
|
||||
OldConfig = oldConfig,
|
||||
NewConfig = newConfig
|
||||
};
|
||||
|
||||
var result = await InsertOne(historicalConfigChanges);
|
||||
if (result == null)
|
||||
_logger.LogError("Error logging config changes. newConfig: {newConfig}, oldConfig: {oldConfig}",
|
||||
historicalConfigChanges.NewConfig, historicalConfigChanges.OldConfig);
|
||||
else
|
||||
_logger.LogDebug("Config changes logged. newConfig: {newConfig}, oldConfig: {oldConfig}",
|
||||
historicalConfigChanges.NewConfig, historicalConfigChanges.OldConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using adas_core.Domain.Models;
|
||||
|
||||
namespace adas_core.Application.Services.Interfaces;
|
||||
|
||||
public interface IApiRequestService
|
||||
{
|
||||
Task SaveRequestAsync(ApiRequest apiRequest);
|
||||
Task SaveRequest(ApiRequest apiRequest);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user