Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
@@ -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 < 30s
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 configuracin 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