2024-04-11 15:51:56 +02:00
2024-06-15 02:43:01 +02:00
// createCoupleGeneticAnalysis provides functions to create a Couple genetic analysis
// Couple analyses provide an analysis of the prospective offspring of a pair of people
// These analyses are performed on one or more genome files.
// Analyses contain 3 categories of results: Monogenic Diseases, Polygenic Diseases and Traits
// Use createPersonGeneticAnalysis.go to create Person analyses
2024-04-11 15:51:56 +02:00
2024-06-15 02:43:01 +02:00
package createCoupleGeneticAnalysis
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
// Disclaimer: I am a novice in the ways of genetics. This package could be flawed in numerous ways.
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
// TODO: We want to eventually use neural nets for polygenic disease analysis (see geneticPrediction.go)
2024-04-11 15:51:56 +02:00
// This is only possible once we get access to the necessary training data
2024-07-19 19:16:28 +02:00
import "seekia/resources/geneticPredictionModels"
2024-04-11 15:51:56 +02:00
import "seekia/resources/geneticReferences/locusMetadata"
import "seekia/resources/geneticReferences/monogenicDiseases"
import "seekia/resources/geneticReferences/polygenicDiseases"
import "seekia/resources/geneticReferences/traits"
2024-06-02 10:43:39 +02:00
import "seekia/internal/encoding"
2024-06-15 02:43:01 +02:00
import "seekia/internal/genetics/createPersonGeneticAnalysis"
2024-06-02 10:43:39 +02:00
import "seekia/internal/genetics/geneticAnalysis"
2024-04-11 15:51:56 +02:00
import "seekia/internal/genetics/locusValue"
import "seekia/internal/genetics/prepareRawGenomes"
import "seekia/internal/helpers"
import "errors"
2024-06-05 06:10:35 +02:00
import mathRand "math/rand/v2"
2024-04-11 15:51:56 +02:00
import "slices"
2024-06-02 10:43:39 +02:00
import "maps"
2024-07-19 19:16:28 +02:00
import "reflect"
2024-04-11 15:51:56 +02:00
//Outputs:
// -bool: Process completed (was not manually stopped mid-way)
2024-06-02 10:43:39 +02:00
// -string: Couple genetic analysis string (encoded in MessagePack)
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
func CreateCoupleGeneticAnalysis ( person1GenomesList [ ] prepareRawGenomes . RawGenomeWithMetadata , person2GenomesList [ ] prepareRawGenomes . RawGenomeWithMetadata , updatePercentageCompleteFunction func ( int ) error , checkIfProcessIsStopped func ( ) bool ) ( bool , string , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func ( newPercentage int ) error {
2024-04-11 15:51:56 +02:00
newPercentageCompletion , err := helpers . ScaleNumberProportionally ( true , newPercentage , 0 , 100 , 0 , 25 )
if ( err != nil ) { return err }
err = updatePercentageCompleteFunction ( newPercentageCompletion )
if ( err != nil ) { return err }
return nil
}
2024-06-02 10:43:39 +02:00
person1GenomesWithMetadataList , allPerson1RawGenomeIdentifiersList , person1HasMultipleGenomes , person1OnlyExcludeConflictsGenomeIdentifier , person1OnlyIncludeSharedGenomeIdentifier , err := prepareRawGenomes . GetGenomesWithMetadataListFromRawGenomesList ( person1GenomesList , person1PrepareRawGenomesUpdatePercentageCompleteFunction )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
processIsStopped := checkIfProcessIsStopped ( )
if ( processIsStopped == true ) {
return false , "" , nil
}
2024-06-02 10:43:39 +02:00
person2PrepareRawGenomesUpdatePercentageCompleteFunction := func ( newPercentage int ) error {
2024-04-11 15:51:56 +02:00
newPercentageCompletion , err := helpers . ScaleNumberProportionally ( true , newPercentage , 0 , 100 , 25 , 50 )
if ( err != nil ) { return err }
err = updatePercentageCompleteFunction ( newPercentageCompletion )
if ( err != nil ) { return err }
return nil
}
2024-06-02 10:43:39 +02:00
person2GenomesWithMetadataList , allPerson2RawGenomeIdentifiersList , person2HasMultipleGenomes , person2OnlyExcludeConflictsGenomeIdentifier , person2OnlyIncludeSharedGenomeIdentifier , err := prepareRawGenomes . GetGenomesWithMetadataListFromRawGenomesList ( person2GenomesList , person2PrepareRawGenomesUpdatePercentageCompleteFunction )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
processIsStopped = checkIfProcessIsStopped ( )
if ( processIsStopped == true ) {
return false , "" , nil
}
2024-07-19 19:16:28 +02:00
// This map stores each genome's locus values
// Map Structure: Genome Identifier -> Genome locus values map (rsID -> Locus Value)
person1GenomesMap := make ( map [ [ 16 ] byte ] map [ int64 ] locusValue . LocusValue )
for _ , genomeWithMetadata := range person1GenomesWithMetadataList {
genomeIdentifier := genomeWithMetadata . GenomeIdentifier
genomeMap := genomeWithMetadata . GenomeMap
person1GenomesMap [ genomeIdentifier ] = genomeMap
}
// This map stores each genome's locus values
// Map Structure: Genome Identifier -> Genome locus values map (rsID -> Locus Value)
person2GenomesMap := make ( map [ [ 16 ] byte ] map [ int64 ] locusValue . LocusValue )
for _ , genomeWithMetadata := range person2GenomesWithMetadataList {
genomeIdentifier := genomeWithMetadata . GenomeIdentifier
genomeMap := genomeWithMetadata . GenomeMap
person2GenomesMap [ genomeIdentifier ] = genomeMap
}
2024-04-11 15:51:56 +02:00
// The analysis will analyze either 1 or 2 genome pairs
// The gui will display the results from each pair
//Outputs:
2024-06-02 10:43:39 +02:00
// -[16]byte: Pair 1 Person 1 Genome Identifier
// -[16]byte: Pair 1 Person 2 Genome Identifier
2024-04-11 15:51:56 +02:00
// -bool: Second pair exists
2024-06-02 10:43:39 +02:00
// -[16]byte: Pair 2 Person 1 Genome Identifier
// -[16]byte: Pair 2 Person 2 Genome Identifier
2024-04-11 15:51:56 +02:00
// -error
2024-06-02 10:43:39 +02:00
getGenomePairsToAnalyze := func ( ) ( [ 16 ] byte , [ 16 ] byte , bool , [ 16 ] byte , [ 16 ] byte , error ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == true && person2HasMultipleGenomes == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1OnlyExcludeConflictsGenomeIdentifier , person2OnlyExcludeConflictsGenomeIdentifier , true , person1OnlyIncludeSharedGenomeIdentifier , person2OnlyIncludeSharedGenomeIdentifier , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == true && person2HasMultipleGenomes == false ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2GenomeIdentifier := allPerson2RawGenomeIdentifiersList [ 0 ]
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1OnlyExcludeConflictsGenomeIdentifier , person2GenomeIdentifier , true , person1OnlyIncludeSharedGenomeIdentifier , person2GenomeIdentifier , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == false && person2HasMultipleGenomes == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1GenomeIdentifier := allPerson1RawGenomeIdentifiersList [ 0 ]
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1GenomeIdentifier , person2OnlyExcludeConflictsGenomeIdentifier , true , person1GenomeIdentifier , person2OnlyIncludeSharedGenomeIdentifier , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
//person1HasMultipleGenomes == false && person2HasMultipleGenomes == false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person1GenomeIdentifier := allPerson1RawGenomeIdentifiersList [ 0 ]
person2GenomeIdentifier := allPerson2RawGenomeIdentifiersList [ 0 ]
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return person1GenomeIdentifier , person2GenomeIdentifier , false , [ 16 ] byte { } , [ 16 ] byte { } , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier , genomePair2Exists , pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier , err := getGenomePairsToAnalyze ( )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newCoupleAnalysis := geneticAnalysis . CoupleAnalysis {
AnalysisVersion : 1 ,
Pair1Person1GenomeIdentifier : pair1Person1GenomeIdentifier ,
Pair1Person2GenomeIdentifier : pair1Person2GenomeIdentifier ,
SecondPairExists : genomePair2Exists ,
Person1HasMultipleGenomes : person1HasMultipleGenomes ,
Person2HasMultipleGenomes : person2HasMultipleGenomes ,
2024-04-11 15:51:56 +02:00
}
if ( genomePair2Exists == true ) {
// At least 1 of the people have multiple genomes
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . Pair2Person1GenomeIdentifier = pair2Person1GenomeIdentifier
newCoupleAnalysis . Pair2Person2GenomeIdentifier = pair2Person2GenomeIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1HasMultipleGenomes == true ) {
newCoupleAnalysis . Person1OnlyExcludeConflictsGenomeIdentifier = person1OnlyExcludeConflictsGenomeIdentifier
newCoupleAnalysis . Person1OnlyIncludeSharedGenomeIdentifier = person1OnlyIncludeSharedGenomeIdentifier
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
if ( person2HasMultipleGenomes == true ) {
newCoupleAnalysis . Person2OnlyExcludeConflictsGenomeIdentifier = person2OnlyExcludeConflictsGenomeIdentifier
newCoupleAnalysis . Person2OnlyIncludeSharedGenomeIdentifier = person2OnlyIncludeSharedGenomeIdentifier
2024-04-11 15:51:56 +02:00
}
}
2024-06-02 10:43:39 +02:00
// We compute and add monogenic disease probabilities
2024-04-11 15:51:56 +02:00
monogenicDiseasesList , err := monogenicDiseases . GetMonogenicDiseaseObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Disease Name -> OffspringMonogenicDiseaseInfo
offspringMonogenicDiseasesMap := make ( map [ string ] geneticAnalysis . OffspringMonogenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
for _ , diseaseObject := range monogenicDiseasesList {
diseaseName := diseaseObject . DiseaseName
variantsList := diseaseObject . VariantsList
diseaseIsDominantOrRecessive := diseaseObject . DominantOrRecessive
2024-06-15 02:43:01 +02:00
person1DiseaseAnalysisObject , err := createPersonGeneticAnalysis . GetPersonMonogenicDiseaseAnalysis ( person1GenomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-15 02:43:01 +02:00
person2DiseaseAnalysisObject , err := createPersonGeneticAnalysis . GetPersonMonogenicDiseaseAnalysis ( person2GenomesWithMetadataList , diseaseObject )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-07-19 19:16:28 +02:00
// This map stores the quantity of variants tested in each person's genome
2024-06-02 10:43:39 +02:00
// Map Structure: Genome Identifier -> Number of variants tested
2024-07-19 19:16:28 +02:00
quantityOfVariantsTestedMap := make ( map [ [ 16 ] byte ] int )
2024-06-02 10:43:39 +02:00
// This map stores the offspring disease probabilities for each genome pair.
// A genome pair is a concatenation of two genome identifiers
// If a map entry doesn't exist, the probabilities are unknown for that genome pair
// Map Structure: Genome Pair Identifier -> OffspringMonogenicDiseaseProbabilities
offspringMonogenicDiseaseInfoMap := make ( map [ [ 32 ] byte ] geneticAnalysis . OffspringGenomePairMonogenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
// This will calculate the probability of monogenic disease for the offspring from the two specified genomes
2024-06-02 10:43:39 +02:00
// It also calculates the probabilities for each monogenic disease variant for the offspring
2024-07-19 19:16:28 +02:00
// It then adds the genome pair disease information to the offspringMonogenicDiseaseInfoMap and quantityOfVariantsTestedMap
2024-06-02 10:43:39 +02:00
addGenomePairInfoToDiseaseMaps := func ( person1GenomeIdentifier [ 16 ] byte , person2GenomeIdentifier [ 16 ] byte ) error {
2024-04-11 15:51:56 +02:00
//Outputs:
// -bool: Probability is known
// -int: Probability of passing a disease variant (value between 0 and 100)
// -int: Number of variants tested
2024-06-02 10:43:39 +02:00
getPersonWillPassDiseaseVariantProbability := func ( personDiseaseAnalysisObject geneticAnalysis . PersonMonogenicDiseaseInfo , genomeIdentifier [ 16 ] byte ) ( bool , int , int ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personDiseaseInfoMap := personDiseaseAnalysisObject . MonogenicDiseaseInfoMap
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
personGenomeDiseaseInfoObject , exists := personDiseaseInfoMap [ genomeIdentifier ]
2024-04-11 15:51:56 +02:00
if ( exists == false ) {
2024-06-02 10:43:39 +02:00
return false , 0 , 0
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
personGenomeProbabilityOfPassingADiseaseVariant := personGenomeDiseaseInfoObject . ProbabilityOfPassingADiseaseVariant
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
personGenomeQuantityOfVariantsTested := personGenomeDiseaseInfoObject . QuantityOfVariantsTested
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
return true , personGenomeProbabilityOfPassingADiseaseVariant , personGenomeQuantityOfVariantsTested
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
person1ProbabilityIsKnown , person1WillPassVariantProbability , person1NumberOfVariantsTested := getPersonWillPassDiseaseVariantProbability ( person1DiseaseAnalysisObject , person1GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
person2ProbabilityIsKnown , person2WillPassVariantProbability , person2NumberOfVariantsTested := getPersonWillPassDiseaseVariantProbability ( person2DiseaseAnalysisObject , person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringHasDiseaseProbabilityIsKnown , percentageProbabilityOffspringHasDisease , offspringHasAVariantProbabilityIsKnown , percentageProbabilityOffspringHasVariant , err := GetOffspringMonogenicDiseaseProbabilities ( diseaseIsDominantOrRecessive , person1ProbabilityIsKnown , person1WillPassVariantProbability , person2ProbabilityIsKnown , person2WillPassVariantProbability )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Now we calculate the probabilities for individual variants
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringMonogenicDiseaseVariantInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , variantObject := range variantsList {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantIdentifierHex := variantObject . VariantIdentifier
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
variantIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( variantIdentifierHex )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
//Outputs:
// -bool: Probability is known
// -float64: Probability that person will pass variant to offspring (between 0 and 1)
2024-04-11 15:51:56 +02:00
// -error
2024-06-05 06:10:35 +02:00
getPersonWillPassVariantProbability := func ( personDiseaseAnalysisObject geneticAnalysis . PersonMonogenicDiseaseInfo , personGenomeIdentifier [ 16 ] byte ) ( bool , float64 , error ) {
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
personDiseaseInfoMap := personDiseaseAnalysisObject . MonogenicDiseaseInfoMap
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
personGenomeDiseaseInfoObject , exists := personDiseaseInfoMap [ personGenomeIdentifier ]
if ( exists == false ) {
return false , 0 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
personGenomeDiseaseVariantsMap := personGenomeDiseaseInfoObject . VariantsInfoMap
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
personVariantInfoObject , exists := personGenomeDiseaseVariantsMap [ variantIdentifier ]
if ( exists == false ) {
// The genome does not have information about this variant
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
return false , 0 , nil
}
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
base1HasVariant := personVariantInfoObject . Base1HasVariant
base2HasVariant := personVariantInfoObject . Base2HasVariant
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
if ( base1HasVariant == true && base2HasVariant == true ) {
return true , 1 , nil
}
if ( base1HasVariant == true && base2HasVariant == false ) {
return true , 0.5 , nil
}
if ( base1HasVariant == false && base2HasVariant == true ) {
return true , 0.5 , nil
2024-04-11 15:51:56 +02:00
}
2024-06-05 06:10:35 +02:00
// Neither base has a variant
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
return true , 0 , nil
}
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
person1VariantProbabilityIsKnown , person1WillPassVariantProbability , err := getPersonWillPassVariantProbability ( person1DiseaseAnalysisObject , person1GenomeIdentifier )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
person2VariantProbabilityIsKnown , person2WillPassVariantProbability , err := getPersonWillPassVariantProbability ( person2DiseaseAnalysisObject , person2GenomeIdentifier )
if ( err != nil ) { return err }
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
if ( person1VariantProbabilityIsKnown == false && person2VariantProbabilityIsKnown == false ) {
continue
}
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
// Outputs:
// -float64: Best Case Person 1 will pass variant probability (0-1)
// -float64: Worst Case Person 1 will pass variant probability (0-1)
// -float64: Best Case Person 2 will pass variant probability (0-1)
// -float64: Worst Case Person 2 will pass variant probability (0-1)
getBestAndWorstCaseProbabilities := func ( ) ( float64 , float64 , float64 , float64 ) {
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
if ( person1VariantProbabilityIsKnown == true && person2VariantProbabilityIsKnown == false ) {
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
return person1WillPassVariantProbability , person1WillPassVariantProbability , float64 ( 0 ) , float64 ( 1 )
}
if ( person1VariantProbabilityIsKnown == false && person2VariantProbabilityIsKnown == true ) {
return float64 ( 0 ) , float64 ( 1 ) , person2WillPassVariantProbability , person2WillPassVariantProbability
2024-04-11 15:51:56 +02:00
}
2024-06-05 06:10:35 +02:00
// Both people's probabilities are known
return person1WillPassVariantProbability , person1WillPassVariantProbability , person2WillPassVariantProbability , person2WillPassVariantProbability
}
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
bestCasePerson1WillPassVariantProbability , worstCasePerson1WillPassVariantProbability , bestCasePerson2WillPassVariantProbability , worstCasePerson2WillPassVariantProbability := getBestAndWorstCaseProbabilities ( )
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
//Outputs:
// -int: Percentage Probability of 0 mutations
// -int: Percentage Probability of 1 mutation
// -int: Percentage Probability of 2 mutations
// -error
getOffspringVariantMutationProbabilities := func ( person1WillPassVariantProbability float64 , person2WillPassVariantProbability float64 ) ( int , int , int , error ) {
// This is the probability that neither person will pass the variant
// P = P(!A) * P(!B)
probabilityOf0Mutations := ( 1 - person1WillPassVariantProbability ) * ( 1 - person2WillPassVariantProbability )
// This is the probability that either person will pass the variant, but not both
// P(A XOR B) = P(A) + P(B) - (2 * P(A and B))
probabilityOf1Mutation := person1WillPassVariantProbability + person2WillPassVariantProbability - ( 2 * person1WillPassVariantProbability * person2WillPassVariantProbability )
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
// This is the probability that both people will pass the variant
// P(A and B) = P(A) * P(B)
probabilityOf2Mutations := person1WillPassVariantProbability * person2WillPassVariantProbability
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
percentageProbabilityOf0Mutations , err := helpers . FloorFloat64ToInt ( probabilityOf0Mutations * 100 )
if ( err != nil ) { return 0 , 0 , 0 , err }
percentageProbabilityOf1Mutation , err := helpers . FloorFloat64ToInt ( probabilityOf1Mutation * 100 )
if ( err != nil ) { return 0 , 0 , 0 , err }
percentageProbabilityOf2Mutations , err := helpers . FloorFloat64ToInt ( probabilityOf2Mutations * 100 )
if ( err != nil ) { return 0 , 0 , 0 , err }
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
return percentageProbabilityOf0Mutations , percentageProbabilityOf1Mutation , percentageProbabilityOf2Mutations , nil
2024-04-11 15:51:56 +02:00
}
2024-06-05 06:10:35 +02:00
bestCase0MutationsProbability , bestCase1MutationProbability , bestCase2MutationsProbability , err := getOffspringVariantMutationProbabilities ( bestCasePerson1WillPassVariantProbability , bestCasePerson2WillPassVariantProbability )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return err }
2024-06-05 06:10:35 +02:00
worstCase0MutationsProbability , worstCase1MutationProbability , worstCase2MutationsProbability , err := getOffspringVariantMutationProbabilities ( worstCasePerson1WillPassVariantProbability , worstCasePerson2WillPassVariantProbability )
if ( err != nil ) { return err }
// We have to figure out which 1-mutation-probability is lower
// The best case probabilities can actually result in a higher probability for 1 mutation
// than the worst case probabilities
// This is because in a worst-case-scenario, the probability of 1 mutation might be 0 because
// the probability of 2 mutations is 100
// This is not needed for the 0 and 2 mutation probabilities because TODO
lowerBound1MutationProbability := min ( bestCase1MutationProbability , worstCase1MutationProbability )
upperBound1MutationProbability := max ( bestCase1MutationProbability , worstCase1MutationProbability )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringMonogenicDiseaseVariantInfoObject := geneticAnalysis . OffspringMonogenicDiseaseVariantInfo {
2024-06-05 06:10:35 +02:00
ProbabilityOf0MutationsLowerBound : worstCase0MutationsProbability ,
ProbabilityOf0MutationsUpperBound : bestCase0MutationsProbability ,
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
ProbabilityOf1MutationLowerBound : lowerBound1MutationProbability ,
ProbabilityOf1MutationUpperBound : upperBound1MutationProbability ,
2024-06-02 10:43:39 +02:00
2024-06-05 06:10:35 +02:00
ProbabilityOf2MutationsLowerBound : bestCase2MutationsProbability ,
ProbabilityOf2MutationsUpperBound : worstCase2MutationsProbability ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap [ variantIdentifier ] = newOffspringMonogenicDiseaseVariantInfoObject
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( person1ProbabilityIsKnown == false && person2ProbabilityIsKnown == false && len ( offspringVariantsInfoMap ) == 0 ) {
// We have no information about this genome pair's disease risk
// We don't add this genome pair's disease info to the diseaseInfoMap
2024-04-11 15:51:56 +02:00
return nil
}
2024-07-19 19:16:28 +02:00
quantityOfVariantsTestedMap [ person1GenomeIdentifier ] = person1NumberOfVariantsTested
quantityOfVariantsTestedMap [ person2GenomeIdentifier ] = person2NumberOfVariantsTested
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringGenomePairMonogenicDiseaseInfoObject := geneticAnalysis . OffspringGenomePairMonogenicDiseaseInfo {
ProbabilityOffspringHasDiseaseIsKnown : offspringHasDiseaseProbabilityIsKnown ,
ProbabilityOffspringHasDisease : percentageProbabilityOffspringHasDisease ,
ProbabilityOffspringHasVariantIsKnown : offspringHasAVariantProbabilityIsKnown ,
ProbabilityOffspringHasVariant : percentageProbabilityOffspringHasVariant ,
VariantsInfoMap : offspringVariantsInfoMap ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomePairIdentifier := helpers . JoinTwo16ByteArrays ( person1GenomeIdentifier , person2GenomeIdentifier )
offspringMonogenicDiseaseInfoMap [ genomePairIdentifier ] = newOffspringGenomePairMonogenicDiseaseInfoObject
return nil
}
err = addGenomePairInfoToDiseaseMaps ( pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
if ( genomePair2Exists == true ) {
err := addGenomePairInfoToDiseaseMaps ( pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
}
newOffspringMonogenicDiseaseInfoObject := geneticAnalysis . OffspringMonogenicDiseaseInfo {
2024-07-19 19:16:28 +02:00
QuantityOfVariantsTestedMap : quantityOfVariantsTestedMap ,
2024-06-02 10:43:39 +02:00
MonogenicDiseaseInfoMap : offspringMonogenicDiseaseInfoMap ,
2024-04-11 15:51:56 +02:00
}
// Now we check for conflicts in monogenic disease results
// For couples, a conflict is when either genome pair has differing results for any disease probability/variant probability
// This is different from a person having conflicts within their different genomes
// Each genome pair only uses 1 genome from each person.
2024-06-02 10:43:39 +02:00
if ( len ( offspringMonogenicDiseaseInfoMap ) >= 2 ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// Conflicts are only possible if two genome pairs with information about this disease exist
2024-04-11 15:51:56 +02:00
checkIfConflictExists := func ( ) ( bool , error ) {
2024-06-02 10:43:39 +02:00
probabilityOffspringHasDiseaseIsKnown := false
probabilityOffspringHasDisease := 0
probabilityOffspringHasVariantIsKnown := false
probabilityOffspringHasVariant := 0
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringMonogenicDiseaseVariantInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , offspringMonogenicDiseaseInfoObject := range offspringMonogenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentProbabilityOffspringHasDiseaseIsKnown := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasDiseaseIsKnown
currentProbabilityOffspringHasDisease := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasDisease
currentProbabilityOffspringHasVariantIsKnown := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasVariantIsKnown
currentProbabilityOffspringHasVariant := offspringMonogenicDiseaseInfoObject . ProbabilityOffspringHasVariant
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
currentOffspringVariantsInfoMap := offspringMonogenicDiseaseInfoObject . VariantsInfoMap
if ( firstItemReached == false ) {
probabilityOffspringHasDiseaseIsKnown = currentProbabilityOffspringHasDiseaseIsKnown
2024-04-11 15:51:56 +02:00
probabilityOffspringHasDisease = currentProbabilityOffspringHasDisease
2024-06-02 10:43:39 +02:00
probabilityOffspringHasVariantIsKnown = currentProbabilityOffspringHasVariantIsKnown
2024-04-11 15:51:56 +02:00
probabilityOffspringHasVariant = currentProbabilityOffspringHasVariant
2024-06-02 10:43:39 +02:00
offspringVariantsInfoMap = currentOffspringVariantsInfoMap
firstItemReached = true
2024-04-11 15:51:56 +02:00
continue
}
2024-06-02 10:43:39 +02:00
if ( currentProbabilityOffspringHasDiseaseIsKnown != probabilityOffspringHasDiseaseIsKnown ) {
return true , nil
}
2024-04-11 15:51:56 +02:00
if ( currentProbabilityOffspringHasDisease != probabilityOffspringHasDisease ) {
return true , nil
}
2024-06-02 10:43:39 +02:00
if ( currentProbabilityOffspringHasVariantIsKnown != probabilityOffspringHasVariantIsKnown ) {
return true , nil
}
2024-04-11 15:51:56 +02:00
if ( currentProbabilityOffspringHasVariant != probabilityOffspringHasVariant ) {
return true , nil
}
2024-06-02 10:43:39 +02:00
areEqual := maps . Equal ( currentOffspringVariantsInfoMap , offspringVariantsInfoMap )
if ( areEqual == false ) {
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := checkIfConflictExists ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newOffspringMonogenicDiseaseInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringMonogenicDiseasesMap [ diseaseName ] = newOffspringMonogenicDiseaseInfoObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . MonogenicDiseasesMap = offspringMonogenicDiseasesMap
2024-04-11 15:51:56 +02:00
// Step 2: Polygenic Diseases
polygenicDiseaseObjectsList , err := polygenicDiseases . GetPolygenicDiseaseObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Disease Name -> OffspringPolygenicDiseaseInfo
offspringPolygenicDiseasesMap := make ( map [ string ] geneticAnalysis . OffspringPolygenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
for _ , diseaseObject := range polygenicDiseaseObjectsList {
diseaseName := diseaseObject . DiseaseName
diseaseLociList := diseaseObject . LociList
2024-06-02 10:43:39 +02:00
// This map stores the polygenic disease info for each genome pair
// Map Structure: Genome Pair Identifier -> OffspringGenomePairPolygenicDiseaseInfo
offspringPolygenicDiseaseInfoMap := make ( map [ [ 32 ] byte ] geneticAnalysis . OffspringGenomePairPolygenicDiseaseInfo )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// This will calculate the disease info for the offspring from the two specified genomes to the diseases map
// It then adds the pair entry to the offspringPolygenicDiseaseInfoMap
addGenomePairDiseaseInfoToDiseaseMap := func ( person1GenomeIdentifier [ 16 ] byte , person2GenomeIdentifier [ 16 ] byte ) error {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
person1LocusValuesMap , exists := person1GenomesMap [ person1GenomeIdentifier ]
if ( exists == false ) {
return errors . New ( "addGenomePairDiseaseInfoToDiseaseMap called with unknown person1GenomeIdentifier." )
2024-06-02 10:43:39 +02:00
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
person2LocusValuesMap , exists := person2GenomesMap [ person2GenomeIdentifier ]
if ( exists == false ) {
return errors . New ( "addGenomePairDiseaseInfoToDiseaseMap called with unknown person2GenomeIdentifier." )
2024-06-07 02:04:13 +02:00
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
anyOffspringLocusTested , genomePairOffspringAverageRiskScore , quantityOfLociTested , genomePairOffspringDiseaseLociInfoMap , genomePairSampleOffspringRiskScoresList , err := GetOffspringPolygenicDiseaseInfo ( diseaseLociList , person1LocusValuesMap , person2LocusValuesMap )
2024-06-07 02:04:13 +02:00
if ( err != nil ) { return err }
if ( anyOffspringLocusTested == false ) {
2024-06-02 10:43:39 +02:00
// We have no information about this genome pair's disease risk
// We don't add this genome pair's disease info to the diseaseInfoMap
2024-04-11 15:51:56 +02:00
return nil
}
2024-06-02 10:43:39 +02:00
newOffspringGenomePairPolygenicDiseaseInfo := geneticAnalysis . OffspringGenomePairPolygenicDiseaseInfo {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
QuantityOfLociTested : quantityOfLociTested ,
2024-06-07 02:04:13 +02:00
OffspringAverageRiskScore : genomePairOffspringAverageRiskScore ,
LociInfoMap : genomePairOffspringDiseaseLociInfoMap ,
SampleOffspringRiskScoresList : genomePairSampleOffspringRiskScoresList ,
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
genomePairIdentifier := helpers . JoinTwo16ByteArrays ( person1GenomeIdentifier , person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
offspringPolygenicDiseaseInfoMap [ genomePairIdentifier ] = newOffspringGenomePairPolygenicDiseaseInfo
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
return nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
err = addGenomePairDiseaseInfoToDiseaseMap ( pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
if ( genomePair2Exists == true ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
err := addGenomePairDiseaseInfoToDiseaseMap ( pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
newOffspringPolygenicDiseaseInfoObject := geneticAnalysis . OffspringPolygenicDiseaseInfo {
PolygenicDiseaseInfoMap : offspringPolygenicDiseaseInfoMap ,
}
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( len ( offspringPolygenicDiseaseInfoMap ) >= 2 ) {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
// We check for conflicts
// Conflicts are only possible if two genome pairs with disease info for this disease exist
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
checkIfConflictExists := func ( ) ( bool , error ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
currentGenomePairPolygenicDiseaseInfo := geneticAnalysis . OffspringGenomePairPolygenicDiseaseInfo { }
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
for _ , genomePairDiseaseInfoObject := range offspringPolygenicDiseaseInfoMap {
2024-04-11 15:51:56 +02:00
2024-06-02 10:43:39 +02:00
if ( firstItemReached == false ) {
2024-07-19 19:16:28 +02:00
currentGenomePairPolygenicDiseaseInfo = genomePairDiseaseInfoObject
2024-06-02 10:43:39 +02:00
firstItemReached = true
continue
}
2024-07-19 19:16:28 +02:00
areEqual := reflect . DeepEqual ( genomePairDiseaseInfoObject , currentGenomePairPolygenicDiseaseInfo )
2024-06-02 10:43:39 +02:00
if ( areEqual == false ) {
return true , nil
2024-04-11 15:51:56 +02:00
}
}
return false , nil
}
conflictExists , err := checkIfConflictExists ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
newOffspringPolygenicDiseaseInfoObject . ConflictExists = conflictExists
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
offspringPolygenicDiseasesMap [ diseaseName ] = newOffspringPolygenicDiseaseInfoObject
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
newCoupleAnalysis . PolygenicDiseasesMap = offspringPolygenicDiseasesMap
2024-04-11 15:51:56 +02:00
// Step 3: Traits
traitObjectsList , err := traits . GetTraitObjectsList ( )
if ( err != nil ) { return false , "" , err }
2024-06-02 10:43:39 +02:00
// Map Structure: Trait Name -> Trait Info Object
2024-07-19 19:16:28 +02:00
offspringDiscreteTraitsMap := make ( map [ string ] geneticAnalysis . OffspringDiscreteTraitInfo )
// Map Structure: Trait Name -> Trait Info Object
offspringNumericTraitsMap := make ( map [ string ] geneticAnalysis . OffspringNumericTraitInfo )
2024-06-02 10:43:39 +02:00
2024-04-11 15:51:56 +02:00
for _ , traitObject := range traitObjectsList {
traitName := traitObject . TraitName
2024-07-19 19:16:28 +02:00
traitIsDiscreteOrNumeric := traitObject . DiscreteOrNumeric
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
if ( traitIsDiscreteOrNumeric == "Discrete" ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
// This map stores the trait info for each genome pair
// Map Structure: Genome Pair Identifier -> OffspringGenomePairDiscreteTraitInfo
offspringTraitInfoMap := make ( map [ [ 32 ] byte ] geneticAnalysis . OffspringGenomePairDiscreteTraitInfo )
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
// This will add the offspring trait information for the provided genome pair to the offspringTraitInfoMap
addGenomePairTraitInfoToOffspringMap := func ( person1GenomeIdentifier [ 16 ] byte , person2GenomeIdentifier [ 16 ] byte ) error {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
person1LocusValuesMap , exists := person1GenomesMap [ person1GenomeIdentifier ]
if ( exists == false ) {
return errors . New ( "addGenomePairTraitInfoToOffspringMap called with unknown person1GenomeIdentifier." )
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
person2LocusValuesMap , exists := person2GenomesMap [ person2GenomeIdentifier ]
if ( exists == false ) {
return errors . New ( "addGenomePairTraitInfoToOffspringMap called with unknown person2GenomeIdentifier." )
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringGenomePairTraitInfo := geneticAnalysis . OffspringGenomePairDiscreteTraitInfo { }
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
neuralNetworkExists , neuralNetworkAnalysisExists , outcomeProbabilitiesMap , averagePredictionConfidence , quantityOfLociTested , quantityOfParentalPhasedLoci , err := GetOffspringDiscreteTraitInfo_NeuralNetwork ( traitObject , person1LocusValuesMap , person2LocusValuesMap )
if ( err != nil ) { return err }
if ( neuralNetworkExists == true ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringGenomePairTraitInfo . NeuralNetworkExists = true
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
if ( neuralNetworkAnalysisExists == true ) {
newOffspringGenomePairTraitInfo . NeuralNetworkAnalysisExists = true
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringGenomePairTraitInfo_NeuralNetwork := geneticAnalysis . OffspringGenomePairDiscreteTraitInfo_NeuralNetwork {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
OffspringOutcomeProbabilitiesMap : outcomeProbabilitiesMap ,
AverageConfidence : averagePredictionConfidence ,
QuantityOfLociKnown : quantityOfLociTested ,
QuantityOfParentalPhasedLoci : quantityOfParentalPhasedLoci ,
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringGenomePairTraitInfo . NeuralNetworkAnalysis = newOffspringGenomePairTraitInfo_NeuralNetwork
}
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
anyRulesExist , rulesAnalysisExists , quantityOfRulesTested , quantityOfLociKnown , offspringProbabilityOfPassingRulesMap , offspringOutcomeProbabilitiesMap , err := GetOffspringDiscreteTraitInfo_Rules ( traitObject , person1LocusValuesMap , person2LocusValuesMap )
if ( err != nil ) { return err }
if ( anyRulesExist == true ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringGenomePairTraitInfo . RulesExist = true
if ( rulesAnalysisExists == true ) {
newOffspringGenomePairTraitInfo . RulesAnalysisExists = true
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringGenomePairTraitInfo_Rules := geneticAnalysis . OffspringGenomePairDiscreteTraitInfo_Rules {
QuantityOfRulesTested : quantityOfRulesTested ,
QuantityOfLociKnown : quantityOfLociKnown ,
ProbabilityOfPassingRulesMap : offspringProbabilityOfPassingRulesMap ,
OffspringOutcomeProbabilitiesMap : offspringOutcomeProbabilitiesMap ,
}
newOffspringGenomePairTraitInfo . RulesAnalysis = newOffspringGenomePairTraitInfo_Rules
}
}
genomePairIdentifier := helpers . JoinTwo16ByteArrays ( person1GenomeIdentifier , person2GenomeIdentifier )
offspringTraitInfoMap [ genomePairIdentifier ] = newOffspringGenomePairTraitInfo
return nil
}
err = addGenomePairTraitInfoToOffspringMap ( pair1Person1GenomeIdentifier , pair1Person2GenomeIdentifier )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
2024-07-19 19:16:28 +02:00
if ( genomePair2Exists == true ) {
2024-06-02 10:43:39 +02:00
2024-07-19 19:16:28 +02:00
err := addGenomePairTraitInfoToOffspringMap ( pair2Person1GenomeIdentifier , pair2Person2GenomeIdentifier )
if ( err != nil ) { return false , "" , err }
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringTraitInfoObject := geneticAnalysis . OffspringDiscreteTraitInfo {
TraitInfoMap : offspringTraitInfoMap ,
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
if ( len ( offspringTraitInfoMap ) >= 2 ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
// We check for conflicts
// Conflicts are only possible if two genome pairs exist with information about the trait
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
checkIfConflictExists := func ( ) ( bool , error ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
// We check for conflicts between each genome pair's outcome scores and trait rules maps
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
genomePairTraitInfoObject := geneticAnalysis . OffspringGenomePairDiscreteTraitInfo { }
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
firstItemReached := false
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
for _ , currentGenomePairTraitInfoObject := range offspringTraitInfoMap {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
if ( firstItemReached == false ) {
genomePairTraitInfoObject = currentGenomePairTraitInfoObject
firstItemReached = true
continue
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
areEqual := reflect . DeepEqual ( genomePairTraitInfoObject , currentGenomePairTraitInfoObject )
if ( areEqual == false ) {
return true , nil
}
2024-04-11 15:51:56 +02:00
}
2024-07-19 19:16:28 +02:00
return false , nil
2024-04-11 15:51:56 +02:00
}
2024-07-19 19:16:28 +02:00
conflictExists , err := checkIfConflictExists ( )
if ( err != nil ) { return false , "" , err }
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
newOffspringTraitInfoObject . ConflictExists = conflictExists
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
offspringDiscreteTraitsMap [ traitName ] = newOffspringTraitInfoObject
2024-04-11 15:51:56 +02:00
}
}
2024-07-19 19:16:28 +02:00
newCoupleAnalysis . DiscreteTraitsMap = offspringDiscreteTraitsMap
newCoupleAnalysis . NumericTraitsMap = offspringNumericTraitsMap
2024-06-02 10:43:39 +02:00
analysisBytes , err := encoding . EncodeMessagePackBytes ( newCoupleAnalysis )
2024-04-11 15:51:56 +02:00
if ( err != nil ) { return false , "" , err }
analysisString := string ( analysisBytes )
return true , analysisString , nil
}
2024-06-07 02:04:13 +02:00
// We also use this function when calculating offspring probabilities between users in viewProfileGui.go
2024-06-05 06:10:35 +02:00
//Outputs:
2024-06-07 02:04:13 +02:00
// -bool: Probability offspring has disease is known
// -int: Percentage probability offspring has disease (0-100)
// -bool: Probability offspring has variant is known
// -int: Percentage probability offspring has variant (0-100)
2024-06-05 06:10:35 +02:00
// -error
2024-06-07 02:04:13 +02:00
func GetOffspringMonogenicDiseaseProbabilities ( dominantOrRecessive string , person1ProbabilityIsKnown bool , person1WillPassVariantPercentageProbability int , person2ProbabilityIsKnown bool , person2WillPassVariantPercentageProbability int ) ( bool , int , bool , int , error ) {
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
if ( dominantOrRecessive != "Dominant" && dominantOrRecessive != "Recessive" ) {
return false , 0 , false , 0 , errors . New ( "GetOffspringMonogenicDiseaseProbabilities called with invalid dominantOrRecessive: " + dominantOrRecessive )
}
if ( person1ProbabilityIsKnown == false && person2ProbabilityIsKnown == false ) {
return false , 0 , false , 0 , nil
}
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
if ( person1ProbabilityIsKnown == true ) {
if ( person1WillPassVariantPercentageProbability < 0 || person1WillPassVariantPercentageProbability > 100 ) {
return false , 0 , false , 0 , errors . New ( "GetOffspringMonogenicDiseaseProbabilities called with invalid person1WillPassVariantProbability" )
}
}
if ( person2ProbabilityIsKnown == true ) {
if ( person2WillPassVariantPercentageProbability < 0 || person2WillPassVariantPercentageProbability > 100 ) {
return false , 0 , false , 0 , errors . New ( "GetOffspringMonogenicDiseaseProbabilities called with invalid person2WillPassVariantProbability" )
}
}
if ( person1ProbabilityIsKnown == false || person2ProbabilityIsKnown == false ) {
// Only 1 of the person's probabilities are known
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
getPersonWhoHasVariantProbabilityOfPassingIt := func ( ) int {
if ( person1ProbabilityIsKnown == true ) {
return person1WillPassVariantPercentageProbability
}
return person2WillPassVariantPercentageProbability
}
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
personWhoHasVariantProbabilityOfPassingIt := getPersonWhoHasVariantProbabilityOfPassingIt ( )
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
if ( personWhoHasVariantProbabilityOfPassingIt == 100 ) {
if ( dominantOrRecessive == "Dominant" ) {
// We know the offspring will have the disease and will have a variant
return true , 100 , true , 100 , nil
2024-06-05 06:10:35 +02:00
}
2024-06-07 02:04:13 +02:00
//dominantOrRecessive == "Recessive"
// We don't know if the offspring will have the disease, but we know they will have a variant
return false , 0 , true , 100 , nil
}
if ( personWhoHasVariantProbabilityOfPassingIt == 0 ) {
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
if ( dominantOrRecessive == "Recessive" ) {
// We know the offspring will not have the disease, but we don't know if they will have a variant
return true , 0 , false , 0 , nil
}
2024-06-05 06:10:35 +02:00
}
2024-06-07 02:04:13 +02:00
// We don't know the probabilities that the offspring will have the disease or if they will have a variant
return false , 0 , false , 0 , nil
}
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
person1WillPassVariantProbability := float64 ( person1WillPassVariantPercentageProbability ) / 100
person2WillPassVariantProbability := float64 ( person2WillPassVariantPercentageProbability ) / 100
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
// The probability offspring has a variant = the probability that either parent passes a variant (inclusive or)
// We find the probability of the offspring having a monogenic disease variant as follows:
// P(A U B) = P(A) + P(B) - P(A ∩ B)
// (Probability of person 1 passing a variant) + (Probability of person 2 passing a variant) - (Probability of offspring having disease)
// A person with a variant may have the disease, or just be a carrier.
probabilityOffspringHasVariant := person1WillPassVariantProbability + person2WillPassVariantProbability - ( person1WillPassVariantProbability * person2WillPassVariantProbability )
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
if ( dominantOrRecessive == "Dominant" ) {
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
// The probability of having the monogenic disease is the same as the probability of having a variant
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
percentageProbabilityOffspringHasVariant := int ( probabilityOffspringHasVariant * 100 )
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
return true , percentageProbabilityOffspringHasVariant , true , percentageProbabilityOffspringHasVariant , nil
2024-06-05 06:10:35 +02:00
}
2024-06-07 02:04:13 +02:00
// We find the probability of the offspring having the mongenic disease as follows:
// P(A and B) = P(A) * P(B)
// (Probability of person 1 Passing a variant) * (Probability of person 2 passing a variant)
probabilityOffspringHasDisease := person1WillPassVariantProbability * person2WillPassVariantProbability
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
percentageProbabilityOffspringHasDisease := probabilityOffspringHasDisease * 100
percentageProbabilityOffspringHasVariant := probabilityOffspringHasVariant * 100
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
// This conversion remove any digits after the radix point
// This will not result in any false 0% values, an example being 0.9% becoming 0%
// This is because the lowest non-zero probability a person can have for passing a variant is 50%
// Thus, the lowest non-zero probability of an offspring having a disease is 25%
percentageProbabilityOffspringHasDiseaseInt := int ( percentageProbabilityOffspringHasDisease )
percentageProbabilityOffspringHasVariantInt := int ( percentageProbabilityOffspringHasVariant )
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
return true , percentageProbabilityOffspringHasDiseaseInt , true , percentageProbabilityOffspringHasVariantInt , nil
}
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
// This is used to calculate user polygenic disease info for users
// It is faster to do it this way, because we don't create 100 prospective offspring
// We instead create 4 outcomes for each locus
// We can do this because testing each locus's risk score is independent of every other locus
// This is not true for traits, because trait rules are effected by multiple different loci
// When using the fast method for polygenic diseases, we don't get a sample of 100 offspring disease risk scores.
// The average risk score should still be the same for the fast and normal methods
// This function is also faster because we don't calculate odds ratio information or information about each locus
//Outputs:
// -bool: Any loci tested (if false, no offspring polygenic disease information is known)
// -int: Offspring Risk Score (Value between 0-10)
// -int: Number of loci tested
// -error
func GetOffspringPolygenicDiseaseInfo_Fast ( diseaseLociList [ ] polygenicDiseases . DiseaseLocus , person1LocusValuesMap map [ int64 ] locusValue . LocusValue , person2LocusValuesMap map [ int64 ] locusValue . LocusValue ) ( bool , int , int , error ) {
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
if ( len ( person1LocusValuesMap ) == 0 ) {
return false , 0 , 0 , nil
}
if ( len ( person2LocusValuesMap ) == 0 ) {
return false , 0 , 0 , nil
}
2024-06-05 06:10:35 +02:00
2024-06-15 02:43:01 +02:00
// I = Insertion
// D = Deletion
validAllelesList := [ ] string { "C" , "A" , "T" , "G" , "I" , "D" }
2024-06-07 02:04:13 +02:00
numberOfLociTested := 0
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
offspringSummedRiskWeights := 0
offspringMinimumPossibleRiskWeightSum := 0
offspringMaximumPossibleRiskWeightSum := 0
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
for _ , locusObject := range diseaseLociList {
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
locusRSID := locusObject . LocusRSID
locusRiskWeightsMap := locusObject . RiskWeightsMap
locusMinimumWeight := locusObject . MinimumRiskWeight
locusMaximumWeight := locusObject . MaximumRiskWeight
2024-06-05 06:10:35 +02:00
2024-06-15 02:43:01 +02:00
person1LocusValueFound , person1LocusBase1Value , person1LocusBase2Value , _ , _ , err := createPersonGeneticAnalysis . GetLocusValueFromGenomeMap ( true , person1LocusValuesMap , locusRSID )
2024-06-07 02:04:13 +02:00
if ( err != nil ) { return false , 0 , 0 , err }
if ( person1LocusValueFound == false ) {
// None of the offspring will have a value for this locus
continue
}
2024-06-05 06:10:35 +02:00
2024-06-15 02:43:01 +02:00
person2LocusValueFound , person2LocusBase1Value , person2LocusBase2Value , _ , _ , err := createPersonGeneticAnalysis . GetLocusValueFromGenomeMap ( true , person2LocusValuesMap , locusRSID )
2024-06-07 02:04:13 +02:00
if ( err != nil ) { return false , 0 , 0 , err }
if ( person2LocusValueFound == false ) {
// None of the offspring will have a value for this locus
continue
}
2024-06-05 06:10:35 +02:00
2024-06-15 02:43:01 +02:00
baseIsValid := slices . Contains ( validAllelesList , person1LocusBase1Value )
if ( baseIsValid == false ) {
return false , 0 , 0 , errors . New ( "GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person1LocusBase1Value )
}
baseIsValid = slices . Contains ( validAllelesList , person1LocusBase2Value )
if ( baseIsValid == false ) {
return false , 0 , 0 , errors . New ( "GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person1LocusBase2Value )
}
baseIsValid = slices . Contains ( validAllelesList , person2LocusBase1Value )
if ( baseIsValid == false ) {
return false , 0 , 0 , errors . New ( "GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person2LocusBase1Value )
}
baseIsValid = slices . Contains ( validAllelesList , person2LocusBase2Value )
if ( baseIsValid == false ) {
return false , 0 , 0 , errors . New ( "GetOffspringPolygenicDiseaseInfo_Fast called with genomeMap containing invalid locus value base: " + person2LocusBase2Value )
}
2024-06-07 02:04:13 +02:00
numberOfLociTested += 1
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
offspringBasePairOutcome1 := person1LocusBase1Value + ";" + person2LocusBase1Value
offspringBasePairOutcome2 := person1LocusBase2Value + ";" + person2LocusBase2Value
offspringBasePairOutcome3 := person1LocusBase1Value + ";" + person2LocusBase2Value
offspringBasePairOutcome4 := person1LocusBase2Value + ";" + person2LocusBase1Value
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
baseOutcomesList := [ ] string { offspringBasePairOutcome1 , offspringBasePairOutcome2 , offspringBasePairOutcome3 , offspringBasePairOutcome4 }
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
outcomesSummedRiskWeight := 0
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
for _ , outcomeBasePair := range baseOutcomesList {
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
offspringOutcomeRiskWeight , exists := locusRiskWeightsMap [ outcomeBasePair ]
if ( exists == false ) {
// We do not know the risk weight for this base pair
// We treat this as a 0 risk weight
2024-06-05 06:10:35 +02:00
continue
}
2024-06-07 02:04:13 +02:00
outcomesSummedRiskWeight += offspringOutcomeRiskWeight
}
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
locusAverageRiskWeight := outcomesSummedRiskWeight / 4
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
offspringSummedRiskWeights += locusAverageRiskWeight
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
offspringMinimumPossibleRiskWeightSum += locusMinimumWeight
offspringMaximumPossibleRiskWeightSum += locusMaximumWeight
}
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
offspringAverageDiseaseRiskScore , err := helpers . ScaleNumberProportionally ( true , offspringSummedRiskWeights , offspringMinimumPossibleRiskWeightSum , offspringMaximumPossibleRiskWeightSum , 0 , 10 )
if ( err != nil ) { return false , 0 , 0 , err }
if ( numberOfLociTested == 0 ) {
// No locations were tested
return false , 0 , 0 , nil
2024-06-05 06:10:35 +02:00
}
2024-06-07 02:04:13 +02:00
return true , offspringAverageDiseaseRiskScore , numberOfLociTested , nil
2024-06-05 06:10:35 +02:00
}
2024-06-15 02:43:01 +02:00
2024-04-11 15:51:56 +02:00
//Outputs:
2024-06-07 02:04:13 +02:00
// -bool: Any loci tested (if false, no offspring polygenic disease information is known)
// -int: Offspring Risk Score (Value between 0-10)
// -int: Number of loci tested
// -map[[3]byte]geneticAnalysis.OffspringPolygenicDiseaseLocusInfo: Offspring Locus information map
// Map Structure: Locus identifier -> OffspringPolygenicDiseaseLocusInfo
// -[]int: Sample offspring risks scores list
2024-04-11 15:51:56 +02:00
// -error
2024-06-07 02:04:13 +02:00
func GetOffspringPolygenicDiseaseInfo ( diseaseLociList [ ] polygenicDiseases . DiseaseLocus , person1LocusValuesMap map [ int64 ] locusValue . LocusValue , person2LocusValuesMap map [ int64 ] locusValue . LocusValue ) ( bool , int , int , map [ [ 3 ] byte ] geneticAnalysis . OffspringPolygenicDiseaseLocusInfo , [ ] int , error ) {
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
if ( len ( person1LocusValuesMap ) == 0 ) {
return false , 0 , 0 , nil , nil , nil
2024-06-02 10:43:39 +02:00
}
2024-06-07 02:04:13 +02:00
if ( len ( person2LocusValuesMap ) == 0 ) {
return false , 0 , 0 , nil , nil , nil
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
// First, we create 100 prospective offspring genomes.
diseaseLociRSIDsList := make ( [ ] int64 , 0 )
for _ , diseaseLocusObject := range diseaseLociList {
locusRSID := diseaseLocusObject . LocusRSID
diseaseLociRSIDsList = append ( diseaseLociRSIDsList , locusRSID )
2024-06-02 10:43:39 +02:00
}
2024-06-07 02:04:13 +02:00
anyLocusValueExists , prospectiveOffspringGenomesList , err := getProspectiveOffspringGenomesList ( diseaseLociRSIDsList , person1LocusValuesMap , person2LocusValuesMap )
if ( err != nil ) { return false , 0 , 0 , nil , nil , err }
if ( anyLocusValueExists == false ) {
return false , 0 , 0 , nil , nil , nil
2024-04-11 15:51:56 +02:00
}
2024-06-02 10:43:39 +02:00
2024-06-07 02:04:13 +02:00
// This will sum every offspring's average disease risk score
offspringAverageRiskScoreSum := 0
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
// This stores a list of every prospective offspring's risk score
sampleOffspringRiskScoresList := make ( [ ] int , 0 )
2024-06-02 10:43:39 +02:00
2024-06-07 02:04:13 +02:00
type offspringSummedLocusInfoObject struct {
2024-06-02 10:43:39 +02:00
2024-06-07 02:04:13 +02:00
SummedLocusRiskWeights int
2024-06-02 10:43:39 +02:00
2024-06-07 02:04:13 +02:00
SummedOddsRatios float64
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
NumberOfSummedOddsRatios int
2024-06-02 10:43:39 +02:00
2024-06-07 02:04:13 +02:00
// This is the number of unknown-odds-ratio-weight sums that we summed up
NumberOfUnknownOddsRatioWeightSums int
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
// This is the sum of every unknown-odds-ratio-weight-sum for each prospective offspring for this genome
UnknownOddsRatioWeightSumsSummed int
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
// Map Structure: Locus Identifier -> offspringSummedLocusInfoObject
offspringLocusInfoSumsMap := make ( map [ [ 3 ] byte ] offspringSummedLocusInfoObject )
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
for offspringIndex , offspringGenomeMap := range prospectiveOffspringGenomesList {
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringSummedRiskWeights := 0
offspringMinimumPossibleRiskWeightSum := 0
offspringMaximumPossibleRiskWeightSum := 0
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
for _ , locusObject := range diseaseLociList {
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
locusIdentifierHex := locusObject . LocusIdentifier
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
locusIdentifier , err := encoding . DecodeHexStringTo3ByteArray ( locusIdentifierHex )
if ( err != nil ) { return false , 0 , 0 , nil , nil , err }
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringLocusInfoSumsObject , exists := offspringLocusInfoSumsMap [ locusIdentifier ]
if ( exists == false ) {
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
if ( offspringIndex != 0 ) {
// We already checked a previous offspring for this locus, and it's value doesn't exist
continue
}
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
locusRSID := locusObject . LocusRSID
locusRiskWeightsMap := locusObject . RiskWeightsMap
locusOddsRatiosMap := locusObject . OddsRatiosMap
locusMinimumWeight := locusObject . MinimumRiskWeight
locusMaximumWeight := locusObject . MaximumRiskWeight
2024-04-11 15:51:56 +02:00
2024-06-15 02:43:01 +02:00
basePairValueFound , locusBase1Value , locusBase2Value , _ , _ , err := createPersonGeneticAnalysis . GetLocusValueFromGenomeMap ( true , offspringGenomeMap , locusRSID )
2024-06-07 02:04:13 +02:00
if ( err != nil ) { return false , 0 , 0 , nil , nil , err }
if ( basePairValueFound == false ) {
// None of the offspring will have a value for this locus
continue
}
2024-04-11 15:51:56 +02:00
2024-06-15 02:43:01 +02:00
locusRiskWeight , locusOddsRatioIsKnown , locusOddsRatio , err := createPersonGeneticAnalysis . GetGenomePolygenicDiseaseLocusRiskInfo ( locusRiskWeightsMap , locusOddsRatiosMap , locusBase1Value , locusBase2Value )
2024-06-07 02:04:13 +02:00
if ( err != nil ) { return false , 0 , 0 , nil , nil , err }
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringLocusInfoSumsObject . SummedLocusRiskWeights += locusRiskWeight
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
if ( locusOddsRatioIsKnown == true ) {
offspringLocusInfoSumsObject . SummedOddsRatios += locusOddsRatio
offspringLocusInfoSumsObject . NumberOfSummedOddsRatios += 1
} else {
offspringLocusInfoSumsObject . UnknownOddsRatioWeightSumsSummed += locusRiskWeight
offspringLocusInfoSumsObject . NumberOfUnknownOddsRatioWeightSums += 1
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringLocusInfoSumsMap [ locusIdentifier ] = offspringLocusInfoSumsObject
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringSummedRiskWeights += locusRiskWeight
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringMinimumPossibleRiskWeightSum += locusMinimumWeight
offspringMaximumPossibleRiskWeightSum += locusMaximumWeight
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
offspringAverageDiseaseRiskScore , err := helpers . ScaleNumberProportionally ( true , offspringSummedRiskWeights , offspringMinimumPossibleRiskWeightSum , offspringMaximumPossibleRiskWeightSum , 0 , 10 )
if ( err != nil ) { return false , 0 , 0 , nil , nil , err }
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
sampleOffspringRiskScoresList = append ( sampleOffspringRiskScoresList , offspringAverageDiseaseRiskScore )
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringAverageRiskScoreSum += offspringAverageDiseaseRiskScore
}
numberOfLociTested := len ( offspringLocusInfoSumsMap )
if ( numberOfLociTested == 0 ) {
// No locations were tested
return false , 0 , 0 , nil , nil , nil
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
offspringAverageRiskScore := offspringAverageRiskScoreSum / 100
// Map Structure: Locus Identifier -> OffspringPolygenicDiseaseLocusInfo
offspringDiseaseLociInfoMap := make ( map [ [ 3 ] byte ] geneticAnalysis . OffspringPolygenicDiseaseLocusInfo )
for locusIdentifier , summedLocusInfoObject := range offspringLocusInfoSumsMap {
summedLocusRiskWeights := summedLocusInfoObject . SummedLocusRiskWeights
summedOddsRatios := summedLocusInfoObject . SummedOddsRatios
numberOfSummedOddsRatios := summedLocusInfoObject . NumberOfSummedOddsRatios
numberOfUnknownOddsRatioWeightSums := summedLocusInfoObject . NumberOfUnknownOddsRatioWeightSums
unknownOddsRatioWeightSumsSummed := summedLocusInfoObject . UnknownOddsRatioWeightSumsSummed
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
// There are 100 prospective offspring, so we divide by 100
locusAverageRiskWeight := summedLocusRiskWeights / 100
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
newLocusInfoObject := geneticAnalysis . OffspringPolygenicDiseaseLocusInfo {
OffspringAverageRiskWeight : locusAverageRiskWeight ,
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
if ( numberOfSummedOddsRatios != 0 ) {
newLocusInfoObject . OffspringOddsRatioIsKnown = true
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringAverageOddsRatio := summedOddsRatios / float64 ( numberOfSummedOddsRatios )
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
newLocusInfoObject . OffspringAverageOddsRatio = offspringAverageOddsRatio
}
if ( numberOfUnknownOddsRatioWeightSums != 0 ) {
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringAverageUnknownOddsRatiosWeightSum := unknownOddsRatioWeightSumsSummed / numberOfUnknownOddsRatioWeightSums
newLocusInfoObject . OffspringAverageUnknownOddsRatiosWeightSum = offspringAverageUnknownOddsRatiosWeightSum
}
offspringDiseaseLociInfoMap [ locusIdentifier ] = newLocusInfoObject
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
return true , offspringAverageRiskScore , numberOfLociTested , offspringDiseaseLociInfoMap , sampleOffspringRiskScoresList , nil
2024-04-11 15:51:56 +02:00
}
2024-06-05 06:10:35 +02:00
//Outputs:
2024-07-19 19:16:28 +02:00
// -bool: A neural network exists for this trait
// -bool: Analysis exists (at least 1 locus exists for this analysis from both people's genomes
// -map[string]int: Outcome probabilities map
// Map Structure: Outcome Name -> Offspring probability of outcome
// -int: Average prediction confidence (the average prediction confidence for all prospective offspring)
// -int: Quantity of loci tested
// -int: Quantity of parental phased loci
2024-04-11 15:51:56 +02:00
// -error
2024-07-19 19:16:28 +02:00
func GetOffspringDiscreteTraitInfo_NeuralNetwork ( traitObject traits . Trait , person1LocusValuesMap map [ int64 ] locusValue . LocusValue , person2LocusValuesMap map [ int64 ] locusValue . LocusValue ) ( bool , bool , map [ string ] int , int , int , int , error ) {
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
traitName := traitObject . TraitName
traitIsDiscreteOrNumeric := traitObject . DiscreteOrNumeric
if ( traitIsDiscreteOrNumeric != "Discrete" ) {
return false , false , nil , 0 , 0 , 0 , errors . New ( "GetOffspringDiscreteTraitInfo_NeuralNetwork called with non-discrete trait." )
2024-06-07 02:04:13 +02:00
}
2024-07-19 19:16:28 +02:00
modelExists , _ := geneticPredictionModels . GetGeneticPredictionModelBytes ( traitName )
if ( modelExists == false ) {
// Neural network prediction is not possible for this trait
return false , false , nil , 0 , 0 , 0 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-05 06:10:35 +02:00
traitLociList := traitObject . LociList
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
// First we count up the quantity of parental phased loci
// We only count the quantity of phased loci for loci which are known for both parents
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
quantityOfParentalPhasedLoci := 0
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
for _ , rsID := range traitLociList {
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
person1LocusValue , exists := person1LocusValuesMap [ rsID ]
if ( exists == false ) {
continue
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
person2LocusValue , exists := person2LocusValuesMap [ rsID ]
if ( exists == false ) {
continue
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
person1LocusIsPhased := person1LocusValue . LocusIsPhased
if ( person1LocusIsPhased == true ) {
quantityOfParentalPhasedLoci += 1
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
person2LocusIsPhased := person2LocusValue . LocusIsPhased
if ( person2LocusIsPhased == true ) {
quantityOfParentalPhasedLoci += 1
}
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
// Next, we create 100 prospective offspring genomes.
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
anyLocusValueExists , prospectiveOffspringGenomesList , err := getProspectiveOffspringGenomesList ( traitLociList , person1LocusValuesMap , person2LocusValuesMap )
if ( err != nil ) { return false , false , nil , 0 , 0 , 0 , err }
if ( anyLocusValueExists == false ) {
return true , false , nil , 0 , 0 , 0 , nil
}
2024-06-07 02:04:13 +02:00
2024-07-19 19:16:28 +02:00
// Map Structure: Outcome Name -> Probability of outcome coming true
// Because we are summing from 100 offspring, the count of outcomes is the same as the probability of an offspring having the outcome
outcomeCountsMap := make ( map [ string ] int )
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
// This is a sum of each prediction's confidence
predictionConfidencesSum := 0
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
quantityOfLociTested := 0
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
for index , offspringGenomeMap := range prospectiveOffspringGenomesList {
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
neuralNetworkExists , predictionIsKnown , predictedOutcome , predictionConfidence , currentQuantityOfLociTested , _ , err := createPersonGeneticAnalysis . GetGenomeDiscreteTraitAnalysis_NeuralNetwork ( traitObject , offspringGenomeMap , false )
if ( err != nil ) { return false , false , nil , 0 , 0 , 0 , err }
if ( neuralNetworkExists == false ) {
return false , false , nil , 0 , 0 , 0 , errors . New ( "GetGenomeTraitAnalysis_NeuralNetwork claiming that neural network doesn't exist when we already checked." )
}
if ( predictionIsKnown == false ) {
return false , false , nil , 0 , 0 , 0 , errors . New ( "GetGenomeTraitAnalysis_NeuralNetwork claiming that prediction is impossible when we already know at least 1 locus value exists for trait." )
2024-04-11 15:51:56 +02:00
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
outcomeCountsMap [ predictedOutcome ] += 1
predictionConfidencesSum += predictionConfidence
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
if ( index == 0 ) {
// This value should be the same for each predicted offspring
quantityOfLociTested = currentQuantityOfLociTested
}
}
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
averagePredictionConfidence := predictionConfidencesSum / 100
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
return true , true , outcomeCountsMap , averagePredictionConfidence , quantityOfLociTested , quantityOfParentalPhasedLoci , nil
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
//Outputs:
// -bool: Any rules exist (if false, rule-based prediction is not possible for this trait)
// -bool: Rule-based analysis exists (if false, no offspring trait information is known, or there is an outcome tie for one of the offspring)
// -int: Quantity of rules tested
// -int: Quantity of loci known
// -map[[3]byte]int: Offspring probability of passing rules map
// Map Structure: Rule identifier -> Offspring probability of passing rule (1-100)
// If a rule entry doesn't exist, we don't know the passes-rule probability for any of the offspring
// -map[string]int: Offspring outcome probabilities map
// Map Structure: Outcome Name -> Offspring probability of outcome (0-100)
// -error
func GetOffspringDiscreteTraitInfo_Rules ( traitObject traits . Trait , person1LocusValuesMap map [ int64 ] locusValue . LocusValue , person2LocusValuesMap map [ int64 ] locusValue . LocusValue ) ( bool , bool , int , int , map [ [ 3 ] byte ] int , map [ string ] int , error ) {
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
traitRulesList := traitObject . RulesList
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
if ( len ( traitRulesList ) == 0 ) {
return false , false , 0 , 0 , nil , nil , nil
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
if ( len ( person1LocusValuesMap ) == 0 ) {
return true , false , 0 , 0 , nil , nil , nil
}
if ( len ( person2LocusValuesMap ) == 0 ) {
return true , false , 0 , 0 , nil , nil , nil
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
// First, we create 100 prospective offspring genomes.
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
traitLociList_Rules := traitObject . LociList_Rules
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
anyLocusValueExists , prospectiveOffspringGenomesList , err := getProspectiveOffspringGenomesList ( traitLociList_Rules , person1LocusValuesMap , person2LocusValuesMap )
if ( err != nil ) { return false , false , 0 , 0 , nil , nil , err }
if ( anyLocusValueExists == false ) {
return true , false , 0 , 0 , nil , nil , nil
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
// Map Structure: Rule Identifier -> Number of offspring who pass the rule (out of 100 prospective offspring)
// Because there are 100 offspring, this also represents the percentage probability that an offspring will pass the rule
offspringPassesRulesCountMap := make ( map [ [ 3 ] byte ] int )
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
// This map stores the quantity of offspring who have each outcome
// The probability an offspring will have this outcome is the same as the
// quantity of offspring who have this outcome in our set of 100 randomly generated offspring
// Map structure: Outcome name -> quantity of offspring who have this outcome
outcomeCountsMap := make ( map [ string ] int )
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
quantityOfLociKnown := 0
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
for index , offspringGenomeMap := range prospectiveOffspringGenomesList {
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
// Now we get outcome prediction for prospective offspring
2024-04-11 15:51:56 +02:00
2024-07-19 19:16:28 +02:00
anyRulesExist , quantityOfRulesTested , currentQuantityOfLociKnown , offspringPassesRulesMap , predictionOutcomeIsKnown , predictedOutcome , err := createPersonGeneticAnalysis . GetGenomeDiscreteTraitAnalysis_Rules ( traitObject , offspringGenomeMap , false )
if ( err != nil ) { return false , false , 0 , 0 , nil , nil , err }
if ( anyRulesExist == false ) {
return false , false , 0 , 0 , nil , nil , errors . New ( "GetGenomeTraitAnalysis_Rules returning noRulesExists when we already checked and trait rules do in-fact exist." )
}
if ( quantityOfRulesTested == 0 ) {
// This will be the same for each of the 100 generated offspring
// No analysis is possible.
return true , false , 0 , currentQuantityOfLociKnown , nil , nil , nil
}
if ( index == 0 ) {
// currentQuantityOfLociKnown will be the same for each prospective offspring
quantityOfLociKnown = currentQuantityOfLociKnown
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
for ruleIdentifier , genomePassesRule := range offspringPassesRulesMap {
if ( genomePassesRule == true ) {
offspringPassesRulesCountMap [ ruleIdentifier ] += 1
}
}
2024-06-05 06:10:35 +02:00
2024-07-19 19:16:28 +02:00
if ( predictionOutcomeIsKnown == false ) {
// There was a tie between outcomes for this offspring
// We can't predict anything about this trait for this couple using rules
// This is why we need to create rules which make it unlikely for a tie between outcomes to occur.
return true , false , 0 , 0 , nil , nil , nil
2024-06-07 02:04:13 +02:00
}
2024-07-19 19:16:28 +02:00
outcomeCountsMap [ predictedOutcome ] += 1
2024-06-07 02:04:13 +02:00
}
2024-07-19 19:16:28 +02:00
quantityOfRulesTested := len ( offspringPassesRulesCountMap )
return true , true , quantityOfRulesTested , quantityOfLociKnown , offspringPassesRulesCountMap , outcomeCountsMap , nil
2024-06-07 02:04:13 +02:00
}
// This function will return a list of 100 prospective offspring genomes
// Each genome represents an equal-probability offspring genome from both people's genomes
// This function takes into account the effects of genetic linkage
// Any locations which do not exist in both people's genomes will not be included
2024-07-19 19:16:28 +02:00
//
// TODO: The user should be able to choose how many prospective offspring to create in the settings.
// More offspring will take longer, but will yield a more accurate trait analysis.
//
2024-06-07 02:04:13 +02:00
//Outputs:
// -bool: Any locus value exists between both users
// -[]map[int64]locusValue.LocusValue
// -error
func getProspectiveOffspringGenomesList ( lociList [ ] int64 , person1LociMap map [ int64 ] locusValue . LocusValue , person2LociMap map [ int64 ] locusValue . LocusValue ) ( bool , [ ] map [ int64 ] locusValue . LocusValue , error ) {
// -We use randomness to generate the offspring genomes
// -We want the results to be the same for each pair of people each time, so we have to seed our randomness generator
// -This is necessary so that two people's analysis results do not change every time
// -Instead, the same 2 people will produce the exact same result every time
pseudorandomNumberGenerator := mathRand . New ( mathRand . NewPCG ( 1 , 2 ) )
//Outputs:
// -[]int64: A list of random breakpoints for this chromosome that are statistically accurate
// -error
getRandomChromosomeBreakpoints := func ( chromosome int ) ( [ ] int64 , error ) {
getChromosomeLength := func ( ) ( int64 , error ) {
// Approximate number of base pairs in each chromosome taken from: https://www.ncbi.nlm.nih.gov/books/NBK557784/
switch chromosome {
case 1 : {
return 249000000 , nil
}
case 2 : {
return 243000000 , nil
}
case 3 : {
return 200000000 , nil
}
case 4 : {
return 192000000 , nil
}
case 5 : {
return 181000000 , nil
}
case 6 : {
return 170000000 , nil
}
case 7 : {
return 158000000 , nil
}
case 8 : {
return 146000000 , nil
}
case 9 : {
return 140000000 , nil
}
case 10 : {
return 135000000 , nil
}
case 11 : {
return 135000000 , nil
}
case 12 : {
return 132000000 , nil
}
case 13 : {
return 114000000 , nil
}
case 14 : {
return 106000000 , nil
}
case 15 : {
return 100000000 , nil
}
case 16 : {
return 89000000 , nil
}
case 17 : {
return 79000000 , nil
}
case 18 : {
return 76000000 , nil
}
case 19 : {
return 64000000 , nil
}
case 20 : {
return 62000000 , nil
}
case 21 : {
return 47000000 , nil
}
case 22 : {
return 50000000 , nil
}
}
chromosomeString := helpers . ConvertIntToString ( chromosome )
return 0 , errors . New ( "getRandomChromosomeBreakpoints called with invalid chromosome: " + chromosomeString )
}
chromosomeLength , err := getChromosomeLength ( )
if ( err != nil ) { return nil , err }
listOfRandomBreakpoints := make ( [ ] int64 , 0 )
// TODO: Take into account different recombination rate for each chromosome
// TODO: There are also breakpoint hotspots which we need to account for
// TODO: I read somewhere that recombination break points are less likely to occur within genes,
// meaning they are more likely to occur at the gene boundaries (codons)
// We step by 1,000,000 each time
// It would be more realistic if we did it in 1 integer increments, but it would be slower
2024-07-19 19:16:28 +02:00
for position := int64 ( 0 ) ; position < chromosomeLength ; position += 1_000_000 {
2024-06-07 02:04:13 +02:00
//From Wikipedia:
// A centimorgan (abbreviated cM) is a unit for measuring genetic linkage.
// It is defined as the distance between chromosome positions (loci) for which the expected
// average number of intervening chromosomal crossovers in a single generation is 0.01.
// One centimorgan corresponds to about 1 million base pairs in humans on average
//
// A chromosomal crossover == recombination breakpoint
//
// For every 1,000,000 base pairs, there is a 0.01 probability that there is a breakpoint
randomFloat := pseudorandomNumberGenerator . Float64 ( )
if ( randomFloat <= 0.01 ) {
// This has a 0.01, or 1% probability of being true
listOfRandomBreakpoints = append ( listOfRandomBreakpoints , position )
}
}
return listOfRandomBreakpoints , nil
}
// Map Structure: rsID -> Locus Value
offspringGenomesList := make ( [ ] map [ int64 ] locusValue . LocusValue , 0 )
for i := 0 ; i < 100 ; i ++ {
// This map stores the chromosome breakpoints for person1
// Map Structure: Chromosome -> List of breakpoints
person1ChromosomeBreakpointsMap := make ( map [ int ] [ ] int64 )
// This map stores the chromosome breakpoints for person2
// Map Structure: Chromosome -> List of breakpoints
person2ChromosomeBreakpointsMap := make ( map [ int ] [ ] int64 )
// This stores the locus values for this prospective offspring
// Map Structure: rsID -> Locus Value
prospectiveOffspringGenome := make ( map [ int64 ] locusValue . LocusValue )
for _ , rsID := range lociList {
//Outputs:
// -bool: Allele is known
// -string: Locus base
// -error
getPersonAllele := func ( personLociMap map [ int64 ] locusValue . LocusValue , personBreakpointsMap map [ int ] [ ] int64 ) ( bool , string , error ) {
personLocusValue , exists := personLociMap [ rsID ]
if ( exists == false ) {
return false , "" , nil
}
personLocusBase1 := personLocusValue . Base1Value
2024-07-19 19:16:28 +02:00
personLocusBase2 := personLocusValue . Base2Value
2024-06-07 02:04:13 +02:00
personLocusIsPhased := personLocusValue . LocusIsPhased
2024-07-19 19:16:28 +02:00
if ( personLocusBase1 == personLocusBase2 ) {
// Phase doesn't matter
return true , personLocusBase1 , nil
}
2024-06-07 02:04:13 +02:00
if ( personLocusIsPhased == false ) {
// Breakpoints are unnecessary
// We either choose base 1 or 2
2024-07-19 19:16:28 +02:00
randomInt := pseudorandomNumberGenerator . IntN ( 2 )
if ( randomInt == 1 ) {
2024-06-07 02:04:13 +02:00
return true , personLocusBase1 , nil
}
return true , personLocusBase2 , nil
}
// We have a phased locus
// We figure out which allele to use by seeing which allele gets inherited from our random breakpoints list
// We figure out the chromosome and position of this locus
locusMetadataExists , locusMetadataObject , err := locusMetadata . GetLocusMetadata ( rsID )
if ( err != nil ) { return false , "" , err }
if ( locusMetadataExists == false ) {
rsIDString := helpers . ConvertInt64ToString ( rsID )
return false , "" , errors . New ( "getProspectiveOffspringGenomesList called with unknown rsID: " + rsIDString )
}
locusPosition := locusMetadataObject . Position
locusChromosome := locusMetadataObject . Chromosome
getPersonChromosomeBreakpointsList := func ( ) ( [ ] int64 , error ) {
breakpointsList , exists := personBreakpointsMap [ locusChromosome ]
if ( exists == true ) {
return breakpointsList , nil
}
// We have to create a new breakpoints list
newBreakpointsList , err := getRandomChromosomeBreakpoints ( locusChromosome )
if ( err != nil ) { return nil , err }
personBreakpointsMap [ locusChromosome ] = newBreakpointsList
return newBreakpointsList , nil
}
personBreakpointsList , err := getPersonChromosomeBreakpointsList ( )
if ( err != nil ) { return false , "" , err }
getLocusListIndex := func ( ) int {
2024-07-19 19:16:28 +02:00
for index , breakpointPosition := range personBreakpointsList {
2024-06-07 02:04:13 +02:00
2024-07-19 19:16:28 +02:00
if ( int64 ( locusPosition ) <= breakpointPosition ) {
2024-06-07 02:04:13 +02:00
return index
}
}
index := len ( personBreakpointsList )
2024-06-05 06:10:35 +02:00
2024-06-07 02:04:13 +02:00
// This is reached if the final breakpoint in the list is less than the locus's position, or if there were no breakpoints
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
return index
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
locusListIndex := getLocusListIndex ( )
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
if ( locusListIndex % 2 == 0 ) {
return true , personLocusBase1 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
return true , personLocusBase2 , nil
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
person1AlleleIsKnown , person1Allele , err := getPersonAllele ( person1LociMap , person1ChromosomeBreakpointsMap )
if ( err != nil ) { return false , nil , err }
if ( person1AlleleIsKnown == false ) {
continue
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
person2AlleleIsKnown , person2Allele , err := getPersonAllele ( person2LociMap , person2ChromosomeBreakpointsMap )
if ( err != nil ) { return false , nil , err }
if ( person2AlleleIsKnown == false ) {
continue
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringLocusValue := locusValue . LocusValue {
Base1Value : person1Allele ,
Base2Value : person2Allele ,
LocusIsPhased : true ,
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
prospectiveOffspringGenome [ rsID ] = offspringLocusValue
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
if ( len ( prospectiveOffspringGenome ) == 0 ) {
// We don't have any locations at which both people's genomes contain a locus value.
return false , nil , nil
}
2024-04-11 15:51:56 +02:00
2024-06-07 02:04:13 +02:00
offspringGenomesList = append ( offspringGenomesList , prospectiveOffspringGenome )
2024-04-11 15:51:56 +02:00
}
2024-06-07 02:04:13 +02:00
return true , offspringGenomesList , nil
2024-04-11 15:51:56 +02:00
}