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;
///
/// Represents a service that provides calculated observations, receiving its dependencies through the supplied .
///
///
/// This class implements the contract, exposing the behavior defined by that interface while relying on constructor-injected services for its operations.
///
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
{
private readonly Lazy _alarmService = serviceProvider.GetRequiredService>();
private readonly ApiSettings
_apiSettings = serviceProvider.GetRequiredService>().Value; //apiSettings;
private readonly IConfigObservationService _configObservationService =
serviceProvider.GetRequiredService(); //configObservationService;
private readonly ILogger _logger =
serviceProvider.GetRequiredService>();
//Use GroupedObservation to avoid circular dependency with _observationService
private readonly Lazy _observationService =
serviceProvider.GetRequiredService>(); //observationService;
private readonly List _pressBloodArteryMean = ["TAm"];
///
/// Asynchronously calculates the active bolus for the specified patient.
///
/// The unique identifier of the patient whose active bolus is to be calculated.
public Task CalculateActiveBolus(ObjectId patientId)
{
return Task.CompletedTask;
}
///
/// Calculates medicine observations for the specified patient based on their active medicines.
///
/// The list of medicines currently active for the patient.
/// The unique identifier of the patient whose medicine observations are being calculated.
public Task CalculateMedicineObservation(List activeMedicines, ObjectId patientId)
{
return Task.CompletedTask;
}
///
/// Retrieves the active treatments associated with the specified patient identifier.
/// Returns an empty collection when no active treatments are found.
///
/// The unique identifier of the patient whose active treatments are being retrieved.
/// A task that represents the asynchronous operation, containing a collection of active records for the patient, or an empty collection if none exist.
public Task> GetActiveTreatmentsByPatient(ObjectId id)
{
return Task.FromResult>(new List());
}
///
/// Maps the obs to an event or alarm depending on its type and inserts it if needed
///
/// Type
/// Observation
/// True if the observation needs to generate an alert
/// Mapped observation
public async Task Map(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;
}
///
/// Maps the specified pump observation to a new instance asynchronously.
///
/// The source to map.
/// A task that represents the asynchronous mapping operation. The task result contains the mapped .
public async Task 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;
}
///
/// Maps a instance, performing the required transformation logic.
///
/// The to be mapped.
/// A task representing the asynchronous operation, containing the mapped .
/// Thrown in all cases because the method has not yet been implemented.
public Task Map(PatientTreatment treatment)
{
throw new NotImplementedException();
}
///
/// Maps a instance. This method is a placeholder and has not been implemented yet.
///
/// The to be mapped.
/// A task that represents the asynchronous operation, containing the mapped .
/// Thrown to indicate that the mapping logic has not been implemented.
public Task Map(PatientDiagnosis diagnosis)
{
throw new NotImplementedException();
}
///
/// Ensures chronological order of patient observations by adjusting a new observation's time to one second after the most recent observation with the same name, when its time is earlier than or equal to the previous one (compared at second precision). Returns the observation unchanged and logs an error if the observation name is null or empty.
///
/// The new patient observation to validate and potentially adjust for time consistency.
/// The patient observation with its time corrected if a time inconsistency was detected, otherwise the original observation.
public async Task 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;
}
///
/// Pre-maps a list of patient observations by creating deep copies, applying configuration-based mapping,
/// and triggering blue code calculation. Returns the original list of patient observations unchanged.
///
/// The list of patient observations to process for mapping and blue code calculation.
/// The original list of patient observations passed to the method.
public Task> PreMapList(List 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);
}
///
/// Maps fields from a onto an existing , copying the alarm's EventId to Code and Event to Name when those values are provided, and always copying the alarm's Value.
///
/// The target instance that will be updated with values from the alarm.
/// The source whose values are applied to .
/// A completed containing the updated .
public Task 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);
}
///
/// Sends an alarm associated with the specified patient observation, using the provided alarm name and optional code.
///
/// The patient observation that triggers or relates to the alarm.
/// The name of the alarm to be sent.
/// The optional alarm code identifying the type of alarm.
/// Thrown because the method is not yet implemented.
Task ICalculatedObservations.SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
///
/// Asynchronously sends an alarm for the specified patient observation through the alarm service, always using a None severity.
///
/// The patient observation associated with the alarm.
/// The name of the alarm.
/// The optional alarm code identifier.
/// The type of alarm to send.
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);
}
///
/// Initial check to ignore old observations generating blue codes
///
/// Observation
/// True if it needs to be ignored
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;
}
///
/// Retrieves the alarm configuration for the given patient observation by looking up a matching configuration entry based on the observation name and patient ID, and applies it to the observation. If no matching configuration is found, the alarm is set to null.
///
/// The patient observation whose alarm configuration should be resolved; its Name and PatientId are used to locate the configuration.
/// The same instance with its Alarm property populated from the matching configuration, or null when no configuration is found.
private async Task CheckAlarmConfig(PatientObservation pobs)
{
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = pobs.Name,
PatientId = pobs.PatientId
});
pobs.Alarm = configObs?.Alarm ?? null;
return pobs;
}
///
/// Creates a new representing an alarm, using the ADAS_ALARM coding system and prefixing the name with "Alarm_". The resulting observation inherits the value, patient identifier, timestamp, and alarm status from the provided source observation.
///
/// The alarm code used to identify the type of alarm; it is set as the Code and used to compose the Name.
/// The source patient observation whose value, patient identifier, time, and alarm flag are copied into the new alarm observation.
/// A new configured as an ADAS alarm observation based on the provided source.
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
};
}
///
/// Checks if a blue code observation Lists needs to be generated and inserts it.
///
///
public async Task CalculateBlueCodeList(List? obsList)
{
//Las 3 observaciones vienen siempre juntas en el mismo mensaje
//Solo se comprueba si se cumplen 2 de las 3 condiciones
var condition = 0;
var patientId = obsList?.FirstOrDefault()?.PatientId;
if (patientId == null) return;
var fcObs = obsList?.FirstOrDefault(o => o is { Name: "FC" });
var sattcObs = obsList?.FirstOrDefault(o => o is { Name: "Sattc" });
var tamObs = obsList?.FirstOrDefault(o => o is { Name: "TAm" });
// FC < 60
if (fcObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(fcObs) &&
double.TryParse(fcObs.Value.ToString(), out var cardiacBeatRate) && cardiacBeatRate < 60)
{
Log.Debug("BlueCode condition: FC {fc} for patient: {obsPatientid} fc increase limit", cardiacBeatRate,
fcObs.PatientId);
condition++;
}
// SpO2 < 80 (Sattc)
if (sattcObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(sattcObs) &&
double.TryParse(sattcObs.Value.ToString(), out var spO2Value) && spO2Value < 80)
{
Log.Debug("Blue code condition: SpO2 {sp} for patient: {obsPatientid} Sattc increase limit", spO2Value,
sattcObs.PatientId);
condition++;
}
if (condition == 0) return;
// Early exit if 2 conditions are met
if (condition == 2)
{
_ = SendBlueCode(fcObs);
return;
}
// TAm actual es un 50% por debajo de la medición de 30 seg antes
if (tamObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(tamObs) &&
double.TryParse(tamObs.Value.ToString(), out var tamObsValue))
{
// Buscamos el anterior
var lastPressBloodObsList =
await _observationService.Value.FindLastObservations(tamObs.PatientId, 1, _pressBloodArteryMean);
var lastPressBloodObs = lastPressBloodObsList.FirstOrDefault();
// Diferencia entre la última observación es < 30 s
if (lastPressBloodObs != null &&
double.TryParse(lastPressBloodObs.Value.ToString(), out var lastPressBloodValue) &&
(tamObs.Time - lastPressBloodObs.Time).TotalSeconds <= 35 &&
tamObsValue <= lastPressBloodValue / 2)
{
Log.Debug("Blue code condition: TAm decrease limit over 30s for patient: {obsPatientid}",
tamObs.PatientId);
condition++;
}
}
if (condition >= 2) _ = SendBlueCode(tamObs);
}
///
/// Sends a Blue Code alarm for the given patient observation. If the observation is null, the method returns without taking any action; otherwise it creates the corresponding alarm observation, validates it against the alarm configuration, persists it, and dispatches the alarm as an automatically triggered Blue alarm.
///
/// The patient observation that triggers the Blue Code alarm; when null, the method short-circuits without processing.
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
///
/// Calculates and normalizes the ventilation mode value for a patient observation.
/// Replaces occurrences of "PRVC" with "VCRP" and maps "FLUJ.ALTO" to "OAF" in the observation's value.
///
/// The base patient observation value whose ventilation mode will be calculated and normalized.
/// The patient observation with the normalized ventilation mode value.
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
///
/// Checks whether a patient observation with the same time already exists and, if so, increments the observation time by one second to avoid a duplicate timestamp.
///
/// The patient observation to evaluate; non- instances are returned unchanged.
/// The original observation, with its Time adjusted by one second when a matching observation is found.
private async Task 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
///
/// Calculates the SpO2/FiO2 (S/F) ratio for a patient by pairing a newly received oxygenation observation
/// (FiO2 or Sattc) with the most recent complementary value and inserting a derived "Sattc_FiO2" observation.
/// The derived value is only inserted when both observations exist, are not expired, parse as numbers, the saturation is at most 97, and FiO2 is non-zero; otherwise the calculation is logged and skipped, and any exception is caught and logged.
///
/// The newly received patient observation that triggered the calculation; its Name must be either "FiO2" or "Sattc".
/// The name of the observation, used to determine whether is the FiO2 or the Sattc value.
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);
}
}
///
/// 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.
///
public async Task CalculateOxygenationIndex(BasePatientObservationValue obs, string name)
{
var toSearchList = new List();
PatientObservation? pVam = null;
PatientObservation? fiO2 = null;
PatientObservation? paO2 = null;
toSearchList.AddRange(new List { "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
///
/// Asynchronously calculates the PaO2/FiO2 (P/F) ratio for a patient and persists the result as a new PaO2_FiO2 observation.
/// Supports being triggered by either an FiO2 or a PaO2_Tidal observation by pairing the provided observation with its counterpart retrieved from the most recent observations, and only persists the result when both values are present, non-expired, parseable as numeric values, and the FiO2 value is non-zero.
///
/// The current patient observation that initiated the calculation; provides the patient identifier and timestamp used for the resulting observation.
/// The name of the observation in , expected to be either FiO2 or PaO2_Tidal, which determines how the counterpart value is resolved.
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);
}
}
///
/// Creates a representing a pump alarm, tagging it with the
/// ADAS_ALARM coding system and a code/name derived from the alarm .
/// The position label used as the observation value distinguishes main rack pumps from auxiliary rack pumps based on , and falls back to the position when no drug name is available. The patient identifier is assigned when present, and the observation is then validated against the alarm configuration.
///
/// The alarm identifier used to build the observation Code and Name.
/// The pump data source providing rack/auxiliary info, drug name, time, and optional patient id.
/// A that yields the resulting observation, or null when the alarm configuration check rejects it.
private async Task 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);
}
}