diff --git a/MCRA.Simulation.Test/Mock/MockDataGenerators/FakeHbmDataGenerator.cs b/MCRA.Simulation.Test/Mock/MockDataGenerators/FakeHbmDataGenerator.cs index 299b39b45..a4f4c0046 100644 --- a/MCRA.Simulation.Test/Mock/MockDataGenerators/FakeHbmDataGenerator.cs +++ b/MCRA.Simulation.Test/Mock/MockDataGenerators/FakeHbmDataGenerator.cs @@ -1,5 +1,4 @@ -using System.Linq; -using MCRA.Data.Compiled.Objects; +using MCRA.Data.Compiled.Objects; using MCRA.Data.Compiled.Wrappers; using MCRA.General; using MCRA.Simulation.Calculators.HumanMonitoringCalculation; @@ -29,8 +28,8 @@ public static List FakeHbmSampleSubsta var survey = FakeHbmSurvey(individualDays); var hbmSamples = FakeHbmSamples(individualDays, substances, samplingMethod, concentrationUnit, lipidGravity); var result = HumanMonitoringSampleSubstanceCollectionsBuilder.Create( - substances, - hbmSamples, + substances, + hbmSamples, survey, new() ); @@ -49,7 +48,7 @@ public static List FakeHbmSampleSubsta double? lipidGravity = null ) { var result = new List(); - foreach(var samplingMethod in samplingMethods) { + foreach (var samplingMethod in samplingMethods) { result.AddRange(FakeHbmSampleSubstanceCollections(individualDays, substances, samplingMethod, concentrationUnit, lipidGravity)); } return result; @@ -70,7 +69,7 @@ public static HumanMonitoringSurvey FakeHbmSurvey( NumberOfSurveyDays = numberOfSurveyDays, Timepoints = Enumerable .Range(0, numberOfSurveyDays) - .Select(i => new HumanMonitoringTimepoint { Code = $"{i}", Name = $"Timepoint {i}", Description = $"Description of timepoint {i}"}) + .Select(i => new HumanMonitoringTimepoint { Code = $"{i}", Name = $"Timepoint {i}", Description = $"Description of timepoint {i}" }) .ToList(), }; return result; @@ -90,7 +89,7 @@ public static List FakeHbmSamples( List<(SimulatedIndividualDay, Compound)> notAnalysedSampleCompounds = null ) { var result = generateSurveyHumanMonitoringSamples( - individualDays, + individualDays, substances, samplingMethod, concentrationUnit, @@ -105,12 +104,6 @@ public static List FakeHbmSamples( /// /// Generates human monitoring individual day concentrations. /// - /// - /// - /// - /// - /// - /// public static ICollection MockHumanMonitoringIndividualDayConcentrations( ICollection individualDays, ICollection substances, @@ -146,10 +139,6 @@ public static ICollection MockHumanMonitoringIndi /// /// Creates a fake monitoring sampling method. /// - /// - /// - /// - /// public static HumanMonitoringSamplingMethod FakeHumanMonitoringSamplingMethod( BiologicalMatrix biologicalMatrix = BiologicalMatrix.Blood, string sampleType = "Spot", @@ -165,9 +154,6 @@ public static HumanMonitoringSamplingMethod FakeHumanMonitoringSamplingMethod( /// /// Creates a fake kinetic conversion factor. /// - /// - /// - /// public static KineticConversionFactor FakeKineticConversionFactor( BiologicalMatrix biologicalMatrixFrom, BiologicalMatrix biologicalMatrixTo, @@ -198,8 +184,8 @@ public static KineticConversionFactorModel FakeKineticConversionFactorModel( biologicalMatrixTo, substance ); - kineticConversionFactor.DoseUnitTo = ExposureUnitTriple.FromDoseUnit(doseUnitFrom); - kineticConversionFactor.DoseUnitFrom = ExposureUnitTriple.FromDoseUnit(doseUnitTo); + kineticConversionFactor.DoseUnitFrom = ExposureUnitTriple.FromDoseUnit(doseUnitFrom); + kineticConversionFactor.DoseUnitTo = ExposureUnitTriple.FromDoseUnit(doseUnitTo); return KineticConversionFactorCalculatorFactory.Create( conversion: kineticConversionFactor, useSubgroups: false, @@ -210,14 +196,6 @@ public static KineticConversionFactorModel FakeKineticConversionFactorModel( /// /// Generates human monitoring individual day concentrations. /// - /// - /// - /// - /// - /// - /// - /// - /// public static ICollection MockHumanMonitoringIndividualConcentrations( ICollection individuals, ICollection compounds, @@ -281,11 +259,11 @@ private static List generateSurveyHumanMonitoringSamples( AnalyticalMethodCompounds = substances .Where(s => !notAnalysedSampleCompounds?.Contains((r, s)) ?? true) .ToDictionary(c => c, c => new AnalyticalMethodCompound() { - Compound = c, - LOD = 0.05, - LOQ = 0.05, - ConcentrationUnitString = concentrationUnit.ToString(), - }) + Compound = c, + LOD = 0.05, + LOQ = 0.05, + ConcentrationUnitString = concentrationUnit.ToString(), + }) }; var sample = new SampleAnalysis() { Code = $"S{sampleCounter}", @@ -299,7 +277,7 @@ private static List generateSurveyHumanMonitoringSamples( Compound = c, ResTypeString = ResType.VAL.ToString(), Sample = sample - }); + }); sampleAnalyses.Add(new SampleAnalysis() { Code = $"humanMonitoringSampleAnalysis_{sampleCounter}", diff --git a/MCRA.Simulation.Test/UnitTests/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculatorTests.cs b/MCRA.Simulation.Test/UnitTests/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculatorTests.cs index bfefcdc3f..74bfa9487 100644 --- a/MCRA.Simulation.Test/UnitTests/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculatorTests.cs +++ b/MCRA.Simulation.Test/UnitTests/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculatorTests.cs @@ -40,7 +40,7 @@ public class HbmMultipleTargetExtrapolationCalculatorTests { /// /// [TestMethod] - public void HumanMonitoringAnalysisActionCalculator_ApplyKineticConversionNotSingleTarget_ShouldApplyConversionForSpecificBiomarkers() { + public void HbmMultipleTargetExtrapolationCalculator_ApplyKineticConversionNotSingleTarget_ShouldApplyConversionForSpecificBiomarkers() { // Arrange var individuals = MockIndividualsGenerator.Create(1, 1, new McraRandomGenerator(1), useSamplingWeights: false); var individualDays = MockIndividualDaysGenerator.CreateSimulatedIndividualDays(individuals); @@ -85,11 +85,9 @@ public void HumanMonitoringAnalysisActionCalculator_ApplyKineticConversionNotSin var kineticConversionFactorModelCmp3 = FakeHbmDataGenerator .FakeKineticConversionFactorModel(BiologicalMatrix.Urine, BiologicalMatrix.Blood, substances[3]); var kineticConversionFactorModelCmp5b = FakeHbmDataGenerator - .FakeKineticConversionFactorModel(BiologicalMatrix.Hair, BiologicalMatrix.Blood, - doseUnitTo: DoseUnit.ugPerg, substance: substances[5]); + .FakeKineticConversionFactorModel(BiologicalMatrix.Hair, BiologicalMatrix.Blood, substances[5], DoseUnit.ugPerg); var kineticConversionFactorModelCmp5u = FakeHbmDataGenerator - .FakeKineticConversionFactorModel(BiologicalMatrix.Hair, BiologicalMatrix.Urine, - doseUnitTo: DoseUnit.ugPerg, substance: substances[5]); + .FakeKineticConversionFactorModel(BiologicalMatrix.Hair, BiologicalMatrix.Urine, substances[5], DoseUnit.ugPerg); var kineticConversionFactorModels = new List { kineticConversionFactorModelCmp0, kineticConversionFactorModelCmp1, @@ -157,5 +155,137 @@ void AssertConversion( AssertConversion(hairCollection, samplesUrine, new ExposureTarget(BiologicalMatrix.Hair), new ExposureTarget(BiologicalMatrix.Urine), substances[5]); } + + /// + /// Kinetic conversion to a matrix that is not present in the source data sampling methods. + /// + /// Example: + /// HBM CodeBook contains hair and urine samples but no blood. Kinetic conversion factors define a factor for + /// conversion from hair to blood. This should result in an added collection for blood. + /// + /// BEFORE + /// ------------------------- + /// Hair 0 1 2 - + /// Urine - - - 3 + /// + /// AFTER + /// ------------------------- + /// Hair 0 1 2 - + /// Urine - - - 3 + /// Blood 0 - 2 - + /// + /// Conversion factors, no conversion defined for substance 1 + /// CMP0 hair --> blood + /// CMP2 hair --> blood + /// + /// + [TestMethod] + public void HbmMultipleTargetExtrapolationCalculator_KineticConversionToMissingTargetMatrix_ShouldAddMatrixFromConversionFactors() { + // Arrange + var individuals = MockIndividualsGenerator.Create(1, 1, new McraRandomGenerator(1), useSamplingWeights: false); + var individualDays = MockIndividualDaysGenerator.CreateSimulatedIndividualDays(individuals); + var substances = MockSubstancesGenerator.Create(new[] { "cmp0", "cmp1", "cmp2", "cmp3" }); + var substancesHair = substances.Take(3).ToList(); + var substancesUrine = substances.TakeLast(1).ToList(); + var targetHair = new ExposureTarget(BiologicalMatrix.Hair); + var targetUrine = new ExposureTarget(BiologicalMatrix.Urine); + + var scenarios = new List<(ExposureTarget Target, List Substances)> { + (targetHair, substancesHair), + (targetUrine, substancesUrine) + }; + + var random = new McraRandomGenerator(seed: 1); + var hbmIndividualDayCollections = scenarios + .SelectMany(r => { + var matrix = r.Target.BiologicalMatrix; + var targetUnit = new TargetUnit( + r.Target, + matrix.GetTargetConcentrationUnit().GetSubstanceAmountUnit(), + matrix.GetTargetConcentrationUnit().GetConcentrationMassUnit(), + TimeScaleUnit.Unspecified + ); + var result = new List { + FakeHbmIndividualDayConcentrationsGenerator + .Create( + individualDays, + r.Substances, + null, + targetUnit, + random + ) }; + return result; + }) + .ToList(); + + var kineticConversionFactorModelCmp0 = FakeHbmDataGenerator + .FakeKineticConversionFactorModel( + BiologicalMatrix.Hair, + BiologicalMatrix.Blood, + substances[0], + DoseUnit.ugPerg, + DoseUnit.ugPerL + ); + var kineticConversionFactorModelCmp2 = FakeHbmDataGenerator + .FakeKineticConversionFactorModel( + BiologicalMatrix.Hair, + BiologicalMatrix.Blood, + substances[2], + DoseUnit.ugPerg, + DoseUnit.ugPerL + ); + var kineticConversionFactorModels = new List { + kineticConversionFactorModelCmp0, + kineticConversionFactorModelCmp2 + }; + + // Act + var result = HbmMultipleTargetExtrapolationCalculator + .Calculate( + hbmIndividualDayCollections, + kineticConversionFactorModels, + individualDays, + substances + ); + + // Assert + var targetBlood = new ExposureTarget(BiologicalMatrix.Blood); + Assert.IsNotNull(result.Any(c => c.Target == targetHair)); + Assert.IsNotNull(result.Any(c => c.Target == targetUrine)); + Assert.IsNotNull(result.Any(c => c.Target == targetBlood)); + var samplesBlood = result + .FirstOrDefault(h => h.Target == targetBlood)?.HbmIndividualDayConcentrations; + Assert.IsTrue(samplesBlood.All(s => s.Substances.Any(s => s == substances[0]))); + Assert.IsTrue(samplesBlood.All(s => s.Substances.Any(s => s == substances[2]))); + + void AssertConversion( + HbmIndividualDayCollection collectionFrom, + ICollection collectionTo, + ExposureTarget targetExposureFrom, + ExposureTarget targetExposureTo, + Compound substance) { + var recordFrom = collectionFrom.HbmIndividualDayConcentrations + .FirstOrDefault(r => r.Individual.Code == "0" && r.Day == "0"); + var recordTo = collectionTo.FirstOrDefault(r => r.Individual.Code == "0" && r.Day == "0"); + var valueFrom = recordFrom.ConcentrationsBySubstance[substance].Exposure; + var valueTo = recordTo.GetExposureForSubstance(substance); + var conversionFactor = kineticConversionFactorModels + .FirstOrDefault( + k => k.ConversionRule.SubstanceFrom == substance + && k.ConversionRule.SubstanceTo == substance + && k.ConversionRule.TargetFrom == targetExposureFrom + && k.ConversionRule.TargetTo == targetExposureTo + ); + + var conversionFactorExpected = conversionFactor.ConversionRule.ConversionFactor; + var conversionFactorMeasured = valueTo / valueFrom; + + Assert.AreEqual(conversionFactorExpected, conversionFactorMeasured, 0.001); + }; + + var hairCollection = hbmIndividualDayCollections.First(r => r.Target == targetHair); + AssertConversion(hairCollection, samplesBlood, targetHair, targetBlood, substances[0]); + AssertConversion(hairCollection, samplesBlood, targetHair, targetBlood, substances[2]); + } } } diff --git a/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmIndividualDayConcentrationsCalculation/HbmIndividualDayCollection.cs b/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmIndividualDayConcentrationsCalculation/HbmIndividualDayCollection.cs index e5e128e29..9a0911ba7 100644 --- a/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmIndividualDayConcentrationsCalculation/HbmIndividualDayCollection.cs +++ b/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmIndividualDayConcentrationsCalculation/HbmIndividualDayCollection.cs @@ -31,5 +31,9 @@ public HbmIndividualDayCollection Clone() { }; } + public override string ToString() { + return TargetUnit.ToString() + $" ({nameof(HbmIndividualDayConcentration)}(s): {HbmIndividualDayConcentrations.Count})"; + } + } } diff --git a/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculator.cs b/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculator.cs index f74592693..b0c00160e 100644 --- a/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculator.cs +++ b/MCRA.Simulation/Calculators/HumanMonitoringCalculation/HbmTargetMatrixExtrapolation/HbmMultipleTargetExtrapolationCalculator.cs @@ -16,36 +16,41 @@ ICollection substances .Select(m => m.ConversionRule.TargetTo) .Distinct() .ToList(); - var convertedHbmIndividualDayCollections = new List(); - foreach (var collection in hbmIndividualDayCollections) { - if (targetsTo.Contains(collection.Target)) { - var collectionTo = collection; - var matrixConversionCalculator = new TargetMatrixKineticConversionCalculator( - kineticConversionFactorModels, - collectionTo.TargetUnit); - var monitoringOtherIndividualDayCalculator = new HbmIndividualDayMatrixExtrapolationCalculator( - matrixConversionCalculator - ); - var conversionModelsFrom = kineticConversionFactorModels - .Where(m => m.ConversionRule.TargetTo == collectionTo.Target) - .Select(m => m.ConversionRule.TargetFrom) - .ToList(); - var collectionsFrom = hbmIndividualDayCollections - .Where(c => conversionModelsFrom.Contains(c.Target)) - .ToList(); + var convertedHbmIndividualDayCollections = hbmIndividualDayCollections + .Where(c => !targetsTo.Contains(c.Target)) + .ToList(); + foreach (var targetTo in targetsTo) { + var collectionTo = hbmIndividualDayCollections.FirstOrDefault(c => c.Target == targetTo); + if (collectionTo == null) { + collectionTo = HbmIndividualDayConcentrationsCalculator + .CreateDefaultHbmIndividualDayCollection(simulatedIndividualDays, targetTo); + } + + var matrixConversionCalculator = new TargetMatrixKineticConversionCalculator( + kineticConversionFactorModels, + collectionTo.TargetUnit); + var monitoringOtherIndividualDayCalculator = new HbmIndividualDayMatrixExtrapolationCalculator( + matrixConversionCalculator + ); - collectionTo = monitoringOtherIndividualDayCalculator + var conversionModelsFrom = kineticConversionFactorModels + .Where(m => m.ConversionRule.TargetTo == collectionTo.Target) + .Select(m => m.ConversionRule.TargetFrom) + .ToList(); + var collectionsFrom = hbmIndividualDayCollections + .Where(c => conversionModelsFrom.Contains(c.Target)) + .ToList(); + + collectionTo = monitoringOtherIndividualDayCalculator .Calculate( collectionTo, collectionsFrom, simulatedIndividualDays, substances ); - convertedHbmIndividualDayCollections.Add(collectionTo); - } else { - convertedHbmIndividualDayCollections.Add(collection); - } + + convertedHbmIndividualDayCollections.Add(collectionTo); } return convertedHbmIndividualDayCollections; }