Skip to content

Commit

Permalink
fix: kinetic conversion to blood when blood is not present as samplin…
Browse files Browse the repository at this point in the history
…g method in HBM data (#1959)
  • Loading branch information
Voorthuijsen, Tijmen van committed Jun 13, 2024
1 parent adb1fb7 commit 75990b0
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -29,8 +28,8 @@ public static List<HumanMonitoringSampleSubstanceCollection> FakeHbmSampleSubsta
var survey = FakeHbmSurvey(individualDays);
var hbmSamples = FakeHbmSamples(individualDays, substances, samplingMethod, concentrationUnit, lipidGravity);
var result = HumanMonitoringSampleSubstanceCollectionsBuilder.Create(
substances,
hbmSamples,
substances,
hbmSamples,
survey,
new()
);
Expand All @@ -49,7 +48,7 @@ public static List<HumanMonitoringSampleSubstanceCollection> FakeHbmSampleSubsta
double? lipidGravity = null
) {
var result = new List<HumanMonitoringSampleSubstanceCollection>();
foreach(var samplingMethod in samplingMethods) {
foreach (var samplingMethod in samplingMethods) {
result.AddRange(FakeHbmSampleSubstanceCollections(individualDays, substances, samplingMethod, concentrationUnit, lipidGravity));
}
return result;
Expand All @@ -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;
Expand All @@ -90,7 +89,7 @@ public static List<HumanMonitoringSample> FakeHbmSamples(
List<(SimulatedIndividualDay, Compound)> notAnalysedSampleCompounds = null
) {
var result = generateSurveyHumanMonitoringSamples(
individualDays,
individualDays,
substances,
samplingMethod,
concentrationUnit,
Expand All @@ -105,12 +104,6 @@ public static List<HumanMonitoringSample> FakeHbmSamples(
/// <summary>
/// Generates human monitoring individual day concentrations.
/// </summary>
/// <param name="individualDays"></param>
/// <param name="substances"></param>
/// <param name="samplingMethod"></param>
/// <param name="fractionZero"></param>
/// <param name="seed"></param>
/// <returns></returns>
public static ICollection<HbmIndividualDayConcentration> MockHumanMonitoringIndividualDayConcentrations(
ICollection<SimulatedIndividualDay> individualDays,
ICollection<Compound> substances,
Expand Down Expand Up @@ -146,10 +139,6 @@ public static ICollection<HbmIndividualDayConcentration> MockHumanMonitoringIndi
/// <summary>
/// Creates a fake monitoring sampling method.
/// </summary>
/// <param name="biologicalMatrix"></param>
/// <param name="exposureRoute"></param>
/// <param name="sampleType"></param>
/// <returns></returns>
public static HumanMonitoringSamplingMethod FakeHumanMonitoringSamplingMethod(
BiologicalMatrix biologicalMatrix = BiologicalMatrix.Blood,
string sampleType = "Spot",
Expand All @@ -165,9 +154,6 @@ public static HumanMonitoringSamplingMethod FakeHumanMonitoringSamplingMethod(
/// <summary>
/// Creates a fake kinetic conversion factor.
/// </summary>
/// <param name="biologicalMatrixFrom"></param>
/// <param name="biologicalMatrixTo"></param>
/// <returns></returns>
public static KineticConversionFactor FakeKineticConversionFactor(
BiologicalMatrix biologicalMatrixFrom,
BiologicalMatrix biologicalMatrixTo,
Expand Down Expand Up @@ -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,
Expand All @@ -210,14 +196,6 @@ public static KineticConversionFactorModel FakeKineticConversionFactorModel(
/// <summary>
/// Generates human monitoring individual day concentrations.
/// </summary>
/// <param name="simulatedIndividualDays"></param>
/// <param name="compounds"></param>
/// <param name="fractionZero"></param>
/// <param name="biologicalMatrix"></param>
/// <param name="exposureRoute"></param>
/// <param name="sampleType"></param>
/// <param name="seed"></param>
/// <returns></returns>
public static ICollection<HbmIndividualConcentration> MockHumanMonitoringIndividualConcentrations(
ICollection<Individual> individuals,
ICollection<Compound> compounds,
Expand Down Expand Up @@ -281,11 +259,11 @@ private static List<HumanMonitoringSample> 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}",
Expand All @@ -299,7 +277,7 @@ private static List<HumanMonitoringSample> generateSurveyHumanMonitoringSamples(
Compound = c,
ResTypeString = ResType.VAL.ToString(),
Sample = sample
});
});

sampleAnalyses.Add(new SampleAnalysis() {
Code = $"humanMonitoringSampleAnalysis_{sampleCounter}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class HbmMultipleTargetExtrapolationCalculatorTests {
///
/// </summary>
[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);
Expand Down Expand Up @@ -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<KineticConversionFactorModel> {
kineticConversionFactorModelCmp0,
kineticConversionFactorModelCmp1,
Expand Down Expand Up @@ -157,5 +155,137 @@ void AssertConversion(
AssertConversion(hairCollection, samplesUrine, new ExposureTarget(BiologicalMatrix.Hair),
new ExposureTarget(BiologicalMatrix.Urine), substances[5]);
}

/// <summary>
/// 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
///
/// </summary>
[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<Compound> 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<HbmIndividualDayCollection> {
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<KineticConversionFactorModel> {
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<HbmIndividualDayConcentration> 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]);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ public HbmIndividualDayCollection Clone() {
};
}

public override string ToString() {
return TargetUnit.ToString() + $" ({nameof(HbmIndividualDayConcentration)}(s): {HbmIndividualDayConcentrations.Count})";
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,41 @@ ICollection<Compound> substances
.Select(m => m.ConversionRule.TargetTo)
.Distinct()
.ToList();
var convertedHbmIndividualDayCollections = new List<HbmIndividualDayCollection>();
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;
}
Expand Down

0 comments on commit 75990b0

Please sign in to comment.