rama creada apartir de master en j
This commit is contained in:
@@ -10,6 +10,13 @@ using MongoDB.Bson;
|
||||
|
||||
namespace adas_core.Application.Customizations.H12O.UCIN;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the UCIN-specific clinical calculations that derive secondary
|
||||
/// <see cref="PatientObservation"/> values (Complexity, Oxygenation_Index, Respiratory, IntravenousLines,
|
||||
/// Medication, ERMedication, Monitor, Surgery, Temp_Gradient, TAm alert, etc.) from incoming
|
||||
/// raw observations and active <see cref="PatientTreatment"/> entries, using the catalogue of
|
||||
/// codes, groups, and types configured in <see cref="ApiSettings"/>.
|
||||
/// </summary>
|
||||
public class CalculatedObservations : ICalculatedObservations
|
||||
{
|
||||
private readonly List<string> _complexityObservations =
|
||||
@@ -72,6 +79,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
private readonly Lazy<ITreatmentService> _treatmentService;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CalculatedObservations"/> class,
|
||||
/// resolving its dependencies (observation, medicine, and treatment services, plus the logger)
|
||||
/// from the supplied <see cref="IServiceProvider"/> and loading the configured code catalogues
|
||||
/// (EEG, bolus medications, transcutaneous, regional brain saturation, surgery, respiratory,
|
||||
/// ONi, ventilation modes, ECMO, and notes indicating medication) from <see cref="ApiSettings"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The application's service provider used to resolve the required dependencies and configuration.</param>
|
||||
public CalculatedObservations(IServiceProvider serviceProvider)
|
||||
{
|
||||
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>(); //observationService;
|
||||
@@ -127,58 +142,82 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
ecmo?.ForEach(x => _ecmo.Add(x.Trim()));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Applies the appropriate clinical calculations to a raw patient observation based on its
|
||||
/// <see cref="BasePatientObservation.Name"/>, code, coding system, and expiration state, producing
|
||||
/// derived observations (respiratory assistance, oxygenation index, ventilation mode, TAm alert,
|
||||
/// parsed weight, temperature gradient, intravenous lines, monitor, surgery expiration, complexity,
|
||||
/// and hourly/temperature time-increment handling).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The concrete observation type, deriving from <see cref="BasePatientObservation"/>.</typeparam>
|
||||
/// <param name="obs">The observation to map or transform in-place.</param>
|
||||
/// <param name="onlyByName">
|
||||
/// Reserved for future use. When <see langword="true"/>, restricts the mapping strategy to
|
||||
/// name-based lookups only.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A <see cref="Task{T}"/> that resolves to the (possibly transformed) observation, or
|
||||
/// <see langword="null"/> when the input observation has no name.
|
||||
/// </returns>
|
||||
public async Task<T?> Map<T>(T obs, bool onlyByName) where T : BasePatientObservation
|
||||
{
|
||||
// Early exit: if the observation has no name, there's nothing to map
|
||||
if (string.IsNullOrEmpty(obs.Name)) return obs;
|
||||
//Llega Vías o algo en Vías dictionary
|
||||
//Solo tenemos en cuenta las observaciones de ICA
|
||||
|
||||
// Respiratory observations: only process SNOMED-coded respiratory metrics
|
||||
if (!string.IsNullOrEmpty(obs.Code) && _respiratory.Contains(obs.Code) && obs.CodingSystem == "SNM")
|
||||
{
|
||||
_logger.LogDebug("Mapping observation Respiratorio {obs}", obs);
|
||||
await CalculateAsistResp(obs);
|
||||
}
|
||||
|
||||
// Oxygenation index calculation: triggered by air pressure, FiO2, or PaO2 readings
|
||||
if (obs.Name is "AirPressure_Mean" or "FiO2" or "PaO2") await CalculateOxygenationIndex(obs);
|
||||
|
||||
//Solo tenemos en cuenta las observaciones de Central
|
||||
// Ventilation mode evaluation: processes respiratory mode changes
|
||||
if (obs.Name == "Resp_Mode")
|
||||
{
|
||||
_logger.LogDebug("Mapping Resp mode observation {obs}", obs);
|
||||
await CalculateVentilationMode(obs);
|
||||
}
|
||||
|
||||
|
||||
// Mean arterial pressure (TAm) alerting: includes gestational age considerations
|
||||
if (obs.Name is "TAm" or "Age_Gestational_Fixed" or "Age_Gestational") await CalculateTAmAlert(obs);
|
||||
|
||||
// Weight parsing: only for active (non-expired) patient observations
|
||||
if (obs.Name is "Weight_Newborn" or "Weight_Current")
|
||||
if (obs is PatientObservation { Expired: false })
|
||||
obs = (T)await ParseWeight(obs);
|
||||
|
||||
// Temperature gradient calculation between incubator and patient
|
||||
if (obs is { Name: "Temp_Incubator" } or { Name: "Temp_Patient" }) await CalculateTempGradient(obs);
|
||||
|
||||
|
||||
// Intravenous lines observation processing with potential transformation
|
||||
if (obs is { Name: "IntravenousLinesObs" })
|
||||
{
|
||||
var intraObs = await CalculateIntravenousLineObservation(obs);
|
||||
|
||||
// Apply the transformed observation if calculation produced a result
|
||||
if (intraObs != null)
|
||||
obs = (T)intraObs;
|
||||
}
|
||||
|
||||
// Regional brain saturation monitoring: pCO2tc or transcutaneous O2 with specific codes
|
||||
if (obs.Name is "pCO2tc" or "Transcutaneous_O2")
|
||||
if (obs.Code != null && _regionalBrainSaturation.Contains(obs.Code))
|
||||
await CalculateMonitor(obs);
|
||||
|
||||
// Surgery-related expiration check
|
||||
if (obs.Name == "Surgery") await CheckSurgeryExpired(obs);
|
||||
|
||||
|
||||
// Complexity calculations for predefined complex observation types
|
||||
if (obs.Name != null && _complexityObservations.Contains(obs.Name))
|
||||
{
|
||||
_logger.LogDebug("Mapping observation Complexity {obs}", obs);
|
||||
await CalculateComplexity(obs);
|
||||
}
|
||||
|
||||
// Hourly accumulation observations: diuresis, drainages, hydric balance, and fluid entries
|
||||
// These check for existing observations at the same time and increment values
|
||||
if (obs.Name != null && (obs.Name.Equals("Diuresis_Hour") ||
|
||||
obs.Name.Equals("Drainages_Hour") ||
|
||||
obs.Name.Equals("Hydric_Balance") ||
|
||||
@@ -186,13 +225,25 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
obs.Name.Equals("Enteral_Entries_Hour")))
|
||||
obs = (T)await CheckObsExistsAndIncrementTime(obs);
|
||||
|
||||
// Temperature observations: handle duplicate time entries by incrementing time
|
||||
if (obs.Name != null && (obs.Name.Equals("Temp_Patient") ||
|
||||
obs.Name.Equals("Temp_Incubator")
|
||||
))
|
||||
obs = (T)await CheckObsWithSameTimeExistsAndIncrementTime(obs);
|
||||
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="PatientTreatment"/> to its processed form, computing the end time of single-dose
|
||||
/// treatments, evaluating the medicines associated with the treatment's codes/notes, and dispatching
|
||||
/// the appropriate downstream calculations (bolus, monitor, surgery, ECMO complexity, and ONi observations)
|
||||
/// based on the configured code catalogues.
|
||||
/// </summary>
|
||||
/// <param name="treatment">The <see cref="PatientTreatment"/> to be mapped.</param>
|
||||
/// <returns>A task that represents the asynchronous mapping operation. The task result contains the processed <see cref="PatientTreatment"/>.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="treatment"/> parameter is <c>null</c>.</exception>
|
||||
public async Task<PatientTreatment> Map(PatientTreatment treatment)
|
||||
{
|
||||
if (treatment.SingleDose)
|
||||
@@ -258,18 +309,39 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return treatment;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Identity mapping for a <see cref="PumpObservation"/>: returns the supplied instance unchanged,
|
||||
/// wrapped in a completed task. Provided to satisfy the customization contract; pump observations
|
||||
/// do not currently require UCIN-specific calculation.
|
||||
/// </summary>
|
||||
/// <param name="pumpObservation">The pump observation to map.</param>
|
||||
/// <returns>A task containing the same <see cref="PumpObservation"/> instance that was passed in.</returns>
|
||||
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
|
||||
{
|
||||
return await Task.FromResult(pumpObservation);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience overload that recalculates the "Medication" and "ERMedication" observations for a
|
||||
/// patient using the supplied list of active medicines, building a transient <see cref="PatientTreatment"/>
|
||||
/// for the underlying call.
|
||||
/// </summary>
|
||||
/// <param name="activeMedicines">The list of active <see cref="Medicine"/> instances currently associated with the patient.</param>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
|
||||
{
|
||||
await CalculateMedicineObservation(activeMedicines, [], new PatientTreatment { PatientId = patientId });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the "OpiateBoluses" observation for a patient based on the bolus treatments
|
||||
/// returned by <c>GetActiveBolus</c>. Skips persistence when the value is unchanged from the
|
||||
/// last stored observation.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateActiveBolus(ObjectId patientId)
|
||||
{
|
||||
// SIN RXA
|
||||
@@ -317,22 +389,42 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await _observationService.Value.InsertObservation(bolusObs, mapObs: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all treatments currently considered active for the specified patient, delegating
|
||||
/// the actual retrieval to the configured <see cref="ITreatmentService"/>.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the patient.</param>
|
||||
/// <returns>A collection of active <see cref="PatientTreatment"/> objects for the patient.</returns>
|
||||
public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
|
||||
{
|
||||
var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(id);
|
||||
|
||||
|
||||
return activeTreatments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identity mapping for a <see cref="PatientDiagnosis"/>: returns the supplied instance unchanged,
|
||||
/// wrapped in a completed task. Provided to satisfy the customization contract; diagnoses do not
|
||||
/// currently require UCIN-specific calculation.
|
||||
/// </summary>
|
||||
/// <param name="diagnosis">The <see cref="PatientDiagnosis"/> to map.</param>
|
||||
/// <returns>A task containing the same <see cref="PatientDiagnosis"/> instance that was passed in.</returns>
|
||||
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
|
||||
{
|
||||
return Task.FromResult(diagnosis);
|
||||
}
|
||||
|
||||
/*
|
||||
* Designed to fix observations like intravenous with the possibility to receive multiple intravenous observations in the same hl7 message.
|
||||
* Sometimes ADAS calculates many in the same second. causing inconsistencies when retrieving last observation.
|
||||
*/
|
||||
/// <summary>
|
||||
/// Resolves time-inconsistencies for a new patient observation when it arrives with the same
|
||||
/// (down-to-the-second) timestamp as the latest stored observation of the same name. The new
|
||||
/// observation's <c>Time</c> is shifted forward by one second, and a fresh <see cref="ObjectId"/>
|
||||
/// is generated to avoid duplicate-key collisions.
|
||||
/// </summary>
|
||||
/// <param name="newObservation">The new patient observation to evaluate for time inconsistencies.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation. The task result contains the fixed
|
||||
/// <see cref="PatientObservation"/> if the input had a valid name; otherwise, <see langword="null"/>.
|
||||
/// </returns>
|
||||
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newObservation.Name))
|
||||
@@ -361,21 +453,54 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return newObservation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identity mapping for a list of <see cref="PatientObservation"/> instances: returns the supplied
|
||||
/// list unchanged, wrapped in a completed task. Provided to satisfy the customization contract;
|
||||
/// the pre-mapping phase does not currently require UCIN-specific transformation.
|
||||
/// </summary>
|
||||
/// <param name="listToInsert">The list of patient observations to be pre-mapped.</param>
|
||||
/// <returns>A task containing the original list of patient observations.</returns>
|
||||
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
|
||||
{
|
||||
return Task.FromResult(listToInsert);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identity mapping for a source alarm observation paired with a <see cref="PatientObservationAlarm"/>:
|
||||
/// returns the supplied observation unchanged, wrapped in a completed task. Provided to satisfy the
|
||||
/// customization contract; alarms are not currently transformed in the UCIN customization.
|
||||
/// </summary>
|
||||
/// <param name="obs">The patient observation that triggered the alarm.</param>
|
||||
/// <param name="alarmToInsert">The alarm metadata to be inserted alongside the observation.</param>
|
||||
/// <returns>A task containing the same <see cref="PatientObservation"/> instance that was passed in.</returns>
|
||||
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
|
||||
{
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder alarm dispatch hook used by the customization contract. The UCIN customization
|
||||
/// does not currently implement custom alarm dispatching; calling this method always throws.
|
||||
/// </summary>
|
||||
/// <param name="obs">The patient observation that triggered the alarm.</param>
|
||||
/// <param name="name">The alarm display name.</param>
|
||||
/// <param name="code">The optional alarm code from <see cref="AlarmEnum.Name"/>.</param>
|
||||
/// <returns>Never returns a result.</returns>
|
||||
/// <exception cref="NotImplementedException">Always thrown because alarm dispatch is not implemented in the UCIN customization.</exception>
|
||||
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a "Surgery" observation is marked as expired, inserts a follow-up "Surgery" observation
|
||||
/// with value <c>0</c> at the same time plus one second, signaling that the surgery complexity
|
||||
/// contribution has been removed. Errors are logged and rethrown.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/> with <c>Name == "Surgery"</c>.</param>
|
||||
/// <returns>A task that represents the asynchronous insert operation.</returns>
|
||||
/// <exception cref="InvalidCastException">Thrown when <paramref name="obs"/> cannot be cast to <see cref="PatientObservation"/>.</exception>
|
||||
/// <exception cref="Exception">Rethrown after the underlying exception is written to the console for diagnostics.</exception>
|
||||
public async Task CheckSurgeryExpired(BasePatientObservation obs)
|
||||
{
|
||||
try
|
||||
@@ -404,9 +529,13 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*Used for observations that can come with the same time, the last which enter is the newest. So increment time so that the system can identify which is the last.
|
||||
*/
|
||||
/// <summary>
|
||||
/// For hourly accumulation observations, checks whether another observation with the same name
|
||||
/// already exists within the same hour and, if so, shifts this observation's <c>Time</c> forward
|
||||
/// by one second so the system can identify the most recent entry.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the (possibly time-shifted) base patient observation.</returns>
|
||||
public async Task<BasePatientObservation> CheckObsExistsAndIncrementTime(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
@@ -423,7 +552,13 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return pobs;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// For temperature observations, checks whether another observation with the same name already
|
||||
/// exists at the exact same timestamp and, if so, shifts this observation's <c>Time</c> forward
|
||||
/// by one second so the system can identify the most recent entry.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the (possibly time-shifted) base patient observation.</returns>
|
||||
public async Task<BasePatientObservation> CheckObsWithSameTimeExistsAndIncrementTime(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
@@ -435,13 +570,15 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return pobs;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Oxygenation index is calculated => PMAP x FiO2 x 100 / PaO2
|
||||
/// the method is called when it receives pmap or fio or pao2 and tries to take the other values to perform the
|
||||
/// calculation if they are not
|
||||
/// in database then does nothing.
|
||||
/// Computes the Oxygenation Index (PMAP × FiO2 × 100 / PaO2) using the supplied observation plus
|
||||
/// the latest stored <c>AirPressure_Mean</c>, <c>FiO2</c>, and <c>PaO2</c> values. Persists the
|
||||
/// resulting <c>Oxygenation_Index</c> observation with a 10-second expiration matching FiO2's
|
||||
/// expiration window. Skipped gracefully when PaO2 is zero, when any of the inputs cannot be
|
||||
/// parsed, or when an exception is caught and logged.
|
||||
/// </summary>
|
||||
/// <param name="obs">The observation that triggered the calculation (one of <c>AirPressure_Mean</c>, <c>FiO2</c>, or <c>PaO2</c>).</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateOxygenationIndex(BasePatientObservation obs)
|
||||
{
|
||||
var toSearchList = new List<string>();
|
||||
@@ -512,7 +649,12 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <c>EndTime</c> of a single-dose treatment to 8 hours after its <c>OrderTime</c>
|
||||
/// (or 8 hours from now when <c>OrderTime</c> is <see langword="null"/>).
|
||||
/// </summary>
|
||||
/// <param name="treatment">The single-dose treatment whose end time is to be calculated.</param>
|
||||
/// <returns>The same <see cref="PatientTreatment"/> instance with its <c>EndTime</c> updated.</returns>
|
||||
private static PatientTreatment CalculateSingleDoseEndDate(PatientTreatment treatment)
|
||||
{
|
||||
/* Deprecated calc finish treatment date on shift end. Now every treatment single dose is last 8 Hours
|
||||
@@ -540,6 +682,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return treatment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the medicines associated with a treatment (by matching its codes and notes against the
|
||||
/// medicine catalogue, falling back to <c>NotesIndicatingMedication</c> patterns), aggregates the
|
||||
/// patient's active treatments and medicines, detects parenteral nutrition (NPT) treatments, and
|
||||
/// triggers <c>CalculateMedicineObservation</c> to update the Medication and ERMedication observations.
|
||||
/// </summary>
|
||||
/// <param name="treatment">The treatment whose medicines should be checked.</param>
|
||||
/// <returns>A task that represents the asynchronous check operation.</returns>
|
||||
public async Task CheckTreatmentMedicines(PatientTreatment treatment)
|
||||
{
|
||||
//sI TIENE UNA NOTA CON NPT SON DE TIPO NUTRICIÓN PARENTERAL Y SUMAN 1
|
||||
@@ -627,6 +777,13 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a synthetic <see cref="Medicine"/> representing parenteral nutrition (NPT) when the
|
||||
/// treatment's notes indicate a NPT order. Lipid-based NPTs are tagged with
|
||||
/// <c>ParenteralNutritionLipids</c>; all other NPTs are tagged with <c>ParenteralNutrition</c>.
|
||||
/// </summary>
|
||||
/// <param name="treatment">The treatment to inspect for NPT indicators, or <see langword="null"/>.</param>
|
||||
/// <returns>A task that resolves to a <see cref="Medicine"/> instance describing the parenteral nutrition, or an unnamed, untyped instance when no NPT notes are present.</returns>
|
||||
private static Task<Medicine> CalculateParentalNutritionMedicine(PatientTreatment? treatment)
|
||||
{
|
||||
List<string> medicineType = [];
|
||||
@@ -651,13 +808,16 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return Task.FromResult(medicine);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cada vez que entra un tratamiento recalcular medicación y riesgo de medicación: recuperamos todos los tratamientos activos del paciente,
|
||||
* siendo activos: todos aquellos que no han sido cancelados DC Y SIENDO NW NUEVO,
|
||||
* Medicación: Si treatment.orderControl = NW entra nuevo sumamos 1 si no hay ninguno de ese tipo ya activo
|
||||
* si entra treatment.orderControl = DC restamos 1 si no hay ninguno de ese tipo medición.type
|
||||
* y
|
||||
*/
|
||||
/// <summary>
|
||||
/// Maintains the patient's active medicine list according to the treatment's <c>OrderControl</c>
|
||||
/// (<c>NW</c> adds, <c>XO</c> adds if missing, <c>DC</c> removes), counts the distinct medicine
|
||||
/// types, persists the <c>Medication</c> observation if its value changed, and then recalculates
|
||||
/// the <c>ERMedication</c> observation.
|
||||
/// </summary>
|
||||
/// <param name="activeMedicines">The list of active medicines for the patient. Modified in-place.</param>
|
||||
/// <param name="medicines">The medicines to add or remove depending on <c>OrderControl</c>.</param>
|
||||
/// <param name="treatment">The treatment that triggered the recalculation.</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateMedicineObservation(List<Medicine> activeMedicines, List<Medicine> medicines,
|
||||
PatientTreatment treatment)
|
||||
{
|
||||
@@ -708,7 +868,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await CalculateErMedication(activeMedicines, treatment);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the patient's risk level from their active medicines and persists the
|
||||
/// <c>ERMedication</c> observation with min/max bounds of 0 and 5, skipping persistence
|
||||
/// when the value matches the last stored observation.
|
||||
/// </summary>
|
||||
/// <param name="activeMedicines">The list of active medicines used to derive the risk level.</param>
|
||||
/// <param name="treatment">The treatment that triggered the recalculation.</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
private async Task CalculateErMedication(List<Medicine> activeMedicines, PatientTreatment treatment)
|
||||
{
|
||||
//var activeTreatments = treatmentService.Value.GetActiveTreatmentsByPatient(treatment.patientid);
|
||||
@@ -733,7 +900,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await _observationService.Value.InsertObservation(erMedicationObs, mapObs: false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps the patient's active medicine list to a discrete risk level (0 to 5) based on medicine
|
||||
/// types and groups: lipidic parenteral nutrition or multiple high-risk medicines map to 5,
|
||||
/// non-lipid NPT or one or two high-risk medicines map to 4, metabolic medicines map to 2,
|
||||
/// remaining medicines map to 1, and a list containing only iron/vitamin D maps to 0.
|
||||
/// </summary>
|
||||
/// <param name="activeMedicines">The list of active medicines to evaluate.</param>
|
||||
/// <returns>A task that resolves to the integer risk level (0–5).</returns>
|
||||
private static Task<int> CalculateErMedicineLevels(List<Medicine> activeMedicines)
|
||||
{
|
||||
var ironVitaminDCodes = new List<string> { "374424002", "175041000140104" };
|
||||
@@ -784,8 +958,12 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return Task.FromResult(1);
|
||||
}
|
||||
|
||||
|
||||
//TODO revisar con los valores reales cuando se sepan
|
||||
/// <summary>
|
||||
/// Maps a <c>Resp_Mode</c> observation's value to a discrete <c>Resp_Type</c> (HighFrequencyVentilation,
|
||||
/// Invasive, NonInvasive, or None) by matching it against the configured catalogues for each mode.
|
||||
/// </summary>
|
||||
/// <param name="obs">The respiratory-mode observation expected to be a <see cref="PatientObservation"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous insert operation.</returns>
|
||||
public async Task CalculateVentilationMode(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
@@ -811,6 +989,15 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the patient's respiratory assistance score from the supplied observation's
|
||||
/// <c>Value</c> (matched against the configured respiratory-type catalogue) or from the latest
|
||||
/// stored <c>Resp_Mode</c> when the supplied observation is <c>ONi</c>. Active ONi treatment
|
||||
/// (or a non-expired last ONi observation) forces the score to 10. Persists the resulting
|
||||
/// <c>Respiratory</c> observation and returns the original input unchanged.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation that triggered the calculation (expected to be a <see cref="PatientObservation"/>).</param>
|
||||
/// <returns>A task that resolves to the same base patient observation that was passed in.</returns>
|
||||
public async Task<BasePatientObservation> CalculateAsistResp(BasePatientObservation obs)
|
||||
{
|
||||
//Comprobar si hay un resp_type más nuevo y si no lo hay ignorar
|
||||
@@ -862,7 +1049,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates the synthetic <c>ECMO</c> observation according to the treatment's <c>OrderControl</c>:
|
||||
/// <c>NW</c> sets it to <c>NW</c>, <c>DC</c> sets it to <c>XO</c> if any other ECMO treatment
|
||||
/// remains active or to <c>DC</c> otherwise, and any other <c>OrderControl</c> is ignored.
|
||||
/// In every case the change is followed by a complexity recalculation via <c>CalculateComplexity</c>.
|
||||
/// </summary>
|
||||
/// <param name="treatment">The treatment whose ECMO state is being applied.</param>
|
||||
/// <returns>A task that represents the asynchronous recalculation operation.</returns>
|
||||
private async Task CalculateComplexityOnEcmo(PatientTreatment treatment)
|
||||
{
|
||||
var ecmoObs = new PatientObservation
|
||||
@@ -896,12 +1090,18 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await CalculateComplexity(ecmoObs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculamos la complejidad basándonos en el valor de PESO/VÍAS/ASSIST.RESP/MONIT./CIRUGÍA/MEDICACIÓN
|
||||
* 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
|
||||
* La complejidad va de 0 a 5. Guardamos la observación que nos llega y recalculamos complejidad.
|
||||
*/
|
||||
/// <summary>
|
||||
/// Recalculates the patient's overall <c>Complexity</c> observation from scratch using the latest
|
||||
/// values for the configured complexity-contributing observations (weight, medication, surgery,
|
||||
/// intravenous lines, monitor, and respiratory), the active ECMO treatment state, and the
|
||||
/// optionally supplied <paramref name="obs"/> which is preferred over the database value when
|
||||
/// it is newer and not expired. The result is persisted only when its integer value differs
|
||||
/// from the last stored complexity observation, with time inconsistencies resolved via
|
||||
/// <c>FixTimeInconsistencyWithLast</c>. Returns the original observation unchanged.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation that triggered the recalculation.</param>
|
||||
/// <param name="ignoreObs">When <see langword="true"/>, the supplied <paramref name="obs"/> is not added to the calculation inputs (useful for synthetic observations like ECMO).</param>
|
||||
/// <returns>A task that resolves to the same base patient observation that was passed in.</returns>
|
||||
public async Task<BasePatientObservation> CalculateComplexity(BasePatientObservation obs, bool ignoreObs = false)
|
||||
{
|
||||
try
|
||||
@@ -909,7 +1109,11 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
var logUid = Guid.NewGuid();
|
||||
var complexity = new PatientObservation
|
||||
{
|
||||
PatientId = obs.PatientId, Time = DateTime.UtcNow, CodingSystem = "ADAS", Name = "Complexity", Value = 0
|
||||
PatientId = obs.PatientId,
|
||||
Time = DateTime.UtcNow,
|
||||
CodingSystem = "ADAS",
|
||||
Name = "Complexity",
|
||||
Value = 0
|
||||
};
|
||||
var complexityValue = 0;
|
||||
var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(obs.PatientId);
|
||||
@@ -1059,15 +1263,21 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculate intravenous line when new intravenous observation enters. Get all intravenous obs and filtering actives.
|
||||
/// Si existe ya un catéter del mismo tipo, apuntando a la misma localización con una fecha que ya existe y no la ha
|
||||
/// sido retirada y vuelve a venir
|
||||
/// una inserción, no hacemos caso porque es una actualización.
|
||||
/// Recomputes the <c>IntravenousLines</c> complexity score by reconciling the incoming
|
||||
/// <c>IntravenousLinesObs</c> against the patient's active intravenous lines. Detects and
|
||||
/// corrects common ICCA mis-clicks (inserts incorrectly carrying a remove time), updates an
|
||||
/// existing line in place when only the duration changed, and otherwise adds the new line to
|
||||
/// the active set. The aggregated score is capped at 10 and persisted with bounds [0, 10].
|
||||
/// <c>InvalidCastException</c>s are logged and swallowed.
|
||||
/// </summary>
|
||||
/// <param name="obs"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="obs">The base patient observation that triggered the calculation (expected to be a <see cref="PatientObservation"/> whose <c>Value</c> is a <see cref="PatientIntravenousLinesValue"/>).</param>
|
||||
/// <returns>
|
||||
/// A task that resolves to the original base patient observation when processing succeeds or
|
||||
/// fails gracefully with logging; resolves to <see langword="null"/> when the function returns
|
||||
/// early because the incoming observation is a duplicate insert (in which case the caller should
|
||||
/// not overwrite the stored observation).
|
||||
/// </returns>
|
||||
public async Task<BasePatientObservation?> CalculateIntravenousLineObservation(BasePatientObservation obs)
|
||||
{
|
||||
try
|
||||
@@ -1194,7 +1404,15 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return obs;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Derives the weight-related complexity contribution from a weight observation: values expressed
|
||||
/// in kilograms are first converted to grams via <c>ParseWeight</c>, and the final integer complexity
|
||||
/// contribution is mapped from the gram value using the standard UCIN brackets
|
||||
/// (< 750 g → 7, 750–999 → 5, 1000–1249 → 2, 1250–1999 → 1, ≥ 2000 → 0). Returns 0 when the
|
||||
/// observation value cannot be parsed as a number.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation whose value represents the patient's weight.</param>
|
||||
/// <returns>A task that resolves to the integer complexity contribution (0–7).</returns>
|
||||
private async Task<int> CalculateWeight(BasePatientObservation obs)
|
||||
{
|
||||
var complexityValueOfWeight = 0;
|
||||
@@ -1219,7 +1437,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return complexityValueOfWeight;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a weight observation expressed in kilograms to grams in-place (updates <c>Units</c> to
|
||||
/// <c>"gr"</c> and multiplies <c>Value</c> by 1000). Returns the observation unchanged when it is
|
||||
/// not a <see cref="PatientObservation"/> or when its value cannot be parsed; the error is also
|
||||
/// logged.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation expected to be a <see cref="PatientObservation"/> with a numeric value.</param>
|
||||
/// <returns>A task that resolves to the (possibly converted) base patient observation.</returns>
|
||||
public Task<BasePatientObservation> ParseWeight(BasePatientObservation obs)
|
||||
{
|
||||
if (obs is not PatientObservation pobs || !double.TryParse(pobs.Value.ToString(), out var dValue))
|
||||
@@ -1233,9 +1458,19 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
return Task.FromResult(obs);
|
||||
}
|
||||
|
||||
|
||||
//Monitor observation can be a observation or treatment
|
||||
//TODO como saber si tiene fecha de fin o frecuencia
|
||||
/// <summary>
|
||||
/// Recalculates the <c>Monitor</c> observation by adding 1 point for each active
|
||||
/// transcutaneous / regional-brain-saturation / EEG monitoring signal (codes from the configured
|
||||
/// catalogues) found in the most recent observations and active treatments. Persists the resulting
|
||||
/// observation with bounds [0, 3]. Returns early with a warning when no recent monitor observations
|
||||
/// are found.
|
||||
/// </summary>
|
||||
/// <param name="monitorObservation">
|
||||
/// The trigger for the calculation. Accepts either a <see cref="PatientObservation"/>
|
||||
/// (preferred when available) or a <see cref="PatientTreatment"/>. The patient identifier is
|
||||
/// derived from whichever type is supplied.
|
||||
/// </param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateMonitor(object monitorObservation)
|
||||
{
|
||||
var monitorValue = 0;
|
||||
@@ -1302,7 +1537,14 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await _observationService.Value.InsertObservation(monitorObs, mapObs: false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the <c>Surgery</c> complexity score by reconciling the supplied treatment's
|
||||
/// <c>OrderControl</c> (<c>NW</c> adds, <c>DC</c> removes by placer-order identifier) against the
|
||||
/// patient's other active surgery treatments, then persisting a <c>Surgery</c> observation
|
||||
/// with a score of 5 when any active surgery treatment remains, or 0 otherwise.
|
||||
/// </summary>
|
||||
/// <param name="treatment">The treatment that triggered the recalculation.</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
private async Task CalculateSurgery(PatientTreatment treatment)
|
||||
{
|
||||
var surgeryScore = 0;
|
||||
@@ -1315,7 +1557,7 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
switch (treatment.OrderControl)
|
||||
{
|
||||
//Refactor solo con los tratamientos activos de cirugía, un DC cancela a su entityIdentifier correspondiente.
|
||||
|
||||
|
||||
case OrderControlType.Nw:
|
||||
activeSurgeryTreatments.Add(treatment);
|
||||
break;
|
||||
@@ -1342,7 +1584,18 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await _observationService.Value.InsertObservation(surgeryObs, mapObs: false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computes the patient's active bolus treatments by:
|
||||
/// (1) optionally including the newly arrived treatment, (2) grouping all bolus-eligible treatments
|
||||
/// by placer-order <c>NamespaceId</c>, (3) discarding treatments whose administration time was
|
||||
/// later overridden by an end-time, (4) keeping only the latest message-time entry per distinct
|
||||
/// administration time, and (5) filtering to the configured medication-bolus catalogue, an
|
||||
/// administration time within the last 12 hours, and a non-empty RXA status contained in
|
||||
/// <c>_rxaStatus</c>.
|
||||
/// </summary>
|
||||
/// <param name="patientId">The unique identifier of the patient.</param>
|
||||
/// <param name="newTreatment">The optional new treatment to be included in the active-bolus set.</param>
|
||||
/// <returns>A task that resolves to the list of treatments currently counted as active boluses.</returns>
|
||||
private async Task<List<PatientTreatment>> GetActiveBolus(ObjectId patientId, PatientTreatment? newTreatment)
|
||||
{
|
||||
var treatmentsProcessed = new List<PatientTreatment>();
|
||||
@@ -1401,7 +1654,12 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
p.RequestedGiveCodesStatus.Any(rxa => rxa.Status != null && _rxaStatus.Contains(rxa.Status)));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Persists the <c>OpiateBoluses</c> observation whose value is the count of currently active bolus
|
||||
/// treatments for the patient (as computed by <c>GetActiveBolus</c> including the new treatment).
|
||||
/// </summary>
|
||||
/// <param name="treatment">The treatment that triggered the recalculation and should be included in the active-bolus set.</param>
|
||||
/// <returns>A task that represents the asynchronous insert operation.</returns>
|
||||
private async Task CalculateBolus(PatientTreatment treatment)
|
||||
{
|
||||
var activeBolus = await GetActiveBolus(treatment.PatientId, treatment);
|
||||
@@ -1417,9 +1675,17 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
await _observationService.Value.InsertObservation(bolusObs);
|
||||
}
|
||||
|
||||
/*
|
||||
* En rojo si TAm < Edad Gestacional en semanas en la primera semana y luego edad corregida
|
||||
*/
|
||||
/// <summary>
|
||||
/// Applies the red-alert rule for mean arterial pressure (TAm) in neonates: when TAm is below
|
||||
/// the patient's gestational age (in weeks) for the first week, or below the fixed gestational
|
||||
/// age thereafter, the <c>TAm</c> observation's <c>Status</c> is set to <c>Alert</c>; otherwise
|
||||
/// it is set to <c>Ok</c>. If the supplied observation is a gestational-age value (not TAm),
|
||||
/// a fresh TAm observation is re-inserted with the recalculated status. The threshold is read
|
||||
/// from the latest <c>Age_Gestational</c> observation when its value is below 2 weeks,
|
||||
/// otherwise from the latest <c>Age_Gestational_Fixed</c>.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation that triggered the alert evaluation (<c>TAm</c>, <c>Age_Gestational</c>, or <c>Age_Gestational_Fixed</c>).</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateTAmAlert(BasePatientObservation obs)
|
||||
{
|
||||
var pobs = (PatientObservation)obs;
|
||||
@@ -1473,7 +1739,16 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
}
|
||||
}
|
||||
|
||||
/*Entra temp axilar o incubadora. Restamos la axilar a la incubadora. Solo se calcula si están los dos valores. */
|
||||
/// <summary>
|
||||
/// Computes the temperature gradient between the incubator and the patient (incubator − patient)
|
||||
/// when both temperatures have been observed within 10 minutes of each other. Persists the
|
||||
/// <c>Temp_Gradient</c> observation with the resulting value, after shifting its timestamp via
|
||||
/// <c>CheckObsWithSameTimeExistsAndIncrementTime</c> to avoid colliding with a previous
|
||||
/// gradient observation. Returns early when the companion temperature is missing or the two
|
||||
/// readings fall outside the 10-minute window.
|
||||
/// </summary>
|
||||
/// <param name="obs">The base patient observation that triggered the calculation (<c>Temp_Patient</c> or <c>Temp_Incubator</c>).</param>
|
||||
/// <returns>A task that represents the asynchronous calculation operation.</returns>
|
||||
public async Task CalculateTempGradient(BasePatientObservation obs)
|
||||
{
|
||||
List<PatientObservation> tempRetrievedesFromBd;
|
||||
@@ -1525,6 +1800,10 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of the intravenous-line types recognised by the UCIN complexity model,
|
||||
/// ordered from most invasive (Artery) to least invasive (Peripheral).
|
||||
/// </summary>
|
||||
private enum IntraVenousLineTypes
|
||||
{
|
||||
Artery,
|
||||
@@ -1534,6 +1813,10 @@ public class CalculatedObservations : ICalculatedObservations
|
||||
Peripheral
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration of the respiratory-assistance types recognised by the UCIN complexity model,
|
||||
/// ordered from highest score (Ino) to lowest (None).
|
||||
/// </summary>
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private enum RespiratoryTypes
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user