Documentation modifications

This commit is contained in:
julian
2026-06-27 15:23:26 -07:00
parent a633fe6c06
commit a19fb90902
218 changed files with 2882 additions and 0 deletions
@@ -12,10 +12,28 @@ using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Customizations.H12O.UCIN;
/// <summary>
/// Represents a test fixture that contains unit tests for calculated observations, executing with an order priority of 2 relative to other test fixtures.
/// </summary>
/// <remarks>
/// This fixture is annotated with <see cref="TestFixtureAttribute"/> to mark it as a container of NUnit test methods, and with <see cref="OrderAttribute"/> to control its execution sequence within the test suite.
/// </remarks>
/// <!-- aidoc:v1 sig=d5b9e22 -->
[TestFixture]
[Order(2)]
public class CalculatedObservationsTest
{
/// <summary>
/// Initializes the test fixture for <see cref="CalculatedObservations"/> by creating mocked instances of
/// <see cref="IObservationService"/>, <see cref="IConfigObservationService"/>, <see cref="IMedicineService"/>,
/// <see cref="ITreatmentService"/>, and <see cref="ILogger{CalculatedObservations}"/>, configuring
/// <see cref="IOptions{TOptions}"/> settings for medication bolus, transcutaneous, regional brain saturation,
/// electroencephalogram, respiratory, high-frequency ventilation, invasive ventilation, non-invasive
/// ventilation, one-lung isolation, and ECMO codes, and registering all dependencies in a
/// <see cref="ServiceCollection"/> so that a fully wired <see cref="CalculatedObservations"/> instance
/// is produced for each test.
/// </summary>
/// <!-- aidoc:v1 sig=dee8bf2 body=3c4f27b -->
[SetUp]
public void Setup()
{
@@ -79,6 +97,10 @@ public class CalculatedObservationsTest
private IOptions<ApiSettings> _optionsApiSettings;
/// <summary>
/// Verifies that processing a new <see cref="PatientTreatment"/> that contains a medicine—matched by code to a known <see cref="Medicine"/> with an assigned <see cref="MedicineEnum.Types"/>—for the first time for a patient (no prior observations and no active treatments) results in a <see cref="PatientObservation"/> named <c>Medication</c> with value <c>1</c> associated to the treatment's <see cref="PatientTreatment.PatientId"/>.
/// </summary>
/// <!-- aidoc:v1 sig=e86b69e body=2015ef3 -->
[Test]
public async Task Calculate_First_Medicine_Observation_With_Type_Should_Return_1()
{
@@ -128,6 +150,12 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that mapping a new <see cref="PatientTreatment"/> containing a medicine, when no prior
/// observations or active treatments exist for the patient, results in the insertion of a
/// <see cref="PatientObservation"/> named "Medication" with value <c>0</c> for the patient.
/// </summary>
/// <!-- aidoc:v1 sig=9a4b425 body=8e4c1db -->
[Test]
public async Task Calculate_First_Medicine_Observation_With_Out_Type_Should_Return_1()
{
@@ -176,6 +204,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that when a new <see cref="PatientTreatment"/> with an <c>OrderControl</c> of <see cref="OrderControlType.Nw"/> is mapped for the first time and contains a medicine code, a <see cref="PatientObservation"/> with name <c>Medication</c> and value <c>1</c> is inserted for the patient.
/// </summary>
/// <!-- aidoc:v1 sig=58845f5 body=90a5eab -->
[Test]
public async Task Calculate__Medicine_Observation_New_Type_Should_Sum_1()
{
@@ -226,6 +258,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that when a new <see cref="PatientTreatment"/> is mapped and a prior <see cref="PatientObservation"/> of name "Medication" with value 0 already exists for the patient, the calculated observations service inserts a new observation with value 1 (the sum of the existing value 0 plus the new contribution).
/// </summary>
/// <!-- aidoc:v1 sig=cb5c407 body=b26efc0 -->
[Test]
public async Task Calculate__Medicine_Observation_Type_Already_Exists_Should_Sum_0()
{
@@ -296,6 +332,12 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that when a medicine observation of type <see cref="OrderControlType.Dc"/> already exists for the patient,
/// <see cref="CalculatedObservations.Map(PatientTreatment)"/> subtracts the existing value and inserts a new observation with a resulting value of 0.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test execution.</returns>
/// <!-- aidoc:v1 sig=91b1fbb body=81816e9 -->
[Test]
public async Task Calculate__Medicine_Observation_DC_Type_Already_Exists_Should_Subtract_0()
{
@@ -357,6 +399,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that when a <see cref="OrderControlType.Dc"/> treatment is processed and the patient has two active medicines, a <c>Medication</c> observation is inserted with the last observation value decremented by one (2 - 1 = 1).
/// </summary>
/// <!-- aidoc:v1 sig=a244df0 body=151d371 -->
[Test]
public async Task Calculate__Medicine_Observation_DC_OF_TYPE_WITH_TWO_MEDICINES_Should_Subtract_0()
{
@@ -439,6 +485,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that mapping a new <see cref="PatientTreatment"/> containing a medicine without a defined type results in the insertion of a <see cref="PatientObservation"/> named "Medication" with a value of 1 for the associated patient.
/// </summary>
/// <!-- aidoc:v1 sig=9e218b6 body=fd802ff -->
[Test]
public async Task Calculate_Medicine_Observation_New_Medicine_Without_Type_Should_Insert_Medication_1_point()
{
@@ -514,6 +564,12 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that <see cref="PatientObservation"/>.<see cref="PatientObservation.Map"/> inserts a new
/// <see cref="PatientIntravenousLinesValue"/> observation with a score of <c>5</c> when processing the first
/// intravenous arterial observation for a patient, given that no previous observations exist.
/// </summary>
/// <!-- aidoc:v1 sig=a3cd1e1 body=93b85d0 -->
[Test]
public async Task Calculate_First_Intravenous_Arterial_Observation_Should_Return_5()
{
@@ -550,6 +606,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that when a previous <see cref="PatientObservation"/> for the same patient and location has already been removed, the intravenous calculation performed by <see cref="CalculatedObservations.Map"/> produces a result of 0.
/// </summary>
/// <!-- aidoc:v1 sig=ca5324a body=aae89b4 -->
[Test]
public async Task Calculate_Intravenous_Obs_Already_Exist_Removed_Should_Return_0()
{
@@ -600,6 +660,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with an <see cref="PatientIntravenousLinesValue"/> whose type and location match the last stored observation for the same patient does not trigger an insertion of a calculated "IntravenousLines" observation.
/// </summary>
/// <!-- aidoc:v1 sig=076ddee body=f4d6ebd -->
[Test]
public async Task Calculate_Intravenous_two_observations_of_same_type_and_same_location_should_return_null()
{
@@ -647,6 +711,10 @@ public class CalculatedObservationsTest
), true, true), Times.Never);
}
/// <summary>
/// Verifies that when a new PatientObservation of type "IntravenousLinesObs" is processed alongside a previous observation of the same type for the same patient, the calculator produces a derived PatientObservation named "IntravenousLines" with a value of 10.
/// </summary>
/// <!-- aidoc:v1 sig=fd6a90d body=bae7c5e -->
[Test]
public async Task Calculate_Intravenous_two_observations_of_same_type_should_return_10()
{
@@ -696,6 +764,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that the <see cref="Map"/> method limits the calculated sum of <see cref="PatientIntravenousLinesValue"/> observations to a maximum value of ten when the total exceeds that threshold.
/// </summary>
/// <!-- aidoc:v1 sig=8681a17 body=b0efb6d -->
[Test]
public async Task Calculate_Intravenous_sum_more_than_10_should_limit_to_ten()
{
@@ -761,6 +833,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that mapping a new <see cref="PatientObservation"/> with a "Catéter línea media" type, when two prior intravenous lines exist (one "Catéter EPICUTÁNEO PERIFÉRICO" and one "Catéter Venoso PERIFÉRICO"), inserts an <see cref="PatientObservation"/> named "IntravenousLines" with a summed value of 5 for the given <paramref name="patientId"/>.
/// </summary>
/// <!-- aidoc:v1 sig=1e74e0a body=1cc02b8 -->
[Test]
public async Task Calculate_Intravenous_sum_one_middle_line_and_two_Peripheral_should_return_5()
{
@@ -825,6 +901,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that <see cref="_calculatedObservations"/>.<see cref="_calculatedObservations.Map"/> produces a monitor observation with value 3 for a treatment containing the EEG code <c>42803009</c>, based on the most recent transcutaneous and regional brain saturation observations retrieved for the patient.
/// </summary>
/// <!-- aidoc:v1 sig=a0a8af2 body=b1ce181 -->
[Test]
public async Task Calculate_Monitor_Transcuatenous_Regional_Brain_Saturation_and_enter_EEG_should_return_3()
{
@@ -896,6 +976,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that mapping a ventilation <see cref="PatientObservation"/> with value <c>OXIDO NITRICO 800 PPM MOL</c> for a patient with no prior observations produces a derived <see cref="PatientObservation"/> named "Respiratory" with an integer value of 10.
/// </summary>
/// <!-- aidoc:v1 sig=c24a7f7 body=316af0f -->
[Test]
public async Task Calculate_Respiratory_INO_should_return_10()
{
@@ -924,6 +1008,13 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that <see cref="RespiratoryCalculations.Map"/> calculates a respiratory value of 5 when the patient observation
/// represents a "V.A.F.O." (ventilación de alta frecuencia oscilatoria) entry coded as <c>361110005</c> in the SNM coding system,
/// using an empty list returned by <see cref="IPatientObservationService.FindLastObservations"/>.
/// </summary>
/// <returns>A <see cref="Task"/> that completes when the assertions have been executed.</returns>
/// <!-- aidoc:v1 sig=ff64ea1 body=e0741e9 -->
[Test]
public async Task Calculate_Respiratory_VAFO_should_return_5()
{
@@ -952,6 +1043,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with value "V.M.C." (ventilation type, SNOMED code "361110005") produces a calculated respiratory observation with value <c>3</c> for the same <see cref="PatientObservation.PatientId"/>, when no previous observations are returned by the observation service.
/// </summary>
/// <!-- aidoc:v1 sig=b50bb78 body=fbe450e -->
[Test]
public async Task Calculate_Respiratory_VMC_should_return_3()
{
@@ -980,6 +1075,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with the ventilation type <c>V.N.I. Ciclada</c> (Non-Invasive Mechanical Ventilation) results in the insertion of a calculated respiratory observation with an integer value of <c>2</c> for the same patient.
/// </summary>
/// <!-- aidoc:v1 sig=8bbe6fe body=f6304b9 -->
[Test]
public async Task Calculate_Respiratory_VMNI_should_return_2()
{
@@ -1008,6 +1107,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> with name "Tipo de ventilación" and value "Bajo Flujo" is mapped, a calculated respiratory observation is inserted with value 1 for the associated patient.
/// </summary>
/// <!-- aidoc:v1 sig=dc95390 body=7ee3ef7 -->
[Test]
public async Task Calculate_Respiratory_Nassal_Canulas_Bajo_Flujo_should_return_2()
{
@@ -1036,6 +1139,10 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.Map"/> returns a respiratory value of 1 when the patient observation indicates an "Alto Flujo" (high flow nasal cannula) ventilation type, identified by <see cref="PatientObservation.Code"/> "361110005" in the SNM coding system.
/// </summary>
/// <!-- aidoc:v1 sig=513d3e9 body=b065156 -->
[Test]
public async Task Calculate_Respiratory_Nassal_Canulas_Alto_Flujo_should_return_1()
{
@@ -1064,6 +1171,12 @@ public class CalculatedObservationsTest
), true, false));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with the name "Resp_Mode" and the value "PC-AC"
/// produces a calculated observation for "Resp_Type" using the "ADAS" coding system with the
/// <see cref="RespirationType.Invasive"/> value.
/// </summary>
/// <!-- aidoc:v1 sig=b5d9b6c body=e8d6f79 -->
[Test]
public async Task Calculate_Ventilation_Mode_Returns_INVASIVE()
{
@@ -1085,6 +1198,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with the name <c>Resp_Mode</c> and value <c>DUOPAP</c> triggers an insert of a new observation named <c>Resp_Type</c> using the <see cref="RespirationType.NonInvasive"/> value.
/// </summary>
/// <!-- aidoc:v1 sig=f5e26c4 body=47ff0a9 -->
[Test]
public async Task Calculate_Ventilation_Mode_Returns_NON_INVASIVE()
{
@@ -1106,6 +1223,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with the <c>Resp_Mode</c> name and the <c>PC-HFO</c> value produces a calculated observation named <c>Resp_Type</c> using the <c>ADAS</c> coding system whose value resolves to <see cref="RespirationType.HighFrequencyVentilation"/>.
/// </summary>
/// <!-- aidoc:v1 sig=0b62048 body=f640d1b -->
[Test]
public async Task Calculate_Ventilation_Mode_Returns_HIGH_FREQUENCY()
{
@@ -1127,6 +1248,11 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> with <see cref="PatientObservation.Name"/> "Resp_Mode" and <see cref="PatientObservation.Value"/> "VENTAPNEA" is mapped, the calculated observation for "Resp_Type" is produced with <see cref="RespirationType.None"/> using the "ADAS" coding system.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous test execution.</returns>
/// <!-- aidoc:v1 sig=d1932f8 body=7b268e3 -->
[Test]
public async Task Calculate_Ventilation_Mode_Returns_NONE()
{
@@ -1148,6 +1274,12 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that CalculateOxygenationIndex retrieves the latest AirPressure_Mean, FiO2, and PaO2
/// <see cref="PatientObservation"/> values for the patient via FindLastObservations and inserts a new
/// <see cref="PatientObservation"/> named "Oxygenation_Index" with the correctly computed value of 1250.
/// </summary>
/// <!-- aidoc:v1 sig=c1d2ff2 body=25f4ca1 -->
[Test]
public async Task CalculateOxygenationIndex_ShouldInsertCorrectObservation()
{
@@ -1178,6 +1310,13 @@ public class CalculatedObservationsTest
It.IsAny<bool>(), It.IsAny<bool>()));
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CalculateAsistResp(BasePatientObservation)"/> inserts a new
/// <see cref="PatientObservation"/> with the name <c>Respiratory</c> for the patient when the most recent
/// retrieved observation matches the expected respiratory mode (<c>Resp_Mode</c>), and returns the
/// original <paramref name="observation"/>.
/// </summary>
/// <!-- aidoc:v1 sig=52c29ce body=d802db9 -->
[Test]
public async Task CalculateAsistResp_ShouldUpdateObservation_WhenConditionsMet()
{
@@ -1215,6 +1354,13 @@ public class CalculatedObservationsTest
// [TestCase("valorInvasive", RespirationType.INVASIVE)]
// [TestCase("valorNonInvasive", RespirationType.NON_INVASIVE)]
// [TestCase("valorNone", RespirationType.NONE)]
/// <summary>
/// Verifies that <see cref="CalculateVentilationMode"/> maps the supplied input value to the expected <see cref="RespirationType"/> and persists it as a <c>Resp_Type</c> observation via <see cref="PatientObservationService.InsertIfChanged"/>.
/// Handles the localized input cases <c>valorHFV</c> (mapped to <c>PC-HFO</c>), <c>valorInvasive</c> (mapped to <c>PRVC</c>), and <c>valorNonInvasive</c> (mapped to <c>DUOPAP</c>); any other value falls back to <paramref name="expectedRespType"/>.
/// </summary>
/// <param name="value">The raw <c>Resp_Mode</c> input value from the observation; when it does not match a known ventilation label, it is replaced by <paramref name="expectedRespType"/>.</param>
/// <param name="expectedRespType">The <see cref="RespirationType"/> expected to be inserted as <c>Resp_Type</c> after the calculation runs.</param>
/// <!-- aidoc:v1 sig=f257d24 body=b077ba8 -->
public async Task CalculateVentilationMode_ShouldInsertCorrectRespType(string value,
RespirationType expectedRespType)
{
@@ -1246,6 +1392,16 @@ public class CalculatedObservationsTest
// [TestCase("Age_Gestational", 1, 2, ObservationStatus.Alert)]
// [TestCase("Age_Gestational", 1, 12, ObservationStatus.Ok)]
// [TestCase("Age_Gestational_Fixed", 5, 10, ObservationStatus.Ok)]
/// <summary>
/// Verifies that the <c>CalculateTAmAlert</c> method updates the status of a <c>TAm</c> observation
/// to the expected <see cref="StatusEnum.Type"/> when the observation name is "TAm", and inserts a new
/// <c>TAm</c> observation when the name is "Age_Gestational" or "Age_Gestational_Fixed".
/// </summary>
/// <param name="observationName">The name of the observation under test, expected to be "TAm", "Age_Gestational", or "Age_Gestational_Fixed".</param>
/// <param name="tamValue">The numeric TAm value assigned to the observation and related grouped observations.</param>
/// <param name="gestationalAge">The numeric gestational age value used in the related grouped observations.</param>
/// <param name="expectedType">The expected <see cref="StatusEnum.Type"/> used to assert the resulting observation status.</param>
/// <!-- aidoc:v1 sig=8451823 body=20a4dd1 -->
public async Task CalculateTAmAlert_ShouldUpdateStatusCorrectly(string observationName, int tamValue,
int gestationalAge, StatusEnum.Type expectedType)
{
@@ -1306,6 +1462,10 @@ public class CalculatedObservationsTest
It.Is<PatientObservation>(o => o.PatientId == patientId && o.Name == "TAm"),
It.IsAny<bool>(), It.IsAny<bool>()));
}
/// <summary>
/// Verifies that the ParseWeight method correctly converts the units to "gr" and scales the value (multiplying by 1000) when given a valid <see cref="PatientObservation"/> with a numeric <see cref="PatientObservation.Value"/>.
/// </summary>
/// <!-- aidoc:v1 sig=dd3fee8 body=5df6de0 -->
[Test]
public async Task ParseWeight_WhenValidObservation_ShouldConvertUnitsAndValue()
{
@@ -1328,6 +1488,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that ParseWeight handles non-numeric <see cref="PatientObservation.Value"/> gracefully by returning the original <see cref="PatientObservation"/> unchanged.
/// </summary>
/// <!-- aidoc:v1 sig=e9945f6 body=ed7a0ab -->
[Test]
public async Task ParseWeight_WhenValueIsNotNumeric_ShouldHandleGracefullyAndReturnOriginal()
{
@@ -1339,6 +1503,10 @@ public class CalculatedObservationsTest
Assert.That(result, Is.EqualTo(obs));
}
/// <summary>
/// Verifies that the ParseWeight method returns the original <see cref="BasePatientObservation"/> unchanged when the observation cannot be converted to a <c>PatientObservation</c>, exercising the fallback behavior for invalid input types.
/// </summary>
/// <!-- aidoc:v1 sig=62502e3 body=f2a846f -->
[Test]
public async Task ParseWeight_WhenInvalidObservation_ShouldReturnOriginal()
{
@@ -1350,6 +1518,10 @@ public class CalculatedObservationsTest
Assert.That(result, Is.EqualTo(obs));
}
/// <summary>
/// Verifies that when the patient temperature <see cref="PatientObservation"/> and the latest incubator temperature observation are both recent, <c>CalculateTempGradient</c> triggers an <c>InsertObservation</c> call exactly once to persist the computed <c>Temp_Gradient</c> value (incubator minus patient temperature).
/// </summary>
/// <!-- aidoc:v1 sig=dadceb5 body=9a088dd -->
[Test]
public async Task CalculateTempGradient_WhenTempPatientAndTempIncubatorAreRecent_ShouldCalculateGradient()
{
@@ -1410,6 +1582,11 @@ public class CalculatedObservationsTest
), Times.Once());
}
/// <summary>
/// Verifies that the temperature gradient calculation does not persist a new <see cref="PatientObservation"/>
/// when the lookup for the complementary temperature reading returns no observations.
/// </summary>
/// <!-- aidoc:v1 sig=eb711fb body=d83838d -->
[Test]
public async Task CalculateTempGradient_WhenOneTemperatureIsMissing_ShouldNotCalculateGradient()
{
@@ -1437,6 +1614,10 @@ public class CalculatedObservationsTest
), Times.Never()); // Verificamos que InsertObservation no se llama
}
/// <summary>
/// Verifies that the intravenous line observation calculation maps <see cref="PatientObservation.Name"/> to "IntravenousLines", preserves <see cref="PatientObservation.PatientId"/>, and ensures <see cref="PatientObservation.Time"/> is not earlier than the current time when no prior intravenous lines observation exists.
/// </summary>
/// <!-- aidoc:v1 sig=6d30754 body=9402c27 -->
[Test]
public async Task CalculateIntravenousLineObservation_ShouldCalculateObservationAndAdjustTimeIfNecessary()
{
@@ -1474,6 +1655,10 @@ public class CalculatedObservationsTest
};
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CalculateMonitor(PatientObservation)"/> correctly calculates and inserts a "Monitor" <see cref="PatientObservation"/> when the patient has a valid existing observation matching the configured regional brain saturation code or an active treatment.
/// </summary>
/// <!-- aidoc:v1 sig=489567e body=5acaf14 -->
[Test]
public async Task CalculateMonitor_WithValidObservationOrTreatment_ShouldCalculateAndInsertObservation()
{
@@ -1520,6 +1705,10 @@ public class CalculatedObservationsTest
Times.Once());
}
/// <summary>
/// Verifies that <c>CalculateMonitor</c> calculates and inserts a new Monitor observation for the patient when invoked with a <see cref="PatientTreatment"/>, provided an active treatment exists and a matching non-expired <see cref="PatientObservation"/> is returned by <c>FindLastObservations</c>.
/// </summary>
/// <!-- aidoc:v1 sig=4a87817 body=2f68585 -->
[Test]
public async Task CalculateMonitor_WithPatientTreatment_ShouldCalculateAndInsertObservation()
{
@@ -1565,6 +1754,13 @@ public class CalculatedObservationsTest
Times.Once());
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CalculateMonitor(PatientObservation)"/> does not invoke
/// <see cref="ObservationService.InsertObservation(PatientObservation, bool, bool)"/> when the supplied
/// <see cref="PatientObservation"/> contains incomplete data (only <see cref="BasePatientObservation.PatientId"/>
/// is populated), and the prior observations returned by <see cref="ObservationService.FindLastObservations"/> are empty.
/// </summary>
/// <!-- aidoc:v1 sig=62f5906 body=d3d0023 -->
[Test]
public async Task CalculateMonitor_WithIncompleteData_ShouldNotInsertObservation()
{
@@ -1594,6 +1790,10 @@ public class CalculatedObservationsTest
Times.Never());
}
/// <summary>
/// Verifies that the surgery expiration check does not insert the <see cref="PatientObservation"/> when its <see cref="PatientObservation.Expired"/> flag is set to <c>false</c>, ensuring that only expired observations trigger an insertion.
/// </summary>
/// <!-- aidoc:v1 sig=51d7692 body=0f0f138 -->
[Test]
public async Task CheckSurgeryExpired_WhenNotExpired_DoesNotInsertObservation()
{
@@ -1608,6 +1808,10 @@ public class CalculatedObservationsTest
Times.Never);
}
/// <summary>
/// Tests that when a <see cref="PatientObservation"/> has expired, <see cref="CalculatedObservations.CheckSurgeryExpired(PatientObservation)"/> inserts a new <see cref="PatientObservation"/> named "Surgery" with value 0, range 0 to 5, coding system "ADAS", timestamp offset by one second from the original observation, and matching patient identifier and expiry duration.
/// </summary>
/// <!-- aidoc:v1 sig=ce416cc body=331e208 -->
[Test]
public async Task CheckSurgeryExpired_WhenExpired_InsertsCorrectObservation()
{
@@ -1634,6 +1838,10 @@ public class CalculatedObservationsTest
), true, false), Times.Once);
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CalculateComplexity"/> inserts a new complexity observation when processing a patient observation in a non-ECMO treatment context, ensuring the active treatments and recent observations are retrieved and the resulting complexity value is persisted.
/// </summary>
/// <!-- aidoc:v1 sig=83462d4 body=c26dd24 -->
[Test]
public async Task CalculateComplexity_NewObservation_NoECMO_AddsComplexity()
{
@@ -1693,6 +1901,12 @@ public class CalculatedObservationsTest
), true, false), Times.Once);
}
/// <summary>
/// Verifies that when an expired <see cref="PatientObservation"/> is passed to the complexity calculation,
/// no new complexity record is inserted into the database, ensuring that expired observations are excluded
/// from contributing to the patient's calculated complexity value.
/// </summary>
/// <!-- aidoc:v1 sig=dbd49ea body=f99d711 -->
[Test]
public async Task CalculateComplexity_ExpiredObservation_DoesNotAddToComplexity()
{
@@ -1725,6 +1939,12 @@ public class CalculatedObservationsTest
), true, true), Times.Never); // No se debe llamar InsertObservation
}
/// <summary>
/// Verifies that when an active treatment includes ECMO, <see cref="CalculatedObservations.CalculateComplexity(BasePatientObservation)"/>
/// preserves the original <see cref="PatientObservation"/> value and records a derived <c>Complexity</c> observation labeled
/// "1 ECMO" for the patient.
/// </summary>
/// <!-- aidoc:v1 sig=188c976 body=8b97b7c -->
[Test]
public async Task CalculateComplexity_ECMOActive_AdjustsComplexity()
{
@@ -1784,6 +2004,10 @@ public class CalculatedObservationsTest
), true, false), Times.Once);
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CheckObsExistsAndIncrementTime"/> increments the <see cref="BasePatientObservation.Time"/> by one second beyond the existing observation when the lookup returns a <see cref="PatientObservation"/> sharing the same timestamp, patient, and observation name as the supplied instance.
/// </summary>
/// <!-- aidoc:v1 sig=4530eaf body=d89050c -->
[Test]
public async Task CheckObsExistsAndIncrementTime_SameHourObservation_IncrementsTime()
{
@@ -1823,6 +2047,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CheckObsWithSameTimeExistsAndIncrementTime"/> increments the <see cref="BasePatientObservation.Time"/> of the supplied <see cref="PatientObservation"/> by one second and assigns a new <see cref="BasePatientObservation.Id"/> when another observation with the same time and <see cref="BasePatientObservation.Name"/> already exists for the same <see cref="BasePatientObservation.PatientId"/>.
/// </summary>
/// <!-- aidoc:v1 sig=3e1e2ae body=5370e25 -->
[Test]
public async Task CheckObsWithSameTimeExistsAndIncrementTime_WhenObservationWithSameTimeExists_IncrementsTime()
{
@@ -1869,6 +2097,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.CheckTreatmentMedicines"/> correctly processes a treatment whose notes contain a parental nutrition formulation (comment "NPT" with comment type "formularybaseformulation"), looking up the associated <see cref="Medicine"/> by code, retrieving active treatments and the last medication observation for the patient, and inserting a new <see cref="PatientObservation"/> with <see cref="BasePatientObservation.Name"/> "Medication", <see cref="BasePatientObservation.CodingSystem"/> "ADAS" and <see cref="BasePatientObservation.Value"/> 1.
/// </summary>
/// <!-- aidoc:v1 sig=28e758c body=0996b61 -->
[Test]
public async Task CheckTreatmentMedicines_ProcessesTreatmentCorrectly_WithParentalNutritionMedicine()
{
@@ -13,6 +13,13 @@ using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Customizations.HPAZ;
/// <summary>
/// Provides a NUnit test fixture that hosts unit tests verifying the behavior of calculated observations.
/// </summary>
/// <remarks>
/// The class is marked with the <see cref="TestFixtureAttribute"/> attribute to group test methods that validate calculated observation logic.
/// </remarks>
/// <!-- aidoc:v1 sig=ce3e368 -->
[TestFixture]
public class CalculatedObservationsTest
{
@@ -832,6 +839,10 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that mapping a <see cref="PumpObservation"/> with a null drug name and <see cref="PumpEnum.AlarmType.Occlusion"/> results in an inserted position name formatted as "Rack 1 Bomba 2".
/// </summary>
/// <!-- aidoc:v1 sig=23bca88 body=2889a88 -->
[Test]
public async Task Map_PumpObservation_DrugName_Null_AlarmType_Occlusion_Return_Insert_Pump_Insert_PositionName()
{
@@ -882,6 +893,10 @@ public class CalculatedObservationsTest
arg.Value.ToString() == "Rack 1 Bomba 2"), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PumpObservation"/> with a null drug name, <see cref="PumpEnum.AlarmType.Occlusion"/> alarm, and <see cref="PumpObservation.IsAux"/> set to true produces an insertion of the main pump observation and an auxiliary observation with a position name formatted as "Rack Aux {GatewayNumber} Bomba {Number}".
/// </summary>
/// <!-- aidoc:v1 sig=6ae491c body=5178203 -->
[Test]
public async Task
Map_PumpObservation_DrugName_Null_AlarmType_Occlusion_Return_Insert_Pump_Insert_Aux_PositionName()
@@ -13,9 +13,20 @@ using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Customizations.HRYC;
/// <summary>
/// Provides an NUnit test fixture that verifies the behavior of calculated observations.
/// </summary>
/// <remarks>
/// Decorated with <see cref="TestFixtureAttribute"/> to mark the class as a container for related NUnit test methods that exercise calculated observation logic.
/// </remarks>
/// <!-- aidoc:v1 sig=ce3e368 -->
[TestFixture]
public class CalculatedObservationsTest
{
/// <summary>
/// Initializes the test environment for <see cref="CalculatedObservations"/> by mocking <see cref="IObservationService"/>, <see cref="IPatientService"/>, <see cref="ILightBeaconService"/>, and <see cref="IConfigObservationService"/>, configuring the latter to look up NEWS alert configurations by <see cref="PatientObservation.Name"/> and return <see langword="null"/> when no entry matches, registering all dependencies through a <see cref="ServiceCollection"/>, and providing a sample <see cref="Patient"/> resolved by its <see cref="Patient.Id"/>.
/// </summary>
/// <!-- aidoc:v1 sig=dee8bf2 body=439c7b9 -->
[SetUp]
public void Setup()
{
@@ -113,6 +124,10 @@ public class CalculatedObservationsTest
private ObjectId _patientUnitId = ObjectId.GenerateNewId();
private Patient _patient;
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with <c>NEWS</c> coding, when the resolved <see cref="ConfigObservation"/> enables a beacon alarm with <see cref="AlarmEnum.BeaconColor.None"/>, triggers the light beacon service to send <see cref="LightBeaconColor.Off"/> to the patient's <see cref="BasePatientObservation.PointOfCareId"/>.
/// </summary>
/// <!-- aidoc:v1 sig=e823ca6 body=65dc365 -->
[Test]
public async Task Calculate_CheckBeaconOnNEWS_Should_Send_PowerOff()
{
@@ -148,6 +163,10 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> with the "NEWS" name, "ADAS" coding system, and a value of 6 is processed, and the resolved <see cref="ConfigObservation"/> enables a yellow beacon alarm, the <see cref="LightBeaconService"/> is invoked exactly once to send <see cref="LightBeaconColor.Yellow"/> to the patient's point of care.
/// </summary>
/// <!-- aidoc:v1 sig=aa7e929 body=e6fe577 -->
[Test]
public async Task Calculate_CheckBeaconOnNEWS_Should_Send_Yellow()
{
@@ -183,6 +202,10 @@ public class CalculatedObservationsTest
LightBeaconColor.Yellow), Times.Once);
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> using the NEWS coding system is mapped and the resolved configuration defines an enabled red beacon alarm, the light beacon service is invoked exactly once to send the red color to the patient's point of care.
/// </summary>
/// <!-- aidoc:v1 sig=843f672 body=55f19b1 -->
[Test]
public async Task Calculate_CheckBeaconOnNEWS_Should_Send_Red()
{
@@ -219,6 +242,10 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that <see cref="_calculatedObservations"/>.Map correctly produces a <see cref="PatientObservation"/> with name <c>NEWS_EXTR_HI_Resp_Rate</c>, coding system <c>ADAS</c>, and the same numeric value as the supplied respiratory rate (<c>FR</c>) when the latest ventilator rate, <c>SpO2</c> and <c>FiO2</c> observations are available.
/// </summary>
/// <!-- aidoc:v1 sig=49f263a body=aad4daf -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_FR_Returns_NEWS_EXTR_HI_Resp_Rate()
@@ -270,6 +297,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> named <c>FR</c> with an invalid (non-numeric) value does not produce a NEWS_EXTR_HI respiratory rate result and instead raises a <see cref="FormatException"/>, even when valid <c>Vent_Rat</c>, <c>SpO2</c>, and <c>FiO2</c> observations are available for the same patient.
/// </summary>
/// <!-- aidoc:v1 sig=80e9384 body=260f9f8 -->
[Test]
public async Task Calculate_FR_Does_Not_Returns_NEWS_EXTR_HI_Resp_Rate_When_Invalid_Value()
{
@@ -321,6 +352,15 @@ public class CalculatedObservationsTest
}
/// <summary>
/// Verifies that mapping a respiratory rate (<c>FR</c>) observation through
/// <see cref="ICalculatedObservations.Map(PatientObservation, bool)"/> produces a
/// <c>NEWS_HI_Resp_Rate</c> observation with the expected value (<c>23</c>) and coding system
/// (<c>ADAS</c>), resolving the ventilator rate (<c>Vent_Rat</c>), <c>SpO2</c>, and <c>FiO2</c>
/// inputs via the mocked <see cref="IObservationService.FindLastObservations"/> calls and inserting
/// the result through <see cref="IObservationService.InsertObservation"/>.
/// </summary>
/// <!-- aidoc:v1 sig=36eec84 body=c350cc3 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_FR_Returns_NEWS_HI_Resp_Rate()
@@ -377,6 +417,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies (under the legacy NEWS calculation path, currently ignored via <see cref="IgnoreAttribute"/>) that mapping a respiratory rate (<c>FR</c>) <see cref="PatientObservation"/> produces an inserted <c>NEWS_EXTR_LO_Resp_Rate</c> observation tagged with the <c>ADAS</c> coding system and carrying the same numeric value, using mocked supporting observations for ventilation rate (<c>Vent_Rate</c>) and oxygenation (<c>SpO2</c>, <c>FiO2</c>).
/// </summary>
/// <!-- aidoc:v1 sig=4fa8ea2 body=f8aaeaa -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_FR_Returns_NEWS_EXTR_LO_Resp_Rate()
@@ -433,6 +477,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with an SpO2 value of 90 produces an inserted <see cref="PatientObservation"/> named <c>NEWS_EXTR_LO_SpO2</c> using the <c>ADAS</c> coding system, by mocking the lookup of the last <c>FiO2</c> and <c>FR</c> observations for the same <see cref="PatientObservation.PatientId"/>. The test is annotated with <c>[Ignore]</c> because it targets the legacy NEWS calculation logic.
/// </summary>
/// <!-- aidoc:v1 sig=937a891 body=deafefb -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_SpO2_Returns_NEWS_EXTR_LO_SpO2()
@@ -471,6 +519,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a SpO2 <see cref="PatientObservation"/> produces a NEWS Low SpO2 score <see cref="PatientObservation"/> with name "NEWS_LO_SpO2", coding system "ADAS", and a value reflecting the source SpO2 reading (93), while relying on the last available respiratory rate (FR) observation for the calculation.
/// </summary>
/// <!-- aidoc:v1 sig=686d725 body=e5019f8 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_SpO2_Returns_NEWS_LO_SpO2()
@@ -509,6 +561,11 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> whose <see cref="BasePatientObservation.Name"/> is <c>Temperature</c> results in a derived <see cref="PatientObservation"/> named <c>NEWS_EXTR_LO_Temperature</c>, coded under the <c>ADAS</c> system, and carrying the original numeric <see cref="BasePatientObservation.Value"/>.
/// Marked with <see cref="IgnoreAttribute"/> because it covers the legacy NEWS calculation logic.
/// </summary>
/// <!-- aidoc:v1 sig=34bdb7c body=0b6d2c3 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_Temperature_Returns_NEWS_EXTR_LO_Temperature()
@@ -534,6 +591,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a high <see cref="PatientObservation"/> with name "Temperature" and value 40 produces a calculated NEWS high-temperature observation (<c>NEWS_HI_Temperature</c>) under the <c>ADAS</c> coding system.
/// </summary>
/// <!-- aidoc:v1 sig=2cfd40e body=7a7791e -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_Temperature_Returns_NEWS_HI_Temperature()
@@ -559,6 +620,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with the name "TAs" produces an associated low NEWS extra observation named "NEWS_EXTR_LO_TAs" in the ADAS coding system, carrying the same numeric value as the source observation.
/// </summary>
/// <!-- aidoc:v1 sig=32954ae body=a546520 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_TAs_Returns_NEWS_EXTR_LO_TAs()
@@ -584,6 +649,11 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that when a <c>TAs</c> (systolic blood pressure) <see cref="PatientObservation"/> is mapped, a derived <c>NEWS_LO_TAs</c> observation is inserted with the expected <see cref="PatientObservation.CodingSystem"/> and <see cref="PatientObservation.Value"/>.
/// This test is currently ignored because it targets the old NEWS calculation logic.
/// </summary>
/// <!-- aidoc:v1 sig=a6cacee body=6089652 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_TAs_Returns_NEWS_LO_TAs()
@@ -609,6 +679,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> named "TAs" with a value of 240 produces an inserted observation named "NEWS_EXTR_HI_TAs" under the "ADAS" coding system through <see cref="_calculatedObservations"/>. The test is currently ignored as it covers the old NEWS calculation logic.
/// </summary>
/// <!-- aidoc:v1 sig=1b56dd6 body=7a377a8 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_TAs_Returns_NEWS_EXTR_HI_TAs()
@@ -634,6 +708,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with the name "FC" and a value of 20 produces an inserted calculated observation named "NEWS_EXTR_LO_FC" using the "ADAS" coding system, where the inserted value matches the original value of 20.
/// </summary>
/// <!-- aidoc:v1 sig=58742a8 body=f2f71c1 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_FC_Returns_NEWS_EXTR_LO_FC()
@@ -659,6 +737,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> with a heart rate (FC) value of 140 through the calculation pipeline produces a derived NEWS extra-high heart rate (<c>NEWS_EXTR_HI_FC</c>) observation carrying the same value and tagged with the ADAS coding system.
/// </summary>
/// <!-- aidoc:v1 sig=1eb9206 body=e721300 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_FC_Returns_NEWS_EXTR_HI_FC()
@@ -684,6 +766,11 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> named "FC" with value 120 produces a derived "NEWS_HI_FC" observation that is inserted through the observation service under the "ADAS" coding system with the original value preserved.
/// The test is marked as ignored because it targets the legacy NEWS calculation flow.
/// </summary>
/// <!-- aidoc:v1 sig=2bf5811 body=3387162 -->
[Ignore("old NEWS calc")]
[Test]
public async Task Calculate_FC_Returns_NEWS_HI_FC()
@@ -709,6 +796,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that during the pre-mapping of a list of <see cref="PatientObservation"/> items, any observation named <c>MDC_VENT_RESP_RATE</c> (code <c>151586</c>) is removed from the resulting list and inserted as a separate observation via <see cref="IObservationService.InsertObservation"/>.
/// </summary>
/// <!-- aidoc:v1 sig=fb56de1 body=f339d1c -->
[Test]
public async Task Check_MDC_VENT_RESP_RATE_IsInsertedBefore_And_RemoveFromList()
{
@@ -769,6 +860,10 @@ public class CalculatedObservationsTest
obs.Code == "151586"), true, true), Times.Once);
}
/// <summary>
/// Verifies that when <see cref="PatientObservation"/> mapping processes a respiratory-rate observation alongside a preceding vent-rate observation returned by <see cref="IObservationService.FindLastObservations"/>, the calculated respiratory-rate observation is inserted with its <see cref="PatientObservation.Time"/> offset by one second from the source observation and with its <see cref="PatientObservation.ParentData"/> populated from the vent-rate observation.
/// </summary>
/// <!-- aidoc:v1 sig=c5af3a7 body=2299b03 -->
[Test]
public async Task
Check_Insert_Resp_Rate_Calculated_HasTimePlusOneSecond_And_Resp_Rate_Calculated_HasVentRateAsParent()
@@ -816,6 +911,14 @@ public class CalculatedObservationsTest
obs.Time == nowPlusOneSecond), true, true), Times.Once);
}
/// <summary>
/// Verifies that mapping a patient observation with the name <c>Resp_Mode</c> and value <c>PC-AC</c>
/// through <see cref="_calculatedObservations"/>.Map produces a calculated observation named
/// <c>Resp_Type</c> using the <c>ADAS</c> coding system with the value
/// <see cref="RespirationType.Invasive"/>.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test execution.</returns>
/// <!-- aidoc:v1 sig=480e248 body=e8d6f79 -->
[Test]
public async Task Calculate_Ventilation_Mode_Should_Return_INVASIVE()
{
@@ -838,6 +941,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> for respiratory mode with a non-invasive ventilation value produces a calculated <c>Resp_Type</c> observation classified as <see cref="RespirationType.NonInvasive"/>.
/// </summary>
/// <!-- aidoc:v1 sig=11ecdaa body=7ef6d7b -->
[Test]
public async Task Calculate_Ventilation_Mode_Should_Return_NON_INVASIVE()
{
@@ -861,6 +968,10 @@ public class CalculatedObservationsTest
}
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> whose <see cref="PatientObservation.Name"/> is "Resp_Mode" and <see cref="PatientObservation.Code"/> is "HNF" causes the calculation service to insert an observation with the <see cref="PatientObservation.Name"/> "Resp_Type", the <see cref="PatientObservation.CodingSystem"/> "ADAS", and the <see cref="PatientObservation.Value"/> set to <see cref="RespirationType.HighFrequencyVentilation"/>.
/// </summary>
/// <!-- aidoc:v1 sig=64e7ba4 body=1553933 -->
[Test]
public async Task Calculate_Ventilation_Mode_Should_Return_HIGH_FREQUENCY()
{
@@ -985,6 +1096,10 @@ public class CalculatedObservationsTest
//}
/// <summary>
/// Verifies that the mapping logic computes a diuresis-to-weight ratio of 43.6 from a diuresis observation of 3924 and a weight observation of 90 for the same patient, inserting the result as a new <see cref="PatientObservation"/> named "Diuresis_Weight".
/// </summary>
/// <!-- aidoc:v1 sig=5a47059 body=df4b18b -->
[Test]
public async Task CalculateDiuresis_Weight_Received_D_3924_W_90_Return_43_6()
{
@@ -1018,6 +1133,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that <see cref="_calculatedObservations"/>.<see cref="ICalculatedObservations.Map(PatientObservation, bool)"/> correctly computes the diuresis-to-weight ratio as 43.6 when the latest diuresis observation value is 3924 and the current weight observation value is 90, and persists a derived <see cref="PatientObservation"/> named "Diuresis_Weight" for the same <paramref name="patientId"/>.
/// </summary>
/// <!-- aidoc:v1 sig=b46e7fe body=50dd2ac -->
[Test]
public async Task CalculateDiuresis_Weight_D_3924_Receive_W_90_Return_43_6()
{
@@ -1051,6 +1170,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> with the name <c>Weight_Current</c> is received but no prior diuresis observations exist for the patient, the mapping does not insert any calculated observation.
/// </summary>
/// <!-- aidoc:v1 sig=7c61653 body=30e7115 -->
[Test]
public async Task CalculateDiuresis_Weight_Received_W_90_D_Null_Return_Nothing()
{
@@ -1073,6 +1196,10 @@ public class CalculatedObservationsTest
Times.Never);
}
/// <summary>
/// Verifies that when <see cref="PatientObservation.Map"/> receives a Diuresis observation with value 3924 and the lookup for the last observation returns no results, the calculated "Diuresis_Weight" observation is not inserted.
/// </summary>
/// <!-- aidoc:v1 sig=7539e9b body=c0f4109 -->
[Test]
public async Task CalculateDiuresis_Weight_Received_D_3924_W_Null_Return_Nothing()
{
@@ -1097,6 +1224,10 @@ public class CalculatedObservationsTest
), true, true), Times.Never);
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> named "AllergiesObs" containing an empty list of <see cref="PatientAllergiesValue"/> produces a calculated <see cref="PatientObservation"/> named "Allergies" with an empty string value for the given patient.
/// </summary>
/// <!-- aidoc:v1 sig=aabc273 body=af29e9f -->
[Test]
public async Task CalculateAllergiesObservation_Received_AllergiesObs_Emty_Return_Allergies_Emty()
{
@@ -1121,6 +1252,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> named "AllergiesObs" that contains non-pharmacological allergies (such as "Latex" and "Alergia ambiental") results in the insertion of an "Allergies" observation whose value is the uppercase, comma-separated concatenation of the allergy types (e.g., "LATEX, AMBIENTAL").
/// </summary>
/// <!-- aidoc:v1 sig=87f619d body=739c72a -->
[Test]
public async Task CalculateAllergiesObservation_Received_AllergiesObs_Without_Farmacos_Return_Allergies()
{
@@ -1161,6 +1296,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.Map(PatientObservation, bool)"/> consolidates a received <c>AllergiesObs</c> <see cref="PatientObservation"/> containing one drug allergy (fármacos), a latex allergy, and an environmental allergy into a single <see cref="PatientObservation"/> named <c>Allergies</c> for the same <see cref="PatientObservation.PatientId"/>, with the values merged into the formatted string <c>LATEX, AMBIENTAL, FÁRMACOS (METILPREDNISOLONA)</c>.
/// </summary>
/// <!-- aidoc:v1 sig=6202c3a body=ca57051 -->
[Test]
public async Task CalculateAllergiesObservation_Received_AllergiesObs_With_1_Farmacos_Return_Allergies()
{
@@ -1208,6 +1347,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that <see cref="CalculateAllergiesObservation"/> aggregates a received <c>AllergiesObs</c> observation containing multiple allergy types — including two drug allergies (<c>METILPREDNISOLONA</c> and <c>Penicilina/cefalosporinas</c>), a latex allergy and an environmental allergy — into a single <see cref="PatientObservation"/> named <c>Allergies</c>, whose value combines the allergy categories and the drug allergens into the expected grouped string.
/// </summary>
/// <!-- aidoc:v1 sig=661f407 body=8eae86e -->
[Test]
public async Task CalculateAllergiesObservation_Received_AllergiesObs_With_2_Farmacos_Return_Allergies()
{
@@ -1261,6 +1404,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> carrying a <see cref="PatientDrainagesValue"/> of type "Drenaje ventricular" with volume 60 and height 8 is processed by the calculator, two derived observations are inserted: one named "DVE" carrying the volume value and another named "Drainage_Height" carrying the height value, both under the "ADAS" coding system.
/// </summary>
/// <!-- aidoc:v1 sig=0c97fe5 body=b098a53 -->
[Test]
public async Task Calculate_Drainages_Received_volume_60_height_8_Return_DVE_height()
{
@@ -1298,6 +1445,10 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Verifies that mapping a <see cref="PatientObservation"/> carrying a <see cref="PatientDrainagesValue"/> of type "Drenaje ventricular" with a volume of 60 and no height produces a "DVE" observation while suppressing the "Drainage_Height" observation.
/// </summary>
/// <!-- aidoc:v1 sig=b24ca90 body=c070bf1 -->
[Test]
public async Task Calculate_Drainages_Received_volume_60_height_null_Return_DVE()
{
@@ -1332,6 +1483,12 @@ public class CalculatedObservationsTest
), true, true), Times.Never);
}
/// <summary>
/// Verifies that when a <see cref="PatientObservation"/> containing a <see cref="PatientDrainagesValue"/>
/// has a null received volume but a defined height (8), the mapping inserts a new observation named "Drainage_Height"
/// with the height value while not creating a "DVE" observation.
/// </summary>
/// <!-- aidoc:v1 sig=9f6bb92 body=73e12d9 -->
[Test]
public async Task Calculate_Drainages_Received_volume_null_height_8_Return_height()
{
@@ -1366,6 +1523,11 @@ public class CalculatedObservationsTest
), true, true));
}
/// <summary>
/// Tests that mapping a <see cref="PatientObservation"/> of drainages type with null volume and null height values
/// does not generate any calculated <c>DVE</c> or <c>Drainage_Height</c> observations.
/// </summary>
/// <!-- aidoc:v1 sig=ff5bb6d body=bf2607d -->
[Test]
public async Task Calculate_Drainages_Received_volume_null_height_null_Return_nothing()
{
@@ -13,6 +13,13 @@ using Options = Microsoft.Extensions.Options.Options;
namespace adas_core.Test.Customizations.HUVH.UCIA;
/// <summary>
/// Test fixture that validates the behavior of <see cref="CalculatedObservationsTest"/> related functionality within the test suite.
/// </summary>
/// <remarks>
/// Marked with <c>TestFixture</c> and assigned <c>Order(2)</c> to control its execution sequence relative to other fixtures.
/// </remarks>
/// <!-- aidoc:v1 sig=d5b9e22 -->
[TestFixture]
[Order(2)]
public class CalculatedObservationsTest
@@ -298,6 +305,10 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that the mapping logic inserts a calculated ventilation mode observation when an <c>Air_Flow</c> <see cref="PatientObservation"/> is processed and the patient's coding system is <c>CCC</c>, ensuring the insert call is triggered exactly once.
/// </summary>
/// <!-- aidoc:v1 sig=20f6c3b body=3db2e9e -->
[Test]
public async Task CalculateVentilationMode_Should_Insert_When_AirFlow_Arrives_And_CCC_Mode()
{
@@ -488,6 +499,10 @@ public class CalculatedObservationsTest
);
}
/// <summary>
/// Verifies that the over-analgesia calculation does not create a new <see cref="PatientObservation"/> when the required conditions are not met: the source observation value falls below the expected threshold and the patient has no active <see cref="PatientTreatment"/>s.
/// </summary>
/// <!-- aidoc:v1 sig=8fad758 body=1678766 -->
[Test]
public async Task CalculateOverAnalgesia_Should_Not_Create_Observation_When_Conditions_Are_Not_Met()
{
@@ -547,6 +562,10 @@ public class CalculatedObservationsTest
);
}
/// <summary>
/// Verifies that driving pressure is not calculated nor persisted through <see cref="CalculatedObservations.Map"/> when the complementary <see cref="PatientObservation"/> required for the calculation is missing for the patient.
/// </summary>
/// <!-- aidoc:v1 sig=5427d69 body=8449228 -->
[Test]
public async Task CalculateDrivingPressure_Should_Not_Calculate_If_Observation_Is_Missing()
{
@@ -603,6 +622,13 @@ public class CalculatedObservationsTest
);
}
/// <summary>
/// Verifies that the ROX index mapping operation does not persist a calculated
/// <see cref="PatientObservation"/> when the required complementary observation
/// (needed to compute the ratio) cannot be retrieved, ensuring the calculation
/// is skipped instead of being stored with incomplete data.
/// </summary>
/// <!-- aidoc:v1 sig=6d25e04 body=89d4d97 -->
[Test]
public async Task CalculateRoxIndex_Should_Not_Calculate_If_Observation_Is_Missing()
{
@@ -13,9 +13,20 @@ using Newtonsoft.Json;
namespace adas_core.Test.Customizations.HUVH.UCIN;
/// <summary>
/// Test fixture that contains unit tests for the <see cref="CalculatedObservations"/> type.
/// </summary>
/// <remarks>
/// Marked with <c>TestFixture</c> so that the NUnit test runner discovers and executes the contained test methods.
/// </remarks>
/// <!-- aidoc:v1 sig=ce3e368 -->
[TestFixture]
public class CalculatedObservationsTest
{
/// <summary>
/// Initializes the mocked dependencies and configuration required to instantiate <see cref="CalculatedObservations"/> for unit tests, including observation, medicine, and treatment services, the logger, and an <see cref="IOptions{ApiSettings}"/> populated with mapping interventions and complexity observation values deserialized from JSON.
/// </summary>
/// <!-- aidoc:v1 sig=dee8bf2 body=89278f9 -->
[SetUp]
public void Setup()
{
@@ -1468,6 +1479,10 @@ public class CalculatedObservationsTest
Times.Never);
}
/// <summary>
/// Verifies that when a new "Intervention_In" observation is mapped via <c>Map</c> and the patient's prior "Respiratory_Device_Multivalue" observation is expired (so there is no existing active last observation), a new <see cref="PatientObservation"/> named "Respiratory_Device_Multivalue" is inserted containing the new intervention while the existing expired observation is left untouched.
/// </summary>
/// <!-- aidoc:v1 sig=4271f27 body=7d4a79d -->
[Test]
public async Task Map_WithNewInterventionNoExistingLastIntervention_NewMultiValueObservation()
{
@@ -1519,6 +1534,13 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that when a new <c>Intervention_In</c> observation is mapped for a patient that already has an
/// active <c>Respiratory_Device_Multivalue</c> observation, the new intervention is appended to the existing
/// multi-value observation via <see cref="IObservationService.UpdateObservation"/> rather than inserted as a
/// new record through <see cref="IObservationService.InsertObservation"/>.
/// </summary>
/// <!-- aidoc:v1 sig=4e3257d body=697676f -->
[Test]
public async Task Map_WithNewInterventionAddsToExistingMultiValueObservation()
{
@@ -1805,6 +1827,12 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that the <c>Map</c> method persists a "Complexity" <see cref="PatientObservation"/> with value 5
/// when an "Intravenous_Routes_Multivalue" entry of "VV central" (contributing 2) and a "Respiratory_Device_Multivalue"
/// entry of "Ventilación mecánica Invasiva" (contributing 3) are provided, summing their mapped complexity contributions.
/// </summary>
/// <!-- aidoc:v1 sig=2e89a15 body=4e1ae0e -->
[Test]
public async Task Map_WithIntravenousRoutesAndRespiratoryDeviceMultiValueObservation_CalculatesComplexity_Return5()
{
@@ -1944,6 +1972,13 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that <see cref="PatientObservation"/> values mapped through the calculated observations pipeline
/// produce a <c>Complexity</c> observation with value <c>4</c> when a newborn weight observation of <c>600</c>
/// is provided and no previous observations exist for the respiratory, intravenous, medication, system,
/// or surgery multivalue categories.
/// </summary>
/// <!-- aidoc:v1 sig=bdf12e7 body=260627c -->
[Test]
public async Task Map_WithNewbornWeightObservation_CalculatesComplexity_Return4()
{
@@ -2034,6 +2069,10 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Tests that <see cref="CalculateSaturation.Map"/> inserts a diff observation with the calculated difference and a combined pre/post observation when a pre-observation is provided and the matching post-observation is found through <see cref="IObservationService.FindLastObservations"/>.
/// </summary>
/// <!-- aidoc:v1 sig=1391042 body=6df8725 -->
[Test]
public async Task CalculateSaturation_DiffObservation_WhenPreObservationExistsAndPostIsFound_InsertsDiffAndPrePost()
{
@@ -2082,6 +2121,10 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that when a post observation is processed and a matching pre observation is found, both a diff observation (with the computed difference value) and a combined pre/post observation (with the pre and post values) are inserted.
/// </summary>
/// <!-- aidoc:v1 sig=b6cc196 body=fb6ab5b -->
[Test]
public async Task CalculateSaturation_DiffObservation_WhenPostObservationExistsAndPreIsFound_InsertsDiffAndPrePost()
{
@@ -2130,6 +2173,10 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that <see cref="CalculatedObservations.Map(PatientObservation)"/> does not insert new <see cref="PatientObservation"/> records when only the pre-observation is available for the patient and no matching post-observation is returned by <see cref="IObservationService.FindLastObservations"/>.
/// </summary>
/// <!-- aidoc:v1 sig=fc1b349 body=1b524a8 -->
[Test]
public async Task CalculateSaturation_DiffObservation_WhenOnlyPreObservationExists_DoesNotInsertNewObservations()
{
@@ -2161,6 +2208,12 @@ public class CalculatedObservationsTest
x => x.InsertObservation(It.IsAny<PatientObservation>(), It.IsAny<bool>(), It.IsAny<bool>()), Times.Never);
}
/// <summary>
/// Verifies that <c>CalculateSaturation</c> for a diff observation does not insert new
/// <see cref="PatientObservation"/> entries when only the post-observation is found and the
/// pre-observation is missing, ensuring no fallback insert occurs in this scenario.
/// </summary>
/// <!-- aidoc:v1 sig=c544665 body=5c5be3e -->
[Test]
public async Task CalculateSaturation_DiffObservation_WhenOnlyPostObservationExists_DoesNotInsertNewObservations()
{
@@ -2192,6 +2245,10 @@ public class CalculatedObservationsTest
x => x.InsertObservation(It.IsAny<PatientObservation>(), It.IsAny<bool>(), It.IsAny<bool>()), Times.Never);
}
/// <summary>
/// Verifies that the saturation difference calculation correctly handles pre and post <see cref="PatientObservation"/> values that differ, producing a "SpO2_Diff" observation with the numeric difference (7.4) and a "SpO2_Pre_Post" observation with both values formatted as "99,5/92,1".
/// </summary>
/// <!-- aidoc:v1 sig=f1e8f91 body=ed06b26 -->
[Test]
public async Task CalculateSaturation_DiffObservation_WithDifferentPrePostValues_CalculatesCorrectDifference()
{
@@ -2240,6 +2297,14 @@ public class CalculatedObservationsTest
Times.Once);
}
/// <summary>
/// Verifies that when mapping a pre-observation whose <see cref="PatientObservation.Value"/> is non-numeric, the
/// <c>CalculateSaturation</c> logic treats the pre-value as 0 when computing the difference against the latest
/// matching post-observation returned by <see cref="IObservationService.FindLastObservations"/>.
/// Confirms that a <c>SpO2_Diff</c> observation is inserted with the value <c>0 - post</c> rounded to two decimals,
/// and a <c>SpO2_Pre_Post</c> observation is inserted concatenating the raw pre-value and post-value.
/// </summary>
/// <!-- aidoc:v1 sig=36a4380 body=95adfa9 -->
[Test]
public async Task CalculateSaturation_DiffObservation_WithNonNumericPreValue_TreatsAsZero()
{
@@ -2382,6 +2447,11 @@ public class CalculatedObservationsTest
x => x.InsertObservation(It.IsAny<PatientObservation>(), It.IsAny<bool>(), It.IsAny<bool>()), Times.Never);
}
/// <summary>
/// Verifies that mapping a <see cref="PatientTreatment"/> whose <see cref="PatientTreatment.EndTime"/> is set yields a result with <see cref="OrderControlType.Dc"/>, overriding the order control that would otherwise be derived from the patient's previously active treatments.
/// </summary>
/// <returns>A <see cref="Task"/> that completes when the assertions have been evaluated.</returns>
/// <!-- aidoc:v1 sig=c43f82e body=7d24ee8 -->
[Test]
public async Task Map_TreatmentWithEndTimeSet_OverridesOrderControlFromOldTreatments()
{
@@ -2406,6 +2476,10 @@ public class CalculatedObservationsTest
Assert.That(result.OrderControl, Is.EqualTo(OrderControlType.Dc));
}
/// <summary>
/// Verifies that the map operation inserts an observation when the supplied <see cref="PatientTreatment"/> carries a medication code. The test arranges a treatment with a single requested give code, configures the treatment, medicine, and observation services to return an existing active treatment list, the matching <see cref="Medicine"/>, and no previous observations; then asserts that the resulting <see cref="OrderControlType"/> is <see cref="OrderControlType.Xo"/> and that the observation service is invoked once with a <see cref="PatientObservation"/> whose name equals "Medication_Multivalue" and whose value is a <see cref="String"/> array of length one containing the medication text.
/// </summary>
/// <!-- aidoc:v1 sig=cc4c529 body=a5d79cd -->
[Test]
public async Task Map_TreatmentWithMedication_InsertObservation()
{