2519 lines
97 KiB
Go
2519 lines
97 KiB
Go
|
|
// createGeneticAnalysis provides functions to create a genetic analysis
|
|
// These are performed on one or more genome files.
|
|
// They produce 3 kinds of results: Monogenic Diseases, Polygenic Diseases and Traits
|
|
// They can be performed on a Person or a Couple
|
|
// Couple analyses provide an analysis of the prospective offspring of the couple
|
|
|
|
package createGeneticAnalysis
|
|
|
|
// TODO: Some of the probabilities produced by this package are wrong
|
|
// In this package, we are assuming that genetic recombination (the formation of the genetic sequences for the sperm/eggs) happens randomly for each allele locus
|
|
// In reality, the recombination break points occur less often, and larger portions of each chromosome remain intact.
|
|
// This effects the estimates and probabilities for all of the generated analyses
|
|
// In particular, the probability of passing a defective gene does not increase as much as this package currently
|
|
// estimates that it does, in the case of multiple defects existing in the same gene.
|
|
// Also, based on my research, I believe that recombination break points are less likely to occur within genes, meaning they are more likely to occur at the gene boundaries (codons)
|
|
// We need to remedy this problem and fix this package
|
|
// Research gene linkage and recombination to understand more.
|
|
//
|
|
// Fixing this problem may require us to only allow will-pass-a-variant probabilities to be calculated from phased loci.
|
|
// The phase of a loci becomes much more important in determining the will-pass-a-variant probability
|
|
// Having multiple variants within a gene might not increase the probability of passing a variant, assuming all of those variants were on the same chromosome
|
|
// Thus, we need phased loci to determine an accurate will-pass-a-variant probability
|
|
// We will still be able to determine will-pass-a-variant probabilities for users who only have 1 variant on 1 base,
|
|
// regardless of if their loci phase is known or not. That probability is 50%.
|
|
|
|
|
|
|
|
// TODO: We want to eventually use neural nets for both trait and polygenic disease analysis
|
|
// These will be trained on a set of genomes and will output a probability analysis for each trait/disease
|
|
// This is only possible once we get access to the necessary training data
|
|
//
|
|
// This is how offspring trait prediction could work with the neural net model:
|
|
// Both users will share all relevant SNPS base pairs that determine the trait on their profile.
|
|
// Each location has 4 possible outcomes, so for 1000 SNPs, there are 4^1000 possible offspring outcomes for a given couple. (this
|
|
// is actually too high because recombination break points do not occur at each locus, see genetic linkage)
|
|
// This is too many options for us to check all of them.
|
|
// Seekia will create 100 offspring that would be produced from both users, and run each offspring through the neural net.
|
|
// Each offspring would be different. The allele from each parent for each SNP would be randomly chosen.
|
|
// The user can choose how many prospective offspring to create in the settings.
|
|
// More offspring will take longer, but will yield a more accurate trait probability.
|
|
// Seekia will show the the average trait result and a chart showing the trait results for all created offspring.
|
|
|
|
|
|
// TODO: We should not add any passing-a-variant probabilities that are dependent on an unknown phase to user profiles
|
|
// For example, if the phase for any mutated bases are unknown, and this unknown phase effects the probability of
|
|
// the user passing a variant, that probability should not be shared on the user's profile.
|
|
// Seekia currently shares these probabilities on user profiles.
|
|
// Only probabilities which are fully known should be shared. Otherwise, the user is sharing
|
|
// information that is not fully accurate but is instead speculative.
|
|
//
|
|
// We must add this warning into the GUI for a user and couple genetic analyses too.
|
|
// Basically, we need to make it clear that some probabilities are based completely on random genetic chance, and others
|
|
// are based on our limited understanding of the user's genome (due to unphased data).
|
|
|
|
|
|
// TODO: Add the ability to weight different genome files based on their reliability.
|
|
// Some files are much more accurate because they record each location many times.
|
|
|
|
|
|
import "seekia/resources/geneticReferences/locusMetadata"
|
|
import "seekia/resources/geneticReferences/monogenicDiseases"
|
|
import "seekia/resources/geneticReferences/polygenicDiseases"
|
|
import "seekia/resources/geneticReferences/traits"
|
|
|
|
import "seekia/internal/genetics/locusValue"
|
|
import "seekia/internal/genetics/prepareRawGenomes"
|
|
import "seekia/internal/helpers"
|
|
|
|
import "errors"
|
|
import "math"
|
|
import "encoding/json"
|
|
import "strings"
|
|
import "slices"
|
|
|
|
|
|
func verifyBasePair(inputBasePair string)bool{
|
|
|
|
base1, base2, delimiterFound := strings.Cut(inputBasePair, ";")
|
|
if (delimiterFound == false){
|
|
return false
|
|
}
|
|
|
|
// I = Insertion
|
|
// D = Deletion
|
|
|
|
validBasesList := []string{"C", "A", "T", "G", "I", "D"}
|
|
|
|
baseIsValid := slices.Contains(validBasesList, base1)
|
|
if (baseIsValid == false){
|
|
return false
|
|
}
|
|
|
|
baseIsValid = slices.Contains(validBasesList, base2)
|
|
if (baseIsValid == false){
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
//Inputs:
|
|
// -map[string]string: Genome Identifier -> Genome Raw data string
|
|
//Outputs:
|
|
// -bool: Process completed (it was not stopped manually before completion)
|
|
// -string: New Genetic analysis string
|
|
// -error
|
|
func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMetadata, updatePercentageCompleteFunction func(int)error, checkIfProcessIsStopped func()bool)(bool, string, error){
|
|
|
|
prepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
|
|
|
|
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 0, 50)
|
|
if (err != nil){ return err }
|
|
|
|
err = updatePercentageCompleteFunction(newPercentageCompletion)
|
|
if (err != nil) { return err }
|
|
|
|
return nil
|
|
}
|
|
|
|
genomesWithMetadataList, allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(genomesList, prepareRawGenomesUpdatePercentageCompleteFunction)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
combinedGenomesExistString := helpers.ConvertBoolToYesOrNoString(multipleGenomesExist)
|
|
|
|
metadataItem := map[string]string{
|
|
"ItemType": "Metadata",
|
|
"AnalysisVersion": "1",
|
|
"AnalysisType": "Person",
|
|
"CombinedGenomesExist": combinedGenomesExistString,
|
|
}
|
|
|
|
if (multipleGenomesExist == true){
|
|
|
|
metadataItem["OnlyExcludeConflictsGenomeIdentifier"] = onlyExcludeConflictsGenomeIdentifier
|
|
metadataItem["OnlyIncludeSharedGenomeIdentifier"] = onlyIncludeSharedGenomeIdentifier
|
|
|
|
allRawGenomeIdentifiersListString := strings.Join(allRawGenomeIdentifiersList, "+")
|
|
metadataItem["AllRawGenomeIdentifiersList"] = allRawGenomeIdentifiersListString
|
|
|
|
} else {
|
|
|
|
genomeIdentifier := allRawGenomeIdentifiersList[0]
|
|
|
|
metadataItem["GenomeIdentifier"] = genomeIdentifier
|
|
}
|
|
|
|
processIsStopped := checkIfProcessIsStopped()
|
|
if (processIsStopped == true){
|
|
return false, "", nil
|
|
}
|
|
|
|
geneticAnalysisMapList := []map[string]string{metadataItem}
|
|
|
|
monogenicDiseasesList, err := monogenicDiseases.GetMonogenicDiseaseObjectsList()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
for _, monogenicDiseaseObject := range monogenicDiseasesList{
|
|
|
|
diseaseName := monogenicDiseaseObject.DiseaseName
|
|
|
|
diseaseAnalysisMap, variantsMapList, err := getMonogenicDiseaseAnalysis(genomesWithMetadataList, monogenicDiseaseObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
diseaseAnalysisMap["ItemType"] = "MonogenicDisease"
|
|
diseaseAnalysisMap["DiseaseName"] = diseaseName
|
|
|
|
geneticAnalysisMapList = append(geneticAnalysisMapList, diseaseAnalysisMap)
|
|
geneticAnalysisMapList = append(geneticAnalysisMapList, variantsMapList...)
|
|
}
|
|
|
|
polygenicDiseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
for _, diseaseObject := range polygenicDiseaseObjectsList{
|
|
|
|
diseaseAnalysisMap, diseaseLociMapList, err := getPolygenicDiseaseAnalysis(genomesWithMetadataList, diseaseObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
diseaseName := diseaseObject.DiseaseName
|
|
|
|
diseaseAnalysisMap["ItemType"] = "PolygenicDisease"
|
|
diseaseAnalysisMap["DiseaseName"] = diseaseName
|
|
|
|
geneticAnalysisMapList = append(geneticAnalysisMapList, diseaseAnalysisMap)
|
|
geneticAnalysisMapList = append(geneticAnalysisMapList, diseaseLociMapList...)
|
|
}
|
|
|
|
traitObjectsList, err := traits.GetTraitObjectsList()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
for _, traitObject := range traitObjectsList{
|
|
|
|
traitAnalysisMap, traitRulesMapList, err := getTraitAnalysis(genomesWithMetadataList, traitObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
traitName := traitObject.TraitName
|
|
|
|
traitAnalysisMap["ItemType"] = "Trait"
|
|
traitAnalysisMap["TraitName"] = traitName
|
|
|
|
geneticAnalysisMapList = append(geneticAnalysisMapList, traitAnalysisMap)
|
|
geneticAnalysisMapList = append(geneticAnalysisMapList, traitRulesMapList...)
|
|
}
|
|
|
|
analysisBytes, err := json.MarshalIndent(geneticAnalysisMapList, "", "\t")
|
|
if (err != nil) { return false, "", err }
|
|
|
|
analysisString := string(analysisBytes)
|
|
|
|
return true, analysisString, nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -bool: Process completed (was not manually stopped mid-way)
|
|
// -string: Couple genetic analysis string
|
|
// -error
|
|
func CreateCoupleGeneticAnalysis(personAGenomesList []prepareRawGenomes.RawGenomeWithMetadata, personBGenomesList []prepareRawGenomes.RawGenomeWithMetadata, updatePercentageCompleteFunction func(int)error, checkIfProcessIsStopped func()bool)(bool, string, error){
|
|
|
|
personAPrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
|
|
|
|
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
|
|
}
|
|
|
|
personAGenomesWithMetadataList, allPersonARawGenomeIdentifiersList, personAHasMultipleGenomes, personAOnlyExcludeConflictsGenomeIdentifier, personAOnlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(personAGenomesList, personAPrepareRawGenomesUpdatePercentageCompleteFunction)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
processIsStopped := checkIfProcessIsStopped()
|
|
if (processIsStopped == true){
|
|
return false, "", nil
|
|
}
|
|
|
|
personBPrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
|
|
|
|
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
|
|
}
|
|
|
|
personBGenomesWithMetadataList, allPersonBRawGenomeIdentifiersList, personBHasMultipleGenomes, personBOnlyExcludeConflictsGenomeIdentifier, personBOnlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(personBGenomesList, personBPrepareRawGenomesUpdatePercentageCompleteFunction)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
processIsStopped = checkIfProcessIsStopped()
|
|
if (processIsStopped == true){
|
|
return false, "", nil
|
|
}
|
|
|
|
// The analysis will analyze either 1 or 2 genome pairs
|
|
// The gui will display the results from each pair
|
|
//Outputs:
|
|
// -string: Pair 1 Person A Genome Identifier
|
|
// -string: Pair 1 Person B Genome Identifier
|
|
// -bool: Second pair exists
|
|
// -string: Pair 2 Person A Genome Identifier
|
|
// -string: Pair 2 Person B Genome Identifier
|
|
// -error
|
|
getGenomePairsToAnalyze := func()(string, string, bool, string, string, error){
|
|
|
|
if (personAHasMultipleGenomes == true && personBHasMultipleGenomes == true){
|
|
|
|
return personAOnlyExcludeConflictsGenomeIdentifier, personBOnlyExcludeConflictsGenomeIdentifier, true, personAOnlyIncludeSharedGenomeIdentifier, personBOnlyIncludeSharedGenomeIdentifier, nil
|
|
}
|
|
if (personAHasMultipleGenomes == true && personBHasMultipleGenomes == false){
|
|
|
|
personBGenomeIdentifier := allPersonBRawGenomeIdentifiersList[0]
|
|
|
|
return personAOnlyExcludeConflictsGenomeIdentifier, personBGenomeIdentifier, true, personAOnlyIncludeSharedGenomeIdentifier, personBGenomeIdentifier, nil
|
|
}
|
|
if (personAHasMultipleGenomes == false && personBHasMultipleGenomes == true){
|
|
|
|
personAGenomeIdentifier := allPersonARawGenomeIdentifiersList[0]
|
|
|
|
return personAGenomeIdentifier, personBOnlyExcludeConflictsGenomeIdentifier, true, personAGenomeIdentifier, personBOnlyIncludeSharedGenomeIdentifier, nil
|
|
}
|
|
|
|
//personAHasMultipleGenomes == false && personBHasMultipleGenomes == false
|
|
|
|
personAGenomeIdentifier := allPersonARawGenomeIdentifiersList[0]
|
|
personBGenomeIdentifier := allPersonBRawGenomeIdentifiersList[0]
|
|
|
|
return personAGenomeIdentifier, personBGenomeIdentifier, false, "", "", nil
|
|
}
|
|
|
|
pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier, genomePair2Exists, pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier, err := getGenomePairsToAnalyze()
|
|
if (err != nil){ return false, "", err }
|
|
|
|
personAHasMultipleGenomesString := helpers.ConvertBoolToYesOrNoString(personAHasMultipleGenomes)
|
|
personBHasMultipleGenomesString := helpers.ConvertBoolToYesOrNoString(personBHasMultipleGenomes)
|
|
|
|
secondPairExistsString := helpers.ConvertBoolToYesOrNoString(genomePair2Exists)
|
|
|
|
metadataItem := map[string]string{
|
|
"ItemType": "Metadata",
|
|
"AnalysisVersion": "1",
|
|
"AnalysisType": "Couple",
|
|
"PersonAHasMultipleGenomes": personAHasMultipleGenomesString,
|
|
"PersonBHasMultipleGenomes": personBHasMultipleGenomesString,
|
|
"Pair1PersonAGenomeIdentifier": pair1PersonAGenomeIdentifier,
|
|
"Pair1PersonBGenomeIdentifier": pair1PersonBGenomeIdentifier,
|
|
"SecondPairExists": secondPairExistsString,
|
|
}
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
// At least 1 of the people have multiple genomes
|
|
|
|
metadataItem["Pair2PersonAGenomeIdentifier"] = pair2PersonAGenomeIdentifier
|
|
metadataItem["Pair2PersonBGenomeIdentifier"] = pair2PersonBGenomeIdentifier
|
|
|
|
if (personAHasMultipleGenomes == true){
|
|
metadataItem["PersonAOnlyExcludeConflictsGenomeIdentifier"] = personAOnlyExcludeConflictsGenomeIdentifier
|
|
metadataItem["PersonAOnlyIncludeSharedGenomeIdentifier"] = personAOnlyIncludeSharedGenomeIdentifier
|
|
}
|
|
|
|
if (personBHasMultipleGenomes == true){
|
|
metadataItem["PersonBOnlyExcludeConflictsGenomeIdentifier"] = personBOnlyExcludeConflictsGenomeIdentifier
|
|
metadataItem["PersonBOnlyIncludeSharedGenomeIdentifier"] = personBOnlyIncludeSharedGenomeIdentifier
|
|
}
|
|
}
|
|
|
|
newAnalysisMapList := []map[string]string{metadataItem}
|
|
|
|
//First we add monogenic disease probabilities
|
|
|
|
monogenicDiseasesList, err := monogenicDiseases.GetMonogenicDiseaseObjectsList()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
for _, diseaseObject := range monogenicDiseasesList{
|
|
|
|
diseaseName := diseaseObject.DiseaseName
|
|
variantsList := diseaseObject.VariantsList
|
|
diseaseIsDominantOrRecessive := diseaseObject.DominantOrRecessive
|
|
|
|
offspringDiseaseMap := map[string]string{
|
|
"ItemType": "MonogenicDisease",
|
|
"DiseaseName": diseaseName,
|
|
}
|
|
|
|
personADiseaseAnalysisMap, personAVariantsMapList, err := getMonogenicDiseaseAnalysis(personAGenomesWithMetadataList, diseaseObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
personBDiseaseAnalysisMap, personBVariantsMapList, err := getMonogenicDiseaseAnalysis(personBGenomesWithMetadataList, diseaseObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
// This will calculate the probability of monogenic disease for the offspring from the two specified genomes
|
|
// It then adds the pair entry to the offspringDiseaseMap
|
|
addPairProbabilitiesToDiseaseMap := func(personAGenomeIdentifier string, personBGenomeIdentifier string)error{
|
|
|
|
//Outputs:
|
|
// -bool: Probability is known
|
|
// -int: Probability of passing a disease variant (value between 0 and 100)
|
|
// -int: Number of variants tested
|
|
// -error
|
|
getPersonWillPassDiseaseVariantProbability := func(personDiseaseAnalysisMap map[string]string, genomeIdentifier string)(bool, int, int, error){
|
|
|
|
probabilityMapKey := genomeIdentifier + "_ProbabilityOfPassingADiseaseVariant"
|
|
|
|
genomeDiseasePercentageProbability, exists := personDiseaseAnalysisMap[probabilityMapKey]
|
|
if (exists == false) {
|
|
return false, 0, 0, errors.New("Malformed person analysis map list: Missing probability of passing a variant value")
|
|
}
|
|
if (genomeDiseasePercentageProbability == "Unknown"){
|
|
return false, 0, 0, nil
|
|
}
|
|
|
|
genomeDiseaseProbabilityInt, err := helpers.ConvertStringToInt(genomeDiseasePercentageProbability)
|
|
if (err != nil) {
|
|
return false, 0, 0, errors.New("Malformed person analysis map list: Invalid probability of passing a variant value: " + genomeDiseasePercentageProbability)
|
|
}
|
|
|
|
numberOfTestedVariantsString, exists := personDiseaseAnalysisMap[genomeIdentifier + "_NumberOfVariantsTested"]
|
|
if (exists == false){
|
|
return false, 0, 0, errors.New("Malformed person analysis map list: Probabilities map missing _NumberOfVariantsTested")
|
|
}
|
|
|
|
numberOfTestedVariants, err := helpers.ConvertStringToInt(numberOfTestedVariantsString)
|
|
if (err != nil){
|
|
return false, 0, 0, errors.New("Malformed person analysis map list: Contains invalid _NumberOfVariantsTested")
|
|
}
|
|
|
|
return true, genomeDiseaseProbabilityInt, numberOfTestedVariants, nil
|
|
}
|
|
|
|
personAProbabilityIsKnown, personAWillPassVariantProbability, personANumberOfVariantsTested, err := getPersonWillPassDiseaseVariantProbability(personADiseaseAnalysisMap, personAGenomeIdentifier)
|
|
if (err != nil) { return err }
|
|
|
|
personBProbabilityIsKnown, personBWillPassVariantProbability, personBNumberOfVariantsTested, err := getPersonWillPassDiseaseVariantProbability(personBDiseaseAnalysisMap, personBGenomeIdentifier)
|
|
if (err != nil) { return err }
|
|
|
|
personANumberOfVariantsTestedString := helpers.ConvertIntToString(personANumberOfVariantsTested)
|
|
personBNumberOfVariantsTestedString := helpers.ConvertIntToString(personBNumberOfVariantsTested)
|
|
|
|
offspringDiseaseMap[personAGenomeIdentifier + "_NumberOfVariantsTested"] = personANumberOfVariantsTestedString
|
|
offspringDiseaseMap[personBGenomeIdentifier + "_NumberOfVariantsTested"] = personBNumberOfVariantsTestedString
|
|
|
|
genomePairIdentifier := personAGenomeIdentifier + "+" + personBGenomeIdentifier
|
|
|
|
if (personAProbabilityIsKnown == false || personBProbabilityIsKnown == false){
|
|
|
|
offspringDiseaseMap[genomePairIdentifier + "_ProbabilityOffspringHasDisease"] = "Unknown"
|
|
offspringDiseaseMap[genomePairIdentifier + "_ProbabilityOffspringHasVariant"] = "Unknown"
|
|
|
|
return nil
|
|
}
|
|
|
|
percentageProbabilityOffspringHasDisease, percentageProbabilityOffspringHasVariant, err := GetOffspringMonogenicDiseaseProbabilities(diseaseIsDominantOrRecessive, personAWillPassVariantProbability, personBWillPassVariantProbability)
|
|
if (err != nil) { return err }
|
|
|
|
probabilityOffspringHasDiseaseString := helpers.ConvertIntToString(percentageProbabilityOffspringHasDisease)
|
|
probabilityOffspringHasVariantString := helpers.ConvertIntToString(percentageProbabilityOffspringHasVariant)
|
|
|
|
offspringDiseaseMap[genomePairIdentifier + "_ProbabilityOffspringHasDisease"] = probabilityOffspringHasDiseaseString
|
|
offspringDiseaseMap[genomePairIdentifier + "_ProbabilityOffspringHasVariant"] = probabilityOffspringHasVariantString
|
|
|
|
return nil
|
|
}
|
|
|
|
err = addPairProbabilitiesToDiseaseMap(pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
err := addPairProbabilitiesToDiseaseMap(pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
}
|
|
|
|
// Now we calculate the probabilities for individual variants
|
|
// We add a map for each variant
|
|
|
|
offspringVariantsMapList := make([]map[string]string, 0, len(variantsList))
|
|
|
|
for _, variantObject := range variantsList{
|
|
|
|
variantIdentifier := variantObject.VariantIdentifier
|
|
|
|
offspringVariantMap := map[string]string{
|
|
"ItemType": "MonogenicDiseaseVariant",
|
|
"VariantIdentifier": variantIdentifier,
|
|
"DiseaseName": diseaseName,
|
|
}
|
|
|
|
addPairProbabilitiesToVariantMap := func(personAGenomeIdentifier string, personBGenomeIdentifier string)error{
|
|
|
|
genomePairIdentifier := personAGenomeIdentifier + "+" + personBGenomeIdentifier
|
|
|
|
//Outputs:
|
|
// -bool: Probability is known
|
|
// -float64: Probability that person will pass variant to offspring (between 0 and 1)
|
|
// -error
|
|
getPersonWillPassVariantProbability := func(personVariantsMapList []map[string]string, personGenomeIdentifier string)(bool, float64, error){
|
|
|
|
for _, personVariantMap := range personVariantsMapList{
|
|
|
|
itemType, exists := personVariantMap["ItemType"]
|
|
if (exists == false){
|
|
return false, 0, errors.New("PersonVariantsMapList missing ItemType")
|
|
}
|
|
if (itemType != "MonogenicDiseaseVariant"){
|
|
return false, 0, errors.New("Person variant map is malformed: Contains non-MonogenicDiseaseVariant itemType: " + itemType)
|
|
}
|
|
|
|
currentVariantIdentifier, exists := personVariantMap["VariantIdentifier"]
|
|
if (exists == false){
|
|
return false, 0, errors.New("Person variant map is malformed: Missing VariantIdentifier")
|
|
}
|
|
if (currentVariantIdentifier != variantIdentifier){
|
|
continue
|
|
}
|
|
|
|
personHasVariantInfo, exists := personVariantMap[personGenomeIdentifier + "_HasVariant"]
|
|
if (exists == false){
|
|
return false, 0, errors.New("Person variant map is malformed: missing person genome _HasVariant value.")
|
|
}
|
|
|
|
if (personHasVariantInfo == "Unknown"){
|
|
return false, 0, nil
|
|
}
|
|
if (personHasVariantInfo == "Yes;Yes"){
|
|
return true, 1, nil
|
|
}
|
|
if (personHasVariantInfo == "Yes;No" || personHasVariantInfo == "No;Yes"){
|
|
return true, 0.5, nil
|
|
}
|
|
if (personHasVariantInfo == "No;No"){
|
|
return true, 0, nil
|
|
}
|
|
return false, 0, errors.New("Person variant map is malformed: Contains invalid variant info: " + personHasVariantInfo)
|
|
}
|
|
|
|
return false, 0, errors.New("Cannot find disease variant in personVariantsMapList: " + variantIdentifier)
|
|
}
|
|
|
|
// Outputs:
|
|
// -bool: Probabilities are known
|
|
// -int: Lower bound Percentage Probability that offspring will have 0 mutations
|
|
// -int: Upper bound Percentage Probability that offspring will have 0 mutations
|
|
// -int: Lower bound Percentage Probability that offspring will have 1 mutation
|
|
// -int: Upper bound Percentage Probability that offspring will have 1 mutation
|
|
// -int: Lower bound Percentage Probability that offspring will have 2 mutations
|
|
// -int: Upper bound Percentage Probability that offspring will have 2 mutations
|
|
// -error
|
|
getOffspringVariantProbabilities := func()(bool, int, int, int, int, int, int, error){
|
|
|
|
//Outputs:
|
|
// -int: Percentage Probability of 0 mutations
|
|
// -int: Percentage Probability of 1 mutation
|
|
// -int: Percentage Probability of 2 mutations
|
|
// -error
|
|
getOffspringProbabilities := func(personAWillPassVariantProbability float64, personBWillPassVariantProbability float64)(int, int, int, error){
|
|
// This is the probability that neither will pass variant
|
|
// P = P(!A) * P(!B)
|
|
probabilityOf0Mutations := (1 - personAWillPassVariantProbability) * (1 - personBWillPassVariantProbability)
|
|
|
|
// This is the probability that either will pass variant, but not both
|
|
// P(A XOR B) = P(A) + P(B) - (2 * P(A and B))
|
|
probabilityOf1Mutation := personAWillPassVariantProbability + personBWillPassVariantProbability - (2 * personAWillPassVariantProbability * personBWillPassVariantProbability)
|
|
|
|
// This is the probability that both will pass variant
|
|
// P(A and B) = P(A) * P(B)
|
|
probabilityOf2Mutations := personAWillPassVariantProbability * personBWillPassVariantProbability
|
|
|
|
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 }
|
|
|
|
return percentageProbabilityOf0Mutations, percentageProbabilityOf1Mutation, percentageProbabilityOf2Mutations, nil
|
|
}
|
|
|
|
personAProbabilityIsKnown, personAWillPassVariantProbability, err := getPersonWillPassVariantProbability(personAVariantsMapList, personAGenomeIdentifier)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
personBProbabilityIsKnown, personBWillPassVariantProbability, err := getPersonWillPassVariantProbability(personBVariantsMapList, personBGenomeIdentifier)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
if (personAProbabilityIsKnown == false && personBProbabilityIsKnown == false){
|
|
return false, 0, 0, 0, 0, 0, 0, nil
|
|
}
|
|
|
|
if (personAProbabilityIsKnown == true && personBProbabilityIsKnown == false){
|
|
|
|
bestCasePersonBWillPassVariantProbability := float64(0)
|
|
worstCasePersonBWillPassVariantProbability := float64(1)
|
|
|
|
bestCase0MutationsProbability, bestCase1MutationProbability, bestCase2MutationsProbability, err := getOffspringProbabilities(personAWillPassVariantProbability, bestCasePersonBWillPassVariantProbability)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
worstCase0MutationsProbability, worstCase1MutationProbability, worstCase2MutationsProbability, err := getOffspringProbabilities(personAWillPassVariantProbability, worstCasePersonBWillPassVariantProbability)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
lowerBound1MutationProbability := min(bestCase1MutationProbability, worstCase1MutationProbability)
|
|
upperBound1MutationProbability := max(bestCase1MutationProbability, worstCase1MutationProbability)
|
|
|
|
return true, worstCase0MutationsProbability, bestCase0MutationsProbability, lowerBound1MutationProbability, upperBound1MutationProbability, bestCase2MutationsProbability, worstCase2MutationsProbability, nil
|
|
}
|
|
|
|
if (personAProbabilityIsKnown == false && personBProbabilityIsKnown == true){
|
|
|
|
bestCasePersonAWillPassVariantProbability := float64(0)
|
|
worstCasePersonAWillPassVariantProbability := float64(1)
|
|
|
|
bestCase0MutationsProbability, bestCase1MutationProbability, bestCase2MutationsProbability, err := getOffspringProbabilities(bestCasePersonAWillPassVariantProbability, personBWillPassVariantProbability)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
worstCase0MutationsProbability, worstCase1MutationProbability, worstCase2MutationsProbability, err := getOffspringProbabilities(worstCasePersonAWillPassVariantProbability, personBWillPassVariantProbability)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
lowerBound1MutationProbability := min(bestCase1MutationProbability, worstCase1MutationProbability)
|
|
upperBound1MutationProbability := max(bestCase1MutationProbability, worstCase1MutationProbability)
|
|
|
|
return true, worstCase0MutationsProbability, bestCase0MutationsProbability, lowerBound1MutationProbability, upperBound1MutationProbability, bestCase2MutationsProbability, worstCase2MutationsProbability, nil
|
|
}
|
|
|
|
offspring0MutationsProbability, offspring1MutationProbability, offspring2MutationsProbability, err := getOffspringProbabilities(personAWillPassVariantProbability, personBWillPassVariantProbability)
|
|
if (err != nil) { return false, 0, 0, 0, 0, 0, 0, err }
|
|
|
|
return true, offspring0MutationsProbability, offspring0MutationsProbability, offspring1MutationProbability, offspring1MutationProbability, offspring2MutationsProbability, offspring2MutationsProbability, nil
|
|
}
|
|
|
|
probabilitiesKnown, probabilityOf0MutationsLowerBound, probabilityOf0MutationsUpperBound, probabilityOf1MutationLowerBound, probabilityOf1MutationUpperBound, probabilityOf2MutationsLowerBound, probabilityOf2MutationsUpperBound, err := getOffspringVariantProbabilities()
|
|
if (err != nil) { return err }
|
|
|
|
if (probabilitiesKnown == false){
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf0MutationsLowerBound"] = "Unknown"
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf0MutationsUpperBound"] = "Unknown"
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf1MutationLowerBound"] = "Unknown"
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf1MutationUpperBound"] = "Unknown"
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf2MutationsLowerBound"] = "Unknown"
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf2MutationsUpperBound"] = "Unknown"
|
|
|
|
return nil
|
|
}
|
|
|
|
probabilityOf0MutationsLowerBoundString := helpers.ConvertIntToString(probabilityOf0MutationsLowerBound)
|
|
probabilityOf0MutationsUpperBoundString := helpers.ConvertIntToString(probabilityOf0MutationsUpperBound)
|
|
probabilityOf1MutationLowerBoundString := helpers.ConvertIntToString(probabilityOf1MutationLowerBound)
|
|
probabilityOf1MutationUpperBoundString := helpers.ConvertIntToString(probabilityOf1MutationUpperBound)
|
|
probabilityOf2MutationsLowerBoundString := helpers.ConvertIntToString(probabilityOf2MutationsLowerBound)
|
|
probabilityOf2MutationsUpperBoundString := helpers.ConvertIntToString(probabilityOf2MutationsUpperBound)
|
|
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf0MutationsLowerBound"] = probabilityOf0MutationsLowerBoundString
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf0MutationsUpperBound"] = probabilityOf0MutationsUpperBoundString
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf1MutationLowerBound"] = probabilityOf1MutationLowerBoundString
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf1MutationUpperBound"] = probabilityOf1MutationUpperBoundString
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf2MutationsLowerBound"] = probabilityOf2MutationsLowerBoundString
|
|
offspringVariantMap[genomePairIdentifier + "_ProbabilityOf2MutationsUpperBound"] = probabilityOf2MutationsUpperBoundString
|
|
|
|
return nil
|
|
}
|
|
|
|
err = addPairProbabilitiesToVariantMap(pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
err := addPairProbabilitiesToVariantMap(pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
}
|
|
|
|
offspringVariantsMapList = append(offspringVariantsMapList, offspringVariantMap)
|
|
}
|
|
|
|
// 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.
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
// Conflicts are only possible if two genome pairs exist
|
|
|
|
checkIfConflictExists := func()(bool, error){
|
|
|
|
genomePair1Identifier := pair1PersonAGenomeIdentifier + "+" + pair1PersonBGenomeIdentifier
|
|
|
|
genomePair2Identifier := pair2PersonAGenomeIdentifier + "+" + pair2PersonBGenomeIdentifier
|
|
|
|
allGenomePairIdentifiersList := []string{genomePair1Identifier, genomePair2Identifier}
|
|
|
|
probabilityOffspringHasDisease := ""
|
|
probabilityOffspringHasVariant := ""
|
|
|
|
for index, genomePairIdentifier := range allGenomePairIdentifiersList{
|
|
|
|
currentProbabilityOffspringHasDisease, exists := offspringDiseaseMap[genomePairIdentifier + "_ProbabilityOffspringHasDisease"]
|
|
if (exists == false) {
|
|
return false, errors.New("Cannot find _ProbabilityOffspringHasDisease key when searching for conflicts.")
|
|
}
|
|
currentProbabilityOffspringHasVariant, exists := offspringDiseaseMap[genomePairIdentifier + "_ProbabilityOffspringHasVariant"]
|
|
if (exists == false) {
|
|
return false, errors.New("Cannot find _ProbabilityOffspringHasVariant key when searching for conflicts.")
|
|
}
|
|
|
|
if (index == 0){
|
|
probabilityOffspringHasDisease = currentProbabilityOffspringHasDisease
|
|
probabilityOffspringHasVariant = currentProbabilityOffspringHasVariant
|
|
continue
|
|
}
|
|
if (currentProbabilityOffspringHasDisease != probabilityOffspringHasDisease){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOffspringHasVariant != probabilityOffspringHasVariant){
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
for _, variantMap := range offspringVariantsMapList{
|
|
|
|
probabilityOf0MutationsLowerBound := ""
|
|
probabilityOf0MutationsUpperBound := ""
|
|
probabilityOf1MutationLowerBound := ""
|
|
probabilityOf1MutationUpperBound := ""
|
|
probabilityOf2MutationsLowerBound := ""
|
|
probabilityOf2MutationsUpperBound := ""
|
|
|
|
for index, genomePairIdentifier := range allGenomePairIdentifiersList{
|
|
|
|
currentProbabilityOf0MutationsLowerBound, exists := variantMap[genomePairIdentifier + "_ProbabilityOf0MutationsLowerBound"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _ProbabilityOf0MutationsLowerBound key when searching for conflicts.")
|
|
}
|
|
currentProbabilityOf0MutationsUpperBound, exists := variantMap[genomePairIdentifier + "_ProbabilityOf0MutationsUpperBound"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _ProbabilityOf0MutationsUpperBound key when searching for conflicts.")
|
|
}
|
|
currentProbabilityOf1MutationLowerBound, exists := variantMap[genomePairIdentifier + "_ProbabilityOf1MutationLowerBound"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _ProbabilityOf1MutationLowerBound key when searching for conflicts.")
|
|
}
|
|
currentProbabilityOf1MutationUpperBound, exists := variantMap[genomePairIdentifier + "_ProbabilityOf1MutationUpperBound"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _ProbabilityOf1MutationUpperBound key when searching for conflicts.")
|
|
}
|
|
currentProbabilityOf2MutationsLowerBound, exists := variantMap[genomePairIdentifier + "_ProbabilityOf2MutationsLowerBound"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _ProbabilityOf2MutationsLowerBound key when searching for conflicts.")
|
|
}
|
|
currentProbabilityOf2MutationsUpperBound, exists := variantMap[genomePairIdentifier + "_ProbabilityOf2MutationsUpperBound"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _ProbabilityOf2MutationsUpperBound key when searching for conflicts.")
|
|
}
|
|
|
|
if (index == 0){
|
|
probabilityOf0MutationsLowerBound = currentProbabilityOf0MutationsLowerBound
|
|
probabilityOf0MutationsUpperBound = currentProbabilityOf0MutationsUpperBound
|
|
|
|
probabilityOf1MutationLowerBound = currentProbabilityOf1MutationLowerBound
|
|
probabilityOf1MutationUpperBound = currentProbabilityOf1MutationUpperBound
|
|
|
|
probabilityOf2MutationsLowerBound = currentProbabilityOf2MutationsLowerBound
|
|
probabilityOf2MutationsUpperBound = currentProbabilityOf2MutationsUpperBound
|
|
continue
|
|
}
|
|
|
|
if (currentProbabilityOf0MutationsLowerBound != probabilityOf0MutationsLowerBound){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOf0MutationsUpperBound != probabilityOf0MutationsUpperBound){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOf1MutationLowerBound != probabilityOf1MutationLowerBound){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOf1MutationUpperBound != probabilityOf1MutationUpperBound){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOf2MutationsLowerBound != probabilityOf2MutationsLowerBound){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOf2MutationsUpperBound != probabilityOf2MutationsUpperBound){
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
conflictExists, err := checkIfConflictExists()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
|
|
|
offspringDiseaseMap["ConflictExists"] = conflictExistsString
|
|
}
|
|
|
|
newAnalysisMapList = append(newAnalysisMapList, offspringDiseaseMap)
|
|
newAnalysisMapList = append(newAnalysisMapList, offspringVariantsMapList...)
|
|
}
|
|
|
|
// Step 2: Polygenic Diseases
|
|
|
|
polygenicDiseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList()
|
|
if (err != nil){ return false, "", err }
|
|
|
|
for _, diseaseObject := range polygenicDiseaseObjectsList{
|
|
|
|
diseaseName := diseaseObject.DiseaseName
|
|
diseaseLociList := diseaseObject.LociList
|
|
|
|
_, personALociMapList, err := getPolygenicDiseaseAnalysis(personAGenomesWithMetadataList, diseaseObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
_, personBLociMapList, err := getPolygenicDiseaseAnalysis(personBGenomesWithMetadataList, diseaseObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
offspringLociMapList := make([]map[string]string, 0, len(diseaseLociList))
|
|
|
|
for _, locusObject := range diseaseLociList{
|
|
|
|
locusIdentifier := locusObject.LocusIdentifier
|
|
|
|
locusRiskWeightsMap := locusObject.RiskWeightsMap
|
|
locusOddsRatiosMap := locusObject.OddsRatiosMap
|
|
|
|
offspringLocusMap := map[string]string{
|
|
"ItemType": "PolygenicDiseaseLocus",
|
|
"LocusIdentifier": locusIdentifier,
|
|
"DiseaseName": diseaseName,
|
|
}
|
|
|
|
// This will calculate the average risk weight and probability change for the offspring from the two specified genomes
|
|
// It then adds the pair entry to the diseaseMap
|
|
addPairDiseaseLocusInfoToLocusMap := func(personAGenomeIdentifier string, personBGenomeIdentifier string)error{
|
|
|
|
getPersonGenomeLocusBasePair := func(personGenomeIdentifier string, personLociMapList []map[string]string)(bool, string, error){
|
|
|
|
for _, locusMap := range personLociMapList{
|
|
|
|
itemType, exists := locusMap["ItemType"]
|
|
if (exists == false){
|
|
return false, "", errors.New("getPolygenicDiseaseAnalysis returning lociMapList with item missing ItemType")
|
|
}
|
|
if (itemType != "PolygenicDiseaseLocus"){
|
|
return false, "", errors.New("getPolygenicDiseaseAnalysis returning lociMapList with non-PolygenicDiseaseLocus item: " + itemType)
|
|
}
|
|
currentLocusIdentifier, exists := locusMap["LocusIdentifier"]
|
|
if (exists == false){
|
|
return false, "", errors.New("getPolygenicDiseaseAnalysis returning lociMapList with item missing LocusIdentifier")
|
|
}
|
|
|
|
if (currentLocusIdentifier != locusIdentifier){
|
|
continue
|
|
}
|
|
|
|
locusBasePair, exists := locusMap[personGenomeIdentifier + "_LocusBasePair"]
|
|
if (exists == false){
|
|
return false, "", errors.New("getPolygenicDiseaseAnalysis returning lociMapList with item missing _LocusBasePair")
|
|
}
|
|
|
|
if (locusBasePair == "Unknown"){
|
|
return false, "", nil
|
|
}
|
|
|
|
return true, locusBasePair, nil
|
|
}
|
|
|
|
return false, "", errors.New("getPolygenicDiseaseAnalysis returning lociMapList missing a locus: " + locusIdentifier)
|
|
}
|
|
|
|
genomePairIdentifier := personAGenomeIdentifier + "+" + personBGenomeIdentifier
|
|
|
|
personALocusBasePairKnown, personALocusBasePair, err := getPersonGenomeLocusBasePair(personAGenomeIdentifier, personALociMapList)
|
|
if (err != nil) { return err }
|
|
|
|
personBLocusBasePairKnown, personBLocusBasePair, err := getPersonGenomeLocusBasePair(personBGenomeIdentifier, personBLociMapList)
|
|
if (err != nil) { return err }
|
|
|
|
if (personALocusBasePairKnown == false || personBLocusBasePairKnown == false){
|
|
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringRiskWeight"] = "Unknown"
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringOddsRatio"] = "Unknown"
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringUnknownOddsRatiosWeightSum"] = "Unknown"
|
|
|
|
return nil
|
|
}
|
|
|
|
offspringAverageRiskWeight, offspringOddsRatioKnown, offspringAverageOddsRatio, averageUnknownOddsRatiosWeightSum, err := GetOffspringPolygenicDiseaseLocusInfo(locusRiskWeightsMap, locusOddsRatiosMap, personALocusBasePair, personBLocusBasePair)
|
|
if (err != nil) { return err }
|
|
|
|
averageRiskWeightString := helpers.ConvertIntToString(offspringAverageRiskWeight)
|
|
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringRiskWeight"] = averageRiskWeightString
|
|
|
|
if (offspringOddsRatioKnown == false){
|
|
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringOddsRatio"] = "Unknown"
|
|
|
|
} else {
|
|
|
|
averageOddsRatioString := helpers.ConvertFloat64ToString(offspringAverageOddsRatio)
|
|
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringOddsRatio"] = averageOddsRatioString
|
|
}
|
|
|
|
averageUnknownOddsRatiosWeightSumString := helpers.ConvertIntToString(averageUnknownOddsRatiosWeightSum)
|
|
|
|
offspringLocusMap[genomePairIdentifier + "_OffspringUnknownOddsRatiosWeightSum"] = averageUnknownOddsRatiosWeightSumString
|
|
|
|
return nil
|
|
}
|
|
|
|
err = addPairDiseaseLocusInfoToLocusMap(pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
err := addPairDiseaseLocusInfoToLocusMap(pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
}
|
|
|
|
offspringLociMapList = append(offspringLociMapList, offspringLocusMap)
|
|
}
|
|
|
|
// Now we iterate through loci to determine genome pair disease info
|
|
|
|
offspringPolygenicDiseaseMap := map[string]string{
|
|
"ItemType": "PolygenicDisease",
|
|
"DiseaseName": diseaseName,
|
|
}
|
|
|
|
polygenicDiseaseLociMap, err := polygenicDiseases.GetPolygenicDiseaseLociMap(diseaseName)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
addPairPolygenicDiseaseInfoToDiseaseMap := func(personAGenomeIdentifier string, personBGenomeIdentifier string)error{
|
|
|
|
genomePairIdentifier := personAGenomeIdentifier + "+" + personBGenomeIdentifier
|
|
|
|
numberOfLociTested := 0
|
|
summedRiskWeights := 0
|
|
minimumPossibleRiskWeightSum := 0
|
|
maximumPossibleRiskWeightSum := 0
|
|
|
|
for _, locusMap := range offspringLociMapList{
|
|
|
|
locusIdentifier, exists := locusMap["LocusIdentifier"]
|
|
if (exists == false){
|
|
return errors.New("offspringLociMapList item missing LocusIdentifier")
|
|
}
|
|
|
|
locusObject, exists := polygenicDiseaseLociMap[locusIdentifier]
|
|
if (exists == false){
|
|
return errors.New("offspringLociMapList contains locus not found in allDiseaseLociObjectsMap")
|
|
}
|
|
|
|
locusMinimumRiskWeight := locusObject.MinimumRiskWeight
|
|
locusMaximumRiskWeight := locusObject.MaximumRiskWeight
|
|
|
|
minimumPossibleRiskWeightSum += locusMinimumRiskWeight
|
|
maximumPossibleRiskWeightSum += locusMaximumRiskWeight
|
|
|
|
locusRiskWeight, exists := locusMap[genomePairIdentifier + "_OffspringRiskWeight"]
|
|
if (exists == false){
|
|
return errors.New("offspringLociMapList contains locusMap missing _OffspringRiskWeight")
|
|
}
|
|
|
|
if (locusRiskWeight == "Unknown"){
|
|
continue
|
|
}
|
|
numberOfLociTested += 1
|
|
|
|
locusRiskWeightInt, err := helpers.ConvertStringToInt(locusRiskWeight)
|
|
if (err != nil){
|
|
return errors.New("offspringLociMapList contains locusMap with invalid _OffspringRiskWeight: " + locusRiskWeight)
|
|
}
|
|
|
|
summedRiskWeights += locusRiskWeightInt
|
|
}
|
|
|
|
if (numberOfLociTested == 0){
|
|
|
|
// No loci were tested
|
|
offspringPolygenicDiseaseMap[genomePairIdentifier + "_NumberOfLociTested"] = "0"
|
|
offspringPolygenicDiseaseMap[genomePairIdentifier + "_OffspringRiskScore"] = "Unknown"
|
|
|
|
return nil
|
|
}
|
|
|
|
numberOfLociTestedString := helpers.ConvertIntToString(numberOfLociTested)
|
|
offspringPolygenicDiseaseMap[genomePairIdentifier + "_NumberOfLociTested"] = numberOfLociTestedString
|
|
|
|
pairDiseaseRiskScore, err := helpers.ScaleNumberProportionally(true, summedRiskWeights, minimumPossibleRiskWeightSum, maximumPossibleRiskWeightSum, 0, 10)
|
|
if (err != nil) { return err }
|
|
|
|
pairDiseaseRiskScoreString := helpers.ConvertIntToString(pairDiseaseRiskScore)
|
|
|
|
offspringPolygenicDiseaseMap[genomePairIdentifier + "_OffspringRiskScore"] = pairDiseaseRiskScoreString
|
|
|
|
return nil
|
|
}
|
|
|
|
addPairPolygenicDiseaseInfoToDiseaseMap(pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
err := addPairPolygenicDiseaseInfoToDiseaseMap(pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
}
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
// We check for conflicts
|
|
// Conflicts are only possible if two genome pairs exist
|
|
|
|
checkIfConflictExists := func()(bool, error){
|
|
|
|
genomePair1Identifier := pair1PersonAGenomeIdentifier + "+" + pair1PersonBGenomeIdentifier
|
|
|
|
genomePair2Identifier := pair2PersonAGenomeIdentifier + "+" + pair2PersonBGenomeIdentifier
|
|
|
|
allGenomePairIdentifiersList := []string{genomePair1Identifier, genomePair2Identifier}
|
|
|
|
for _, locusMap := range offspringLociMapList{
|
|
|
|
offspringRiskWeight := ""
|
|
offspringOddsRatio := ""
|
|
offspringUnknownOddsRatiosWeightSum := ""
|
|
|
|
for index, genomePairIdentifier := range allGenomePairIdentifiersList{
|
|
|
|
currentOffspringRiskWeight, exists := locusMap[genomePairIdentifier + "_OffspringRiskWeight"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _OffspringRiskWeight key when searching for conflicts.")
|
|
}
|
|
currentOffspringOddsRatio, exists := locusMap[genomePairIdentifier + "_OffspringOddsRatio"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _OffspringOddsRatio key when searching for conflicts.")
|
|
}
|
|
currentOffspringUnknownOddsRatiosWeightSum, exists := locusMap[genomePairIdentifier + "_OffspringUnknownOddsRatiosWeightSum"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _OffspringUnknownOddsRatiosWeightSum key when searching for conflicts.")
|
|
}
|
|
|
|
if (index == 0){
|
|
offspringRiskWeight = currentOffspringRiskWeight
|
|
offspringOddsRatio = currentOffspringOddsRatio
|
|
offspringUnknownOddsRatiosWeightSum = currentOffspringUnknownOddsRatiosWeightSum
|
|
continue
|
|
}
|
|
if (currentOffspringRiskWeight != offspringRiskWeight){
|
|
return true, nil
|
|
}
|
|
if (currentOffspringOddsRatio != offspringOddsRatio){
|
|
return true, nil
|
|
}
|
|
if (currentOffspringUnknownOddsRatiosWeightSum != offspringUnknownOddsRatiosWeightSum){
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
conflictExists, err := checkIfConflictExists()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
|
|
|
offspringPolygenicDiseaseMap["ConflictExists"] = conflictExistsString
|
|
}
|
|
|
|
newAnalysisMapList = append(newAnalysisMapList, offspringPolygenicDiseaseMap)
|
|
newAnalysisMapList = append(newAnalysisMapList, offspringLociMapList...)
|
|
}
|
|
|
|
// Step 3: Traits
|
|
|
|
traitObjectsList, err := traits.GetTraitObjectsList()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
for _, traitObject := range traitObjectsList{
|
|
|
|
traitName := traitObject.TraitName
|
|
traitRulesList := traitObject.RulesList
|
|
|
|
_, personATraitRulesMapList, err := getTraitAnalysis(personAGenomesWithMetadataList, traitObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
_, personBTraitRulesMapList, err := getTraitAnalysis(personBGenomesWithMetadataList, traitObject)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
offspringRulesMapList := make([]map[string]string, 0, len(traitRulesList))
|
|
|
|
for _, ruleObject := range traitRulesList{
|
|
|
|
ruleIdentifier := ruleObject.RuleIdentifier
|
|
|
|
// This is a list that describes the locus rsids and their values that must be fulfilled to pass the rule
|
|
ruleLocusObjectsList := ruleObject.LociList
|
|
|
|
offspringRuleMap := map[string]string{
|
|
"ItemType": "TraitRule",
|
|
"RuleIdentifier": ruleIdentifier,
|
|
"TraitName": traitName,
|
|
}
|
|
|
|
// This will calculate the number of offspring that will pass the rule for the offspring from the two specified genomes
|
|
// It then adds the pair entry to the ruleMap
|
|
addPairTraitRuleInfoToRuleMap := func(personAGenomeIdentifier string, personBGenomeIdentifier string)error{
|
|
|
|
//Outputs:
|
|
// -bool: All rule loci are known
|
|
// -map[string]string: RSID -> Locus base pair value
|
|
// -error
|
|
getPersonGenomeRuleLociValuesMap := func(personGenomeIdentifier string, personTraitRulesMapList []map[string]string)(bool, map[string]string, error){
|
|
|
|
for _, ruleMap := range personTraitRulesMapList{
|
|
|
|
itemType, exists := ruleMap["ItemType"]
|
|
if (exists == false){
|
|
return false, nil, errors.New("getTraitAnalysis returning rulesMapList with item missing ItemType")
|
|
}
|
|
if (itemType != "TraitRule"){
|
|
return false, nil, errors.New("getTraitAnalysis returning rulesMapList with non-TraitRule item: " + itemType)
|
|
}
|
|
currentRuleIdentifier, exists := ruleMap["RuleIdentifier"]
|
|
if (exists == false){
|
|
return false, nil, errors.New("getTraitAnalysis returning rulesMapList with item missing RuleIdentifier")
|
|
}
|
|
|
|
if (currentRuleIdentifier != ruleIdentifier){
|
|
continue
|
|
}
|
|
|
|
// Map structure: Locus identifier -> Locus base pair value
|
|
personTraitLociBasePairsMap := make(map[string]string)
|
|
|
|
for _, locusObject := range ruleLocusObjectsList{
|
|
|
|
locusIdentifier := locusObject.LocusIdentifier
|
|
|
|
personLocusBasePair, exists := ruleMap[personGenomeIdentifier + "_RuleLocusBasePair_" + locusIdentifier]
|
|
if (exists == false){
|
|
return false, nil, errors.New("_LocusBasePair_ value not found in person trait analysis ruleMap")
|
|
}
|
|
|
|
if (personLocusBasePair == "Unknown"){
|
|
// Not all locus values are known, thus, we cannot determine if the genome passes the rule
|
|
return false, nil, nil
|
|
}
|
|
|
|
personTraitLociBasePairsMap[locusIdentifier] = personLocusBasePair
|
|
}
|
|
|
|
return true, personTraitLociBasePairsMap, nil
|
|
}
|
|
|
|
return false, nil, errors.New("getTraitAnalysis returning rulesMapList missing a rule: " + ruleIdentifier)
|
|
}
|
|
|
|
allPersonALociKnown, personAGenomeRuleLociValuesMap, err := getPersonGenomeRuleLociValuesMap(personAGenomeIdentifier, personATraitRulesMapList)
|
|
if (err != nil) { return err }
|
|
|
|
allPersonBLociKnown, personBGenomeRuleLociValuesMap, err := getPersonGenomeRuleLociValuesMap(personBGenomeIdentifier, personBTraitRulesMapList)
|
|
if (err != nil) { return err }
|
|
|
|
genomePairIdentifier := personAGenomeIdentifier + "+" + personBGenomeIdentifier
|
|
|
|
if (allPersonALociKnown == false || allPersonBLociKnown == false){
|
|
|
|
// We only know how many of the 4 prospective offspring pass the rule if all loci are known for both people's genomes
|
|
|
|
offspringRuleMap[genomePairIdentifier + "_OffspringProbabilityOfPassingRule"] = "Unknown"
|
|
return nil
|
|
}
|
|
|
|
// This is a probability between 0 and 1
|
|
offspringProbabilityOfPassingRule := float64(1)
|
|
|
|
for _, ruleLocusObject := range ruleLocusObjectsList{
|
|
|
|
locusIdentifier := ruleLocusObject.LocusIdentifier
|
|
|
|
personALocusBasePair, exists := personAGenomeRuleLociValuesMap[locusIdentifier]
|
|
if (exists == false){
|
|
return errors.New("personAGenomeRuleLociValuesMap missing locusIdentifier")
|
|
}
|
|
|
|
personBLocusBasePair, exists := personBGenomeRuleLociValuesMap[locusIdentifier]
|
|
if (exists == false){
|
|
return errors.New("personBGenomeRuleLociValuesMap missing locusIdentifier")
|
|
}
|
|
|
|
locusRequiredBasePairsList := ruleLocusObject.BasePairsList
|
|
|
|
offspringProbabilityOfPassingRuleLocus, err := GetOffspringTraitRuleLocusInfo(locusRequiredBasePairsList, personALocusBasePair, personBLocusBasePair)
|
|
if (err != nil) { return err }
|
|
|
|
offspringProbabilityOfPassingRule *= offspringProbabilityOfPassingRuleLocus
|
|
}
|
|
|
|
offspringPercentageProbabilityOfPassingRule := offspringProbabilityOfPassingRule * 100
|
|
|
|
offspringPercentageProbabilityOfPassingRuleString := helpers.ConvertFloat64ToStringRounded(offspringPercentageProbabilityOfPassingRule, 0)
|
|
|
|
offspringRuleMap[genomePairIdentifier + "_OffspringProbabilityOfPassingRule"] = offspringPercentageProbabilityOfPassingRuleString
|
|
|
|
return nil
|
|
}
|
|
|
|
err = addPairTraitRuleInfoToRuleMap(pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
err := addPairTraitRuleInfoToRuleMap(pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
}
|
|
|
|
offspringRulesMapList = append(offspringRulesMapList, offspringRuleMap)
|
|
}
|
|
|
|
// Now we iterate through rules to determine genome pair trait info
|
|
|
|
offspringTraitMap := map[string]string{
|
|
"ItemType": "Trait",
|
|
"TraitName": traitName,
|
|
}
|
|
|
|
traitRulesMap, err := traits.GetTraitRulesMap(traitName)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
addPairTraitInfoToTraitMap := func(personAGenomeIdentifier string, personBGenomeIdentifier string)error{
|
|
|
|
genomePairIdentifier := personAGenomeIdentifier + "+" + personBGenomeIdentifier
|
|
|
|
if (len(traitRulesList) == 0){
|
|
// This trait is not yet able to be analyzed.
|
|
// This feature will be added soon
|
|
// These traits will use neural networks instead of rules
|
|
offspringTraitMap[genomePairIdentifier + "_NumberOfRulesTested"] = "0"
|
|
return nil
|
|
}
|
|
|
|
numberOfRulesTested := 0
|
|
|
|
// Map Structure: Outcome -> Average score from all rules
|
|
averageOutcomeScoresMap := make(map[string]float64)
|
|
|
|
for _, ruleMap := range offspringRulesMapList{
|
|
|
|
ruleIdentifier, exists := ruleMap["RuleIdentifier"]
|
|
if (exists == false){
|
|
return errors.New("offspringRulesMapList item missing RuleIdentifier")
|
|
}
|
|
|
|
offspringPercentageProbabilityOfPassingRule, exists := ruleMap[genomePairIdentifier + "_OffspringProbabilityOfPassingRule"]
|
|
if (exists == false){
|
|
return errors.New("offspringRulesMapList contains ruleMap missing _OffspringProbabilityOfPassingRule")
|
|
}
|
|
|
|
if (offspringPercentageProbabilityOfPassingRule == "Unknown"){
|
|
continue
|
|
}
|
|
|
|
numberOfRulesTested += 1
|
|
|
|
offspringPercentageProbabilityOfPassingRuleFloat64, err := helpers.ConvertStringToFloat64(offspringPercentageProbabilityOfPassingRule)
|
|
if (err != nil){
|
|
return errors.New("offspringRulesMapList contains ruleMap with invalid _OffspringProbabilityOfPassingRule: " + offspringPercentageProbabilityOfPassingRule)
|
|
}
|
|
|
|
// This is the 0 - 1 probability value
|
|
offspringProbabilityOfPassingRule := offspringPercentageProbabilityOfPassingRuleFloat64/100
|
|
|
|
ruleObject, exists := traitRulesMap[ruleIdentifier]
|
|
if (exists == false){
|
|
return errors.New("offspringRulesMapList contains rule not found in traitRulesMap: " + ruleIdentifier)
|
|
}
|
|
|
|
ruleOutcomePointsMap := ruleObject.OutcomePointsMap
|
|
|
|
for outcomeName, outcomePointsEffect := range ruleOutcomePointsMap{
|
|
|
|
pointsToAdd := float64(outcomePointsEffect) * offspringProbabilityOfPassingRule
|
|
|
|
averageOutcomeScoresMap[outcomeName] += pointsToAdd
|
|
}
|
|
}
|
|
|
|
numberOfRulesTestedString := helpers.ConvertIntToString(numberOfRulesTested)
|
|
offspringTraitMap[genomePairIdentifier + "_NumberOfRulesTested"] = numberOfRulesTestedString
|
|
|
|
if (numberOfRulesTested == 0){
|
|
|
|
return nil
|
|
}
|
|
|
|
traitOutcomesList := traitObject.OutcomesList
|
|
|
|
for _, outcomeName := range traitOutcomesList{
|
|
|
|
getOffspringAverageOutcomeScore := func()float64{
|
|
|
|
averageOutcomeScore, exists := averageOutcomeScoresMap[outcomeName]
|
|
if (exists == false){
|
|
// No rules effected this outcome.
|
|
return 0
|
|
}
|
|
return averageOutcomeScore
|
|
}
|
|
|
|
offspringAverageOutcomeScore := getOffspringAverageOutcomeScore()
|
|
|
|
offspringAverageOutcomeScoreString := helpers.ConvertFloat64ToStringRounded(offspringAverageOutcomeScore, 2)
|
|
|
|
offspringTraitMap[genomePairIdentifier + "_OffspringAverageOutcomeScore_" + outcomeName] = offspringAverageOutcomeScoreString
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
addPairTraitInfoToTraitMap(pair1PersonAGenomeIdentifier, pair1PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
err := addPairTraitInfoToTraitMap(pair2PersonAGenomeIdentifier, pair2PersonBGenomeIdentifier)
|
|
if (err != nil) { return false, "", err }
|
|
}
|
|
|
|
if (genomePair2Exists == true){
|
|
|
|
// We check for conflicts
|
|
// Conflicts are only possible if two genome pairs exist
|
|
|
|
checkIfConflictExists := func()(bool, error){
|
|
|
|
genomePair1Identifier := pair1PersonAGenomeIdentifier + "+" + pair1PersonBGenomeIdentifier
|
|
|
|
genomePair2Identifier := pair2PersonAGenomeIdentifier + "+" + pair2PersonBGenomeIdentifier
|
|
|
|
allGenomePairIdentifiersList := []string{genomePair1Identifier, genomePair2Identifier}
|
|
|
|
for _, ruleMap := range offspringRulesMapList{
|
|
|
|
offspringProbabilityOfPassingRule := ""
|
|
|
|
for index, genomePairIdentifier := range allGenomePairIdentifiersList{
|
|
|
|
currentOffspringProbabilityOfPassingRule, exists := ruleMap[genomePairIdentifier + "_OffspringProbabilityOfPassingRule"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _OffspringProbabilityOfPassingRule key when searching for conflicts.")
|
|
}
|
|
|
|
if (index == 0){
|
|
|
|
offspringProbabilityOfPassingRule = currentOffspringProbabilityOfPassingRule
|
|
continue
|
|
}
|
|
if (currentOffspringProbabilityOfPassingRule != offspringProbabilityOfPassingRule){
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
conflictExists, err := checkIfConflictExists()
|
|
if (err != nil) { return false, "", err }
|
|
|
|
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
|
|
|
offspringTraitMap["ConflictExists"] = conflictExistsString
|
|
}
|
|
|
|
newAnalysisMapList = append(newAnalysisMapList, offspringTraitMap)
|
|
newAnalysisMapList = append(newAnalysisMapList, offspringRulesMapList...)
|
|
}
|
|
|
|
analysisBytes, err := json.MarshalIndent(newAnalysisMapList, "", "\t")
|
|
if (err != nil) { return false, "", err }
|
|
|
|
analysisString := string(analysisBytes)
|
|
|
|
return true, analysisString, nil
|
|
}
|
|
|
|
// We also use this function when calculating offspring probabilities between users in viewProfileGui.go
|
|
//Outputs:
|
|
// -int: Percentage probability offspring has disease (0-100)
|
|
// -int: Percentage probability offspring has variant (0-100)
|
|
// -error
|
|
func GetOffspringMonogenicDiseaseProbabilities(dominantOrRecessive string, personAWillPassVariantPercentageProbability int, personBWillPassVariantPercentageProbability int)(int, int, error){
|
|
|
|
if (dominantOrRecessive != "Dominant" && dominantOrRecessive != "Recessive"){
|
|
return 0, 0, errors.New("GetOffspringMonogenicDiseaseProbabilities called with invalid dominantOrRecessive: " + dominantOrRecessive)
|
|
}
|
|
|
|
personAWillPassVariantProbability := float64(personAWillPassVariantPercentageProbability)/100
|
|
personBWillPassVariantProbability := float64(personBWillPassVariantPercentageProbability)/100
|
|
|
|
if (personAWillPassVariantProbability < 0 || personAWillPassVariantProbability > 1){
|
|
return 0, 0, errors.New("GetOffspringMonogenicDiseaseProbabilities called with invalid personAWillPassVariantProbability")
|
|
}
|
|
|
|
if (personBWillPassVariantProbability < 0 || personBWillPassVariantProbability > 1){
|
|
return 0, 0, errors.New("GetOffspringMonogenicDiseaseProbabilities called with invalid personBWillPassVariantProbability")
|
|
}
|
|
|
|
// 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 A passing a variant) + (Probability of person B passing a variant) - (Probability of offspring having disease)
|
|
// A person with a variant may have the disease, or just be a carrier.
|
|
probabilityOffspringHasVariant := personAWillPassVariantProbability + personBWillPassVariantProbability - (personAWillPassVariantProbability * personBWillPassVariantProbability)
|
|
|
|
if (dominantOrRecessive == "Dominant"){
|
|
|
|
// The probability of having the monogenic disease is the same as the probability of having a variant
|
|
|
|
percentageProbabilityOffspringHasVariant := int(probabilityOffspringHasVariant * 100)
|
|
|
|
return percentageProbabilityOffspringHasVariant, percentageProbabilityOffspringHasVariant, nil
|
|
}
|
|
|
|
// We find the probability of the offspring having the mongenic disease as follows:
|
|
// P(A and B) = P(A) * P(B)
|
|
// (Probability of person A Passing a variant) * (Probability of person B passing a variant)
|
|
probabilityOffspringHasDisease := personAWillPassVariantProbability * personBWillPassVariantProbability
|
|
|
|
percentageProbabilityOffspringHasDisease := probabilityOffspringHasDisease * 100
|
|
percentageProbabilityOffspringHasVariant := probabilityOffspringHasVariant * 100
|
|
|
|
// 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)
|
|
|
|
return percentageProbabilityOffspringHasDiseaseInt, percentageProbabilityOffspringHasVariantInt, nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -int: Offspring average risk weight
|
|
// -bool: Odds ratio is known
|
|
// -float64: Offspring average odds ratio
|
|
// -int: Offspring unknown odds ratios weight sum
|
|
// -error
|
|
func GetOffspringPolygenicDiseaseLocusInfo(locusRiskWeightsMap map[string]int, locusOddsRatiosMap map[string]float64, personALocusBasePair string, personBLocusBasePair string)(int, bool, float64, int, error){
|
|
|
|
personABase1, personABase2, delimiterFound := strings.Cut(personALocusBasePair, ";")
|
|
if (delimiterFound == false){
|
|
return 0, false, 0, 0, errors.New("GetOffspringPolygenicDiseaseLocusInfo called with invalid person A locus base pair: " + personALocusBasePair)
|
|
}
|
|
personBBase1, personBBase2, delimiterFound := strings.Cut(personBLocusBasePair, ";")
|
|
if (delimiterFound == false){
|
|
return 0, false, 0, 0, errors.New("GetOffspringPolygenicDiseaseLocusInfo called with invalid person B locus base pair: " + personBLocusBasePair)
|
|
}
|
|
|
|
// We create the 4 options for the offspring's bases at this locus
|
|
|
|
offspringBasePairOutcomeA := personABase1 + ";" + personBBase1
|
|
offspringBasePairOutcomeB := personABase2 + ";" + personBBase2
|
|
offspringBasePairOutcomeC := personABase1 + ";" + personBBase2
|
|
offspringBasePairOutcomeD := personABase2 + ";" + personBBase1
|
|
|
|
baseOutcomesList := []string{offspringBasePairOutcomeA, offspringBasePairOutcomeB, offspringBasePairOutcomeC, offspringBasePairOutcomeD}
|
|
|
|
summedRiskWeight := 0
|
|
|
|
numberOfSummedOddsRatios := 0
|
|
summedOddsRatios := float64(0)
|
|
|
|
numberOfSummedUnknownOddsRatioWeights := 0
|
|
summedUnknownOddsRatioWeights := 0
|
|
|
|
for _, outcomeBasePair := range baseOutcomesList{
|
|
|
|
isValid := verifyBasePair(outcomeBasePair)
|
|
if (isValid == false){
|
|
return 0, false, 0, 0, errors.New("GetOffspringPolygenicDiseaseLocusInfo called with invalid locus base pair: " + outcomeBasePair)
|
|
}
|
|
|
|
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 for both weight and odds ratio
|
|
|
|
summedOddsRatios += 1
|
|
numberOfSummedOddsRatios += 1
|
|
continue
|
|
}
|
|
summedRiskWeight += offspringOutcomeRiskWeight
|
|
|
|
offspringOutcomeOddsRatio, exists := locusOddsRatiosMap[outcomeBasePair]
|
|
if (exists == false){
|
|
// This particular outcome has no known odds ratio
|
|
// We add it to the unknown odds ratio weights sum
|
|
summedUnknownOddsRatioWeights += offspringOutcomeRiskWeight
|
|
numberOfSummedUnknownOddsRatioWeights += 1
|
|
} else {
|
|
summedOddsRatios += offspringOutcomeOddsRatio
|
|
numberOfSummedOddsRatios += 1
|
|
}
|
|
}
|
|
|
|
averageRiskWeight := summedRiskWeight/4
|
|
|
|
getAverageUnknownOddsRatiosWeightSum := func()int{
|
|
|
|
if (numberOfSummedUnknownOddsRatioWeights == 0){
|
|
return 0
|
|
}
|
|
averageUnknownOddsRatiosWeightSum := summedUnknownOddsRatioWeights/numberOfSummedUnknownOddsRatioWeights
|
|
return averageUnknownOddsRatiosWeightSum
|
|
}
|
|
|
|
averageUnknownOddsRatiosWeightSum := getAverageUnknownOddsRatiosWeightSum()
|
|
|
|
if (numberOfSummedOddsRatios == 0){
|
|
|
|
return averageRiskWeight, false, 0, averageUnknownOddsRatiosWeightSum, nil
|
|
}
|
|
|
|
averageOddsRatio := summedOddsRatios/float64(numberOfSummedOddsRatios)
|
|
|
|
return averageRiskWeight, true, averageOddsRatio, averageUnknownOddsRatiosWeightSum, nil
|
|
}
|
|
|
|
|
|
//Outputs:
|
|
// -float64: Probability of offspring passing rule (0-1)
|
|
// -error
|
|
func GetOffspringTraitRuleLocusInfo(locusRequiredBasePairsList []string, personALocusBasePair string, personBLocusBasePair string)(float64, error){
|
|
|
|
personABase1, personABase2, delimiterFound := strings.Cut(personALocusBasePair, ";")
|
|
if (delimiterFound == false){
|
|
return 0, errors.New("GetOffspringTraitRuleLocusInfo called with invalid personA locus base pair: " + personALocusBasePair)
|
|
}
|
|
personBBase1, personBBase2, delimiterFound := strings.Cut(personBLocusBasePair, ";")
|
|
if (delimiterFound == false){
|
|
return 0, errors.New("GetOffspringTraitRuleLocusInfo called with invalid personB locus base pair: " + personBLocusBasePair)
|
|
}
|
|
|
|
// We create the 4 options for the offspring's bases at this locus
|
|
|
|
offspringBasePairOutcomeA := personABase1 + ";" + personBBase1
|
|
offspringBasePairOutcomeB := personABase2 + ";" + personBBase2
|
|
offspringBasePairOutcomeC := personABase1 + ";" + personBBase2
|
|
offspringBasePairOutcomeD := personABase2 + ";" + personBBase1
|
|
|
|
baseOutcomesList := []string{offspringBasePairOutcomeA, offspringBasePairOutcomeB, offspringBasePairOutcomeC, offspringBasePairOutcomeD}
|
|
|
|
numberOfOffspringOutcomesWhomPassRuleLocus := 0
|
|
|
|
for _, outcomeBasePair := range baseOutcomesList{
|
|
|
|
isValid := verifyBasePair(outcomeBasePair)
|
|
if (isValid == false){
|
|
return 0, errors.New("GetOffspringTraitRuleLocusInfo called with invalid locus base pair: " + outcomeBasePair)
|
|
}
|
|
|
|
outcomePassesRuleLocus := slices.Contains(locusRequiredBasePairsList, outcomeBasePair)
|
|
if (outcomePassesRuleLocus == true){
|
|
numberOfOffspringOutcomesWhomPassRuleLocus += 1
|
|
}
|
|
}
|
|
|
|
offspringProbabilityOfPassingRuleLocus := float64(numberOfOffspringOutcomesWhomPassRuleLocus)/float64(4)
|
|
|
|
return offspringProbabilityOfPassingRuleLocus, nil
|
|
}
|
|
|
|
|
|
// This function will retrieve the base pair of the locus from the input genome map
|
|
// We need this because each rsID has aliases, so we must sometimes check those aliases to find locus values
|
|
//
|
|
// Outputs:
|
|
// -bool: Valid base pair value found
|
|
// -string: Base 1 Value (Nucleotide base for the SNP)
|
|
// -string: Base 2 Value (Nucleotide base for the SNP)
|
|
// -bool: Base pairs are phased
|
|
// -error
|
|
func getGenomeLocusBasePair(inputGenomeMap map[int64]locusValue.LocusValue, locusRSID int64)(bool, string, string, bool, error){
|
|
|
|
// Outputs:
|
|
// -bool: Locus value found
|
|
// -locusValue.LocusValue
|
|
// -error
|
|
getLocusValue := func()(bool, locusValue.LocusValue, error){
|
|
|
|
currentLocusValue, exists := inputGenomeMap[locusRSID]
|
|
if (exists == true){
|
|
return true, currentLocusValue, nil
|
|
}
|
|
|
|
// We check for aliases
|
|
|
|
anyAliasesExist, rsidAliasesList, err := locusMetadata.GetRSIDAliases(locusRSID)
|
|
if (err != nil) { return false, locusValue.LocusValue{}, err }
|
|
if (anyAliasesExist == false){
|
|
return false, locusValue.LocusValue{}, nil
|
|
}
|
|
|
|
for _, rsidAlias := range rsidAliasesList{
|
|
|
|
currentLocusValue, exists := inputGenomeMap[rsidAlias]
|
|
if (exists == true){
|
|
return true, currentLocusValue, nil
|
|
}
|
|
}
|
|
|
|
return false, locusValue.LocusValue{}, nil
|
|
}
|
|
|
|
locusValueFound, locusValueObject, err := getLocusValue()
|
|
if (err != nil) { return false, "", "", false, err }
|
|
if (locusValueFound == false){
|
|
return false, "", "", false, nil
|
|
}
|
|
|
|
base1Value := locusValueObject.Base1Value
|
|
base2Value := locusValueObject.Base2Value
|
|
locusIsPhased := locusValueObject.LocusIsPhased
|
|
|
|
return true, base1Value, base2Value, locusIsPhased, nil
|
|
}
|
|
|
|
|
|
|
|
//Outputs:
|
|
// -map[string]string: Monogenic disease analysis map (contains probabilities for each genomeIdentifier and number of tested variants)
|
|
// -[]map[string]string: Variants map list (contains variant info for each genomeIdentifier)
|
|
// -error
|
|
func getMonogenicDiseaseAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, diseaseObject monogenicDiseases.MonogenicDisease)(map[string]string, []map[string]string, error){
|
|
|
|
diseaseName := diseaseObject.DiseaseName
|
|
dominantOrRecessive := diseaseObject.DominantOrRecessive
|
|
variantsList := diseaseObject.VariantsList
|
|
|
|
// We use this map to keep track of which RSIDs corresponds to each variant
|
|
// Map Structure: Variant Identifier -> []rsID
|
|
variantRSIDsMap := make(map[string][]int64)
|
|
|
|
variantsMapList := make([]map[string]string, 0, len(variantsList))
|
|
|
|
for _, variantObject := range variantsList{
|
|
|
|
variantIdentifier := variantObject.VariantIdentifier
|
|
|
|
// Map Structure:
|
|
// -GenomeIdentifier -> ("Yes"/"No" + ";" + "Yes"/"No") or "Unknown"
|
|
variantMap := map[string]string{
|
|
"ItemType": "MonogenicDiseaseVariant",
|
|
"VariantIdentifier": variantIdentifier,
|
|
"DiseaseName": diseaseName,
|
|
}
|
|
|
|
variantRSID := variantObject.VariantRSID
|
|
variantDefectiveBase := variantObject.DefectiveBase
|
|
|
|
variantRSIDsList := []int64{variantRSID}
|
|
|
|
// We add aliases to variantRSIDsList
|
|
|
|
anyAliasesExist, rsidAliasesList, err := locusMetadata.GetRSIDAliases(variantRSID)
|
|
if (err != nil) { return nil, nil, err }
|
|
if (anyAliasesExist == true){
|
|
variantRSIDsList = append(variantRSIDsList, rsidAliasesList...)
|
|
}
|
|
|
|
variantRSIDsMap[variantIdentifier] = variantRSIDsList
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
genomeMap := genomeWithMetadataObject.GenomeMap
|
|
|
|
basePairValueFound, base1Value, base2Value, locusIsPhased, err := getGenomeLocusBasePair(genomeMap, variantRSID)
|
|
if (err != nil) { return nil, nil, err }
|
|
if (basePairValueFound == false){
|
|
|
|
variantMap[genomeIdentifier + "_HasVariant"] = "Unknown"
|
|
continue
|
|
}
|
|
|
|
getBaseIsVariantMutationBool := func(inputBase string)bool{
|
|
|
|
if (inputBase == variantDefectiveBase){
|
|
return true
|
|
}
|
|
// Base could be mutated to a different unhealthy base
|
|
// That mutation could be a neutral/healthier change
|
|
// We only care about this specific variant
|
|
return false
|
|
}
|
|
|
|
base1IsDefective := getBaseIsVariantMutationBool(base1Value)
|
|
base2IsDefective := getBaseIsVariantMutationBool(base2Value)
|
|
|
|
base1IsDefectiveString := helpers.ConvertBoolToYesOrNoString(base1IsDefective)
|
|
base2IsDefectiveString := helpers.ConvertBoolToYesOrNoString(base2IsDefective)
|
|
|
|
genomeHasVariantMapValue := base1IsDefectiveString + ";" + base2IsDefectiveString
|
|
locusIsPhasedString := helpers.ConvertBoolToYesOrNoString(locusIsPhased)
|
|
|
|
variantMap[genomeIdentifier + "_HasVariant"] = genomeHasVariantMapValue
|
|
variantMap[genomeIdentifier + "_LocusIsPhased"] = locusIsPhasedString
|
|
|
|
//TODO: Add LocusIsPhased to readGeneticAnalysis package
|
|
}
|
|
|
|
variantsMapList = append(variantsMapList, variantMap)
|
|
}
|
|
|
|
// Now we determine probability that user will pass disease to offspring, and probability that user has disease
|
|
// We compute this for each genome
|
|
|
|
diseaseAnalysisMap := make(map[string]string)
|
|
|
|
// We will use this list when checking for conflicts
|
|
allGenomeIdentifiersList := make([]string, 0, len(inputGenomesWithMetadataList))
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
|
|
allGenomeIdentifiersList = append(allGenomeIdentifiersList, genomeIdentifier)
|
|
|
|
numberOfVariantsTested := 0
|
|
|
|
for _, variantMap := range variantsMapList{
|
|
|
|
genomeHasVariantValue, exists := variantMap[genomeIdentifier + "_HasVariant"]
|
|
if (exists == false){
|
|
return nil, nil, errors.New("variantMap malformed: Map missing a _HasVariant for genome")
|
|
}
|
|
|
|
if (genomeHasVariantValue != "Unknown"){
|
|
numberOfVariantsTested += 1
|
|
}
|
|
}
|
|
|
|
numberOfVariantsTestedString := helpers.ConvertIntToString(numberOfVariantsTested)
|
|
|
|
diseaseAnalysisMap[genomeIdentifier + "_NumberOfVariantsTested"] = numberOfVariantsTestedString
|
|
|
|
// Outputs:
|
|
// -bool: Probability is known (will be false if the genome has no tested variants)
|
|
// -float64: Probability Person has disease
|
|
// -float64: Probability Person will pass a defect (variant) to offspring
|
|
// -error
|
|
getPersonDiseaseInfo := func()(bool, float64, float64, error){
|
|
|
|
anyProbabilityKnown := false
|
|
|
|
// These variables are used to count the number of defective variants that exist on each chromosome
|
|
numberOfVariants_Chromosome1 := 0
|
|
numberOfVariants_Chromosome2 := 0
|
|
numberOfVariants_UnknownChromosome := 0
|
|
|
|
// We use this map to keep track of how many mutations exist for each rsid
|
|
// This allows us to know if 2 different variant mutations exist for a single RSID
|
|
// For example, base1 is a different deleterious mutation than base2
|
|
// If this ever happens, we know that the user has the disease,
|
|
// because both copies of the gene locus are defective.
|
|
rsidMutationsMap := make(map[int64]int)
|
|
|
|
for _, variantMap := range variantsMapList{
|
|
|
|
personHasVariantStatus, exists := variantMap[genomeIdentifier + "_HasVariant"]
|
|
if (exists == false){
|
|
return false, 0, 0, errors.New("variantMap malformed: Map missing a _HasVariant for genome.")
|
|
}
|
|
|
|
if (personHasVariantStatus == "Unknown"){
|
|
// We don't know if the genome has the variant. Skip to next variant
|
|
continue
|
|
}
|
|
|
|
anyProbabilityKnown = true
|
|
|
|
locusIsPhasedStatus, exists := variantMap[genomeIdentifier + "_LocusIsPhased"]
|
|
if (exists == false){
|
|
return false, 0, 0, errors.New("variantMap malformed: Map missing _LocusIsPhased for genome.")
|
|
}
|
|
|
|
base1HasVariant, base2HasVariant, foundSemicolon := strings.Cut(personHasVariantStatus, ";")
|
|
if (foundSemicolon == false){
|
|
return false, 0, 0, errors.New("variantMap is malformed: Contains invalid personHasVariantValue: " + personHasVariantStatus)
|
|
}
|
|
|
|
if (base1HasVariant == "No" && base2HasVariant == "No"){
|
|
// Neither chromosome contains the variant mutation.
|
|
continue
|
|
}
|
|
|
|
if (base1HasVariant == "Yes" && base2HasVariant == "Yes"){
|
|
// Both chromosomes contain the same variant mutation.
|
|
// Person has the disease.
|
|
// Person will definitely pass disease variant to offspring.
|
|
return true, 1, 1, nil
|
|
}
|
|
|
|
// We know that this variant exists on 1 of the bases, but not both.
|
|
|
|
variantIdentifier, exists := variantMap["VariantIdentifier"]
|
|
if (exists == false){
|
|
return false, 0, 0, errors.New("VariantMap missing VariantIdentifier.")
|
|
}
|
|
|
|
variantRSIDsList, exists := variantRSIDsMap[variantIdentifier]
|
|
if (exists == false){
|
|
return false, 0, 0, errors.New("variantRSIDMap missing variantIdentifier.")
|
|
}
|
|
|
|
for _, rsid := range variantRSIDsList{
|
|
rsidMutationsMap[rsid] += 1
|
|
}
|
|
|
|
if (locusIsPhasedStatus == "Yes"){
|
|
|
|
if (base1HasVariant == "Yes"){
|
|
numberOfVariants_Chromosome1 += 1
|
|
}
|
|
if (base2HasVariant == "Yes"){
|
|
numberOfVariants_Chromosome2 += 1
|
|
}
|
|
} else {
|
|
|
|
if (base1HasVariant == "Yes" || base2HasVariant == "Yes"){
|
|
numberOfVariants_UnknownChromosome += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
if (anyProbabilityKnown == false){
|
|
return false, 0, 0, nil
|
|
}
|
|
|
|
totalNumberOfVariants := numberOfVariants_Chromosome1 + numberOfVariants_Chromosome2 + numberOfVariants_UnknownChromosome
|
|
|
|
if (totalNumberOfVariants == 0){
|
|
// Person does not have any disease variants.
|
|
// They do not have the disease, and have no chance of passing a disease variant
|
|
return true, 0, 0, nil
|
|
}
|
|
|
|
// Now we check to see if there are any loci which have 2 different variants, one for each base
|
|
|
|
for _, numberOfMutations := range rsidMutationsMap{
|
|
|
|
if (numberOfMutations >= 2){
|
|
// Person has 2 mutations on the same location
|
|
// They must have the disease, and will definitely pass a variant to their offspring
|
|
return true, 1, 1, nil
|
|
}
|
|
}
|
|
|
|
// At this point, we know that there are no homozygous variant mutations
|
|
// All variant mutations are heterozygous, meaning the other chromosome base is healthy
|
|
|
|
// Probability is expressed as a float between 0 - 1
|
|
getProbabilityPersonHasDisease := func()float64{
|
|
|
|
if (dominantOrRecessive == "Dominant"){
|
|
// Only 1 variant is needed for the person to have the disease
|
|
// We know they have at least 1 variant
|
|
return 1
|
|
}
|
|
|
|
// dominantOrRecessive == "Recessive"
|
|
|
|
if (totalNumberOfVariants == 1){
|
|
// There is only 1 variant in total.
|
|
// This single variant cannot exist on both chromosomes.
|
|
// The person does not have the disease
|
|
return 0
|
|
}
|
|
|
|
if (numberOfVariants_Chromosome1 >= 1 && numberOfVariants_Chromosome2 >= 1){
|
|
|
|
// We know there is at least 1 variant mutation on each chromosome
|
|
// Therefore, the person has the disease
|
|
return 1
|
|
}
|
|
|
|
if (numberOfVariants_UnknownChromosome == 0){
|
|
|
|
// We know that variants do not exist on both chromosomes, only on 1.
|
|
// Thus, the person does not have the disease
|
|
return 0
|
|
}
|
|
|
|
if (numberOfVariants_Chromosome1 == 0 && numberOfVariants_Chromosome2 == 0){
|
|
|
|
// All variants have an unknown phase, and we know there are multiple of them.
|
|
// The probability the person does not have the disease is the probability that all mutations are on the same chromosome
|
|
// We calculate the probability that all of the mutations are on the same chromosome
|
|
// If they are all on the same chromosome, the person does not have the disease
|
|
// If at least 1 variant exists on both chromosomes, the person has the disease
|
|
|
|
// We calculate the probability that all variants are all on the same chromosome
|
|
// Probability of n variants existing on the same chromosome = 1/(2^n)
|
|
// P(X) = Probability all variants existing on chromosome 1 = 1/(2^n)
|
|
// P(Y) = Probability all variants existing on chromosome 2 = 1/(2^n)
|
|
// P(A U B) = P(A) + P(B) (If A and B are mutually exclusive)
|
|
// P(X) and P(Y) are mutually exclusive
|
|
// Probability of n variants existing on either chromosome 1 or 2 = P(X) + P(Y)
|
|
// Probability person has disease = !P(X U Y)
|
|
|
|
probabilityAllVariantsAreOnOneChromosome := 1/(math.Pow(2, float64(numberOfVariants_UnknownChromosome)))
|
|
|
|
probabilityPersonHasDisease := 1 - (probabilityAllVariantsAreOnOneChromosome * 2)
|
|
|
|
return probabilityPersonHasDisease
|
|
}
|
|
|
|
// We know that there is at least 1 variant whose phase is known
|
|
// We know that there are no variants whose phase is known which exist on both chromosomes
|
|
// We know there are at least some variants whose phase is unknown
|
|
|
|
// The probability that the person has the disease is
|
|
// the probability that the unknown-phase variants exist on the same chromosome as the ones which we know exist do
|
|
// This probability is 50% for each unknown-phase variant
|
|
|
|
probabilityAllVariantsAreOnOneChromosome := 1/(math.Pow(2, float64(numberOfVariants_UnknownChromosome)))
|
|
|
|
probabilityPersonHasDisease := 1 - probabilityAllVariantsAreOnOneChromosome
|
|
|
|
return probabilityPersonHasDisease
|
|
}
|
|
|
|
probabilityPersonHasDisease := getProbabilityPersonHasDisease()
|
|
|
|
// We know all variants are heterozygous
|
|
|
|
// Probability person will not pass any of n variants to their offspring: 1/(2^n)
|
|
// Probability person will pass at least 1 of n variants to their offspring: 1 - (1/(2^n))
|
|
|
|
probabilityPersonWillPassAnyVariant := 1 - (1/(math.Pow(2, float64(totalNumberOfVariants))))
|
|
|
|
return true, probabilityPersonHasDisease, probabilityPersonWillPassAnyVariant, nil
|
|
}
|
|
|
|
probabilityKnown, probabilityPersonHasDisease, probabilityPersonWillPassAnyVariant, err := getPersonDiseaseInfo()
|
|
if (err != nil) { return nil, nil, err }
|
|
if (probabilityKnown == false){
|
|
diseaseAnalysisMap[genomeIdentifier + "_ProbabilityOfHavingDisease"] = "Unknown"
|
|
diseaseAnalysisMap[genomeIdentifier + "_ProbabilityOfPassingADiseaseVariant"] = "Unknown"
|
|
continue
|
|
}
|
|
|
|
percentageProbabilityPersonHasDisease := probabilityPersonHasDisease * 100
|
|
percentageProbabilityPersonWillPassADiseaseVariant := probabilityPersonWillPassAnyVariant * 100
|
|
|
|
probabilityOfHavingDiseaseString := helpers.ConvertFloat64ToStringRounded(percentageProbabilityPersonHasDisease, 0)
|
|
probabilityOfPassingADiseaseVariantString := helpers.ConvertFloat64ToStringRounded(percentageProbabilityPersonWillPassADiseaseVariant, 0)
|
|
|
|
diseaseAnalysisMap[genomeIdentifier + "_ProbabilityOfHavingDisease"] = probabilityOfHavingDiseaseString
|
|
diseaseAnalysisMap[genomeIdentifier + "_ProbabilityOfPassingADiseaseVariant"] = probabilityOfPassingADiseaseVariantString
|
|
}
|
|
|
|
if (len(allGenomeIdentifiersList) == 1){
|
|
// We do not need to check for conflicts
|
|
// Nothing left to do. Analysis is complete.
|
|
return diseaseAnalysisMap, variantsMapList, nil
|
|
}
|
|
|
|
// We check for conflicts
|
|
|
|
getConflictExistsBool := func()(bool, error){
|
|
|
|
// We start with disease analysis map
|
|
|
|
probabilityOfHavingDisease := ""
|
|
probabilityOfPassingAVariant := ""
|
|
|
|
for index, genomeIdentifier := range allGenomeIdentifiersList{
|
|
|
|
currentProbabilityOfHavingDisease, exists := diseaseAnalysisMap[genomeIdentifier + "_ProbabilityOfHavingDisease"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot create analysis: diseaseAnalysisMap missing _ProbabilityOfHavingDisease")
|
|
}
|
|
|
|
currentProbabilityOfPassingAVariant, exists := diseaseAnalysisMap[genomeIdentifier + "_ProbabilityOfPassingADiseaseVariant"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot create analysis: diseaseAnalysisMap missing _ProbabilityOfPassingADiseaseVariant")
|
|
}
|
|
|
|
if (index == 0){
|
|
probabilityOfHavingDisease = currentProbabilityOfHavingDisease
|
|
probabilityOfPassingAVariant = currentProbabilityOfPassingAVariant
|
|
continue
|
|
}
|
|
|
|
if (currentProbabilityOfHavingDisease != probabilityOfHavingDisease){
|
|
return true, nil
|
|
}
|
|
if (currentProbabilityOfPassingAVariant != probabilityOfPassingAVariant){
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
// Now we test variants for conflicts
|
|
|
|
for _, variantMap := range variantsMapList{
|
|
|
|
// Each variant value is either "Yes;No", "No;Yes", "No;No", or "Yes;Yes"
|
|
|
|
// Two different genomes have "Yes;No" and "No;Yes", it will not count as a conflict
|
|
// If the locus is unphased, then there is no difference between "Yes;No" and "No;Yes"
|
|
// If the locus is phased, then this flip is only meaningful if it effects the probability of disease/passing a variant
|
|
// We already checked those probabilities for conflicts earlier
|
|
// Therefore, any flip is not considered a conflict
|
|
|
|
genomeHasVariantStatus := ""
|
|
|
|
for index, genomeIdentifier := range allGenomeIdentifiersList{
|
|
|
|
currentGenomeHasVariantStatus, exists := variantMap[genomeIdentifier + "_HasVariant"]
|
|
if (exists == false){
|
|
return false, errors.New("variantMap missing genomeIdentifier key when checking for conflicts")
|
|
}
|
|
|
|
if (index == 0){
|
|
genomeHasVariantStatus = currentGenomeHasVariantStatus
|
|
continue
|
|
}
|
|
|
|
if (currentGenomeHasVariantStatus != genomeHasVariantStatus){
|
|
|
|
if (currentGenomeHasVariantStatus == "Yes;No" && genomeHasVariantStatus == "No;Yes"){
|
|
continue
|
|
}
|
|
if (currentGenomeHasVariantStatus == "No;Yes" && genomeHasVariantStatus == "Yes;No"){
|
|
continue
|
|
}
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
conflictExists, err := getConflictExistsBool()
|
|
if (err != nil) { return nil, nil, err }
|
|
|
|
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
|
|
|
diseaseAnalysisMap["ConflictExists"] = conflictExistsString
|
|
|
|
return diseaseAnalysisMap, variantsMapList, nil
|
|
}
|
|
|
|
|
|
|
|
//Outputs:
|
|
// -map[string]string: Polygenic Disease analysis map (contains weight/probability for each genomeIdentifier and number of loci tested)
|
|
// -[]map[string]string: Loci map list (contains locus info for each genomeIdentifier)
|
|
// -error
|
|
func getPolygenicDiseaseAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, diseaseObject polygenicDiseases.PolygenicDisease)(map[string]string, []map[string]string, error){
|
|
|
|
diseaseName := diseaseObject.DiseaseName
|
|
diseaseLociList := diseaseObject.LociList
|
|
|
|
lociMapList := make([]map[string]string, 0, len(diseaseLociList))
|
|
|
|
// Map Structure: Locus identifier -> Disease Locus object
|
|
locusObjectsMap := make(map[string]polygenicDiseases.DiseaseLocus)
|
|
|
|
for _, locusObject := range diseaseLociList{
|
|
|
|
locusIdentifier := locusObject.LocusIdentifier
|
|
|
|
locusObjectsMap[locusIdentifier] = locusObject
|
|
|
|
locusMap := map[string]string{
|
|
"ItemType": "PolygenicDiseaseLocus",
|
|
"LocusIdentifier": locusIdentifier,
|
|
"DiseaseName": diseaseName,
|
|
}
|
|
|
|
locusRSID := locusObject.LocusRSID
|
|
locusRiskWeightsMap := locusObject.RiskWeightsMap
|
|
locusOddsRatiosMap := locusObject.OddsRatiosMap
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
genomeMap := genomeWithMetadataObject.GenomeMap
|
|
|
|
//Outputs:
|
|
// -bool: Disease locus info is known
|
|
// -string: Locus Bases
|
|
// -int: Genome disease locus risk weight
|
|
// -bool: Genome disease locus odds ratio known
|
|
// -float64: Genome disease locus odds ratio
|
|
// -error
|
|
getGenomeDiseaseLocusInfo := func()(bool, string, int, bool, float64, error){
|
|
|
|
basePairValueFound, base1Value, base2Value, _, err := getGenomeLocusBasePair(genomeMap, locusRSID)
|
|
if (err != nil) { return false, "", 0, false, 0, err }
|
|
if (basePairValueFound == false){
|
|
return false, "", 0, false, 0, nil
|
|
}
|
|
|
|
locusBasePair := base1Value + ";" + base2Value
|
|
|
|
riskWeight, exists := locusRiskWeightsMap[locusBasePair]
|
|
if (exists == false){
|
|
// This is an unknown base combination
|
|
// We will treat it as a 0 risk weight
|
|
return true, locusBasePair, 0, true, 1, nil
|
|
}
|
|
|
|
if (riskWeight == 0){
|
|
return true, locusBasePair, 0, true, 1, nil
|
|
}
|
|
|
|
oddsRatio, exists := locusOddsRatiosMap[locusBasePair]
|
|
if (exists == false){
|
|
return true, locusBasePair, 0, false, 0, nil
|
|
}
|
|
|
|
return true, locusBasePair, riskWeight, true, oddsRatio, nil
|
|
}
|
|
|
|
diseaseLocusInfoKnown, locusBasePair, locusRiskWeight, locusOddsRatioKnown, locusOddsRatio, err := getGenomeDiseaseLocusInfo()
|
|
if (err != nil) { return nil, nil, err }
|
|
|
|
if (diseaseLocusInfoKnown == false){
|
|
|
|
locusMap[genomeIdentifier + "_LocusBasePair"] = "Unknown"
|
|
locusMap[genomeIdentifier + "_RiskWeight"] = "Unknown"
|
|
locusMap[genomeIdentifier + "_OddsRatio"] = "Unknown"
|
|
} else {
|
|
|
|
locusMap[genomeIdentifier + "_LocusBasePair"] = locusBasePair
|
|
|
|
riskWeightString := helpers.ConvertIntToString(locusRiskWeight)
|
|
|
|
locusMap[genomeIdentifier + "_RiskWeight"] = riskWeightString
|
|
|
|
if (locusOddsRatioKnown == false){
|
|
locusMap[genomeIdentifier + "_OddsRatio"] = "Unknown"
|
|
} else {
|
|
locusOddsRatioString := helpers.ConvertFloat64ToString(locusOddsRatio)
|
|
locusMap[genomeIdentifier + "_OddsRatio"] = locusOddsRatioString
|
|
}
|
|
}
|
|
}
|
|
|
|
lociMapList = append(lociMapList, locusMap)
|
|
}
|
|
|
|
// Now we construct polygenic disease probability map for each genome
|
|
|
|
diseaseAnalysisMap := make(map[string]string)
|
|
|
|
// We use this list to check for conflicts later
|
|
allGenomeIdentifiersList := make([]string, 0, len(inputGenomesWithMetadataList))
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
|
|
allGenomeIdentifiersList = append(allGenomeIdentifiersList, genomeIdentifier)
|
|
|
|
numberOfLociTested := 0
|
|
|
|
minimumPossibleRiskWeightSum := 0
|
|
maximumPossibleRiskWeightSum := 0
|
|
|
|
summedDiseaseRiskWeight := 0
|
|
|
|
for _, locusMap := range lociMapList{
|
|
|
|
genomeRiskWeight, exists := locusMap[genomeIdentifier + "_RiskWeight"]
|
|
if (exists == false){
|
|
return nil, nil, errors.New("locusMap malformed: Map missing a genomeIdentifier riskWeight")
|
|
}
|
|
|
|
if (genomeRiskWeight == "Unknown"){
|
|
// The genome does not have this locus
|
|
// We cannot test for risk weight
|
|
continue
|
|
}
|
|
|
|
numberOfLociTested += 1
|
|
|
|
locusIdentifier, exists := locusMap["LocusIdentifier"]
|
|
if (exists == false) {
|
|
return nil, nil, errors.New("locusMap malformed: Map missing LocusIdentifier")
|
|
}
|
|
|
|
locusObject, exists := locusObjectsMap[locusIdentifier]
|
|
if (exists == false){
|
|
return nil, nil, errors.New("LocusObjectsMap missing locus: " + locusIdentifier)
|
|
}
|
|
|
|
locusMinimumWeight := locusObject.MinimumRiskWeight
|
|
locusMaximumWeight := locusObject.MaximumRiskWeight
|
|
|
|
minimumPossibleRiskWeightSum += locusMinimumWeight
|
|
maximumPossibleRiskWeightSum += locusMaximumWeight
|
|
|
|
genomeRiskWeightInt, err := helpers.ConvertStringToInt(genomeRiskWeight)
|
|
if (err != nil) {
|
|
return nil, nil, errors.New("Locus map malformed: Contains invalid _RiskWeight: " + genomeRiskWeight)
|
|
}
|
|
|
|
summedDiseaseRiskWeight += genomeRiskWeightInt
|
|
}
|
|
|
|
numberOfLociTestedString := helpers.ConvertIntToString(numberOfLociTested)
|
|
|
|
diseaseAnalysisMap[genomeIdentifier + "_NumberOfLociTested"] = numberOfLociTestedString
|
|
|
|
if (numberOfLociTested == 0){
|
|
diseaseAnalysisMap[genomeIdentifier + "_RiskScore"] = "Unknown"
|
|
|
|
continue
|
|
}
|
|
|
|
diseaseRiskScore, err := helpers.ScaleNumberProportionally(true, summedDiseaseRiskWeight, minimumPossibleRiskWeightSum, maximumPossibleRiskWeightSum, 0, 10)
|
|
if (err != nil) { return nil, nil, err }
|
|
|
|
diseaseRiskScoreString := helpers.ConvertIntToString(diseaseRiskScore)
|
|
|
|
diseaseAnalysisMap[genomeIdentifier + "_RiskScore"] = diseaseRiskScoreString
|
|
}
|
|
|
|
if (len(allGenomeIdentifiersList) == 1){
|
|
// We do not need to check for conflicts
|
|
// Nothing left to do. Analysis is complete.
|
|
return diseaseAnalysisMap, lociMapList, nil
|
|
}
|
|
|
|
// We check for conflicts
|
|
|
|
getConflictExistsBool := func()(bool, error){
|
|
|
|
for _, locusMap := range lociMapList{
|
|
|
|
locusBasePair := ""
|
|
|
|
for index, genomeIdentifier := range allGenomeIdentifiersList{
|
|
|
|
currentLocusBasePair, exists := locusMap[genomeIdentifier + "_LocusBasePair"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _LocusBasePair key when searching for conflicts")
|
|
}
|
|
|
|
if (index == 0){
|
|
|
|
locusBasePair = currentLocusBasePair
|
|
continue
|
|
}
|
|
if (currentLocusBasePair != locusBasePair){
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
conflictExists, err := getConflictExistsBool()
|
|
if (err != nil) { return nil, nil, err }
|
|
|
|
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
|
|
|
diseaseAnalysisMap["ConflictExists"] = conflictExistsString
|
|
|
|
return diseaseAnalysisMap, lociMapList, nil
|
|
}
|
|
|
|
|
|
|
|
//Outputs:
|
|
// -map[string]string: Trait analysis map (contains weight/probability for each genomeIdentifier and number of rules tested)
|
|
// -[]map[string]string: Loci map list (contains locus info for each genomeIdentifier)
|
|
// -error
|
|
func getTraitAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, traitObject traits.Trait)(map[string]string, []map[string]string, error){
|
|
|
|
traitName := traitObject.TraitName
|
|
traitLociList := traitObject.LociList
|
|
traitRulesList := traitObject.RulesList
|
|
|
|
// We first add loci values to trait analysis map
|
|
|
|
traitAnalysisMap := make(map[string]string)
|
|
|
|
// We use this list to check for conflicts later
|
|
allGenomeIdentifiersList := make([]string, 0, len(inputGenomesWithMetadataList))
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
|
|
allGenomeIdentifiersList = append(allGenomeIdentifiersList, genomeIdentifier)
|
|
|
|
genomeMap := genomeWithMetadataObject.GenomeMap
|
|
|
|
for _, locusRSID := range traitLociList{
|
|
|
|
locusRSIDString := helpers.ConvertInt64ToString(locusRSID)
|
|
|
|
locusBasePairKnown, locusBase1, locusBase2, locusIsPhased, err := getGenomeLocusBasePair(genomeMap, locusRSID)
|
|
if (err != nil) { return nil, nil, err }
|
|
if (locusBasePairKnown == false){
|
|
|
|
traitAnalysisMap[genomeIdentifier + "_LocusValue_rs" + locusRSIDString] = "Unknown"
|
|
} else {
|
|
|
|
locusBasePair := locusBase1 + ";" + locusBase2
|
|
|
|
locusIsPhasedString := helpers.ConvertBoolToYesOrNoString(locusIsPhased)
|
|
|
|
traitAnalysisMap[genomeIdentifier + "_LocusValue_rs" + locusRSIDString] = locusBasePair
|
|
traitAnalysisMap[genomeIdentifier + "_LocusIsPhased_rs" + locusRSIDString] = locusIsPhasedString
|
|
}
|
|
}
|
|
}
|
|
|
|
// This map stores the TraitRule maps
|
|
rulesMapList := make([]map[string]string, 0, len(traitRulesList))
|
|
|
|
// Map Structure: Rule identifier -> Rule object
|
|
ruleObjectsMap := make(map[string]traits.TraitRule)
|
|
|
|
if (len(traitRulesList) != 0){
|
|
|
|
// This trait contains at least 1 rule
|
|
|
|
for _, ruleObject := range traitRulesList{
|
|
|
|
ruleIdentifier := ruleObject.RuleIdentifier
|
|
|
|
ruleObjectsMap[ruleIdentifier] = ruleObject
|
|
|
|
ruleMap := map[string]string{
|
|
"ItemType": "TraitRule",
|
|
"RuleIdentifier": ruleIdentifier,
|
|
"TraitName": traitName,
|
|
}
|
|
|
|
ruleLociList := ruleObject.LociList
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
genomeMap := genomeWithMetadataObject.GenomeMap
|
|
|
|
// We add locus base pairs to ruleMap
|
|
// We also check to see if genome passes all rule loci
|
|
// We only consider a rule Known if the genome either passes all loci, or fails to pass 1 locus
|
|
|
|
allRuleLociKnown := true
|
|
anyLocusFailureKnown := false //This is true if at least 1 locus is known to not pass
|
|
|
|
for _, locusObject := range ruleLociList{
|
|
|
|
locusIdentifier := locusObject.LocusIdentifier
|
|
locusRSID := locusObject.LocusRSID
|
|
|
|
locusBasePairKnown, locusBase1, locusBase2, _, err := getGenomeLocusBasePair(genomeMap, locusRSID)
|
|
if (err != nil) { return nil, nil, err }
|
|
if (locusBasePairKnown == false){
|
|
|
|
ruleMap[genomeIdentifier + "_RuleLocusBasePair_" + locusIdentifier] = "Unknown"
|
|
|
|
// The genome has failed to pass a single rule locus, thus, the rule is not passed
|
|
// We still continue so we can add all locus base pairs to the ruleMap
|
|
allRuleLociKnown = false
|
|
continue
|
|
}
|
|
|
|
locusBasePair := locusBase1 + ";" + locusBase2
|
|
|
|
ruleMap[genomeIdentifier + "_RuleLocusBasePair_" + locusIdentifier] = locusBasePair
|
|
|
|
if (anyLocusFailureKnown == true){
|
|
// We don't have to check if the rule locus is passed, because we already know the rule is not passed
|
|
continue
|
|
}
|
|
|
|
locusBasePairsList := locusObject.BasePairsList
|
|
|
|
genomePassesRuleLocus := slices.Contains(locusBasePairsList, locusBasePair)
|
|
if (genomePassesRuleLocus == false){
|
|
// We know the rule is not passed
|
|
// We still continue so we can add all locus base pairs to the ruleMap
|
|
anyLocusFailureKnown = true
|
|
continue
|
|
}
|
|
}
|
|
|
|
if (anyLocusFailureKnown == true){
|
|
|
|
ruleMap[genomeIdentifier + "_PassesRule"] = "No"
|
|
|
|
} else if (allRuleLociKnown == false){
|
|
|
|
ruleMap[genomeIdentifier + "_PassesRule"] = "Unknown"
|
|
} else {
|
|
|
|
ruleMap[genomeIdentifier + "_PassesRule"] = "Yes"
|
|
}
|
|
}
|
|
|
|
rulesMapList = append(rulesMapList, ruleMap)
|
|
}
|
|
|
|
// Now we construct trait outcome scores map for each genome
|
|
|
|
traitOutcomesList := traitObject.OutcomesList
|
|
|
|
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
|
|
// A rule is considered tested if at least 1 locus within the rule is known to fail, or all loci within the rule pass
|
|
numberOfRulesTested := 0
|
|
|
|
// Map Structure: Trait outcome -> Number of points
|
|
traitOutcomeScoresMap := make(map[string]int)
|
|
|
|
for _, ruleMap := range rulesMapList{
|
|
|
|
genomePassesRule, exists := ruleMap[genomeIdentifier + "_PassesRule"]
|
|
if (exists == false){
|
|
return nil, nil, errors.New("ruleMap malformed: Map missing a genomeIdentifier _PassesRule")
|
|
}
|
|
|
|
if (genomePassesRule == "Unknown"){
|
|
continue
|
|
}
|
|
|
|
numberOfRulesTested += 1
|
|
|
|
if (genomePassesRule == "No"){
|
|
continue
|
|
}
|
|
|
|
ruleIdentifier, exists := ruleMap["RuleIdentifier"]
|
|
if (exists == false) {
|
|
return nil, nil, errors.New("ruleMap malformed: Map missing RuleIdentifier")
|
|
}
|
|
|
|
ruleObject, exists := ruleObjectsMap[ruleIdentifier]
|
|
if (exists == false){
|
|
return nil, nil, errors.New("RuleObjectsMap missing rule: " + ruleIdentifier)
|
|
}
|
|
|
|
ruleOutcomePointsMap := ruleObject.OutcomePointsMap
|
|
|
|
for traitOutcome, pointsChange := range ruleOutcomePointsMap{
|
|
|
|
traitOutcomeScoresMap[traitOutcome] += pointsChange
|
|
}
|
|
}
|
|
|
|
numberOfRulesTestedString := helpers.ConvertIntToString(numberOfRulesTested)
|
|
|
|
traitAnalysisMap[genomeIdentifier + "_NumberOfRulesTested"] = numberOfRulesTestedString
|
|
|
|
if (numberOfRulesTested == 0){
|
|
// No rules were tested. The number of points in each outcome are all unknown.
|
|
continue
|
|
}
|
|
|
|
// We add all outcomes for which there were no points
|
|
|
|
for _, traitOutcome := range traitOutcomesList{
|
|
|
|
_, exists := traitOutcomeScoresMap[traitOutcome]
|
|
if (exists == false){
|
|
traitOutcomeScoresMap[traitOutcome] = 0
|
|
}
|
|
}
|
|
|
|
for traitOutcome, outcomeScore := range traitOutcomeScoresMap{
|
|
|
|
outcomeScoreString := helpers.ConvertIntToString(outcomeScore)
|
|
|
|
traitAnalysisMap[genomeIdentifier + "_OutcomeScore_" + traitOutcome] = outcomeScoreString
|
|
}
|
|
}
|
|
} else {
|
|
|
|
// No rules exist for this trait
|
|
|
|
for _, genomeIdentifier := range allGenomeIdentifiersList{
|
|
|
|
traitAnalysisMap[genomeIdentifier + "_NumberOfRulesTested"] = "0"
|
|
}
|
|
}
|
|
|
|
if (len(allGenomeIdentifiersList) == 1){
|
|
// We do not need to check for conflicts
|
|
// Nothing left to do. Analysis is complete.
|
|
return traitAnalysisMap, rulesMapList, nil
|
|
}
|
|
|
|
// We check for conflicts
|
|
|
|
getConflictExistsBool := func()(bool, error){
|
|
|
|
//TODO: Check for locus value conflicts once locus values are used in neural network prediction.
|
|
|
|
// We only have to check each rule result, because they will determine the overall person results
|
|
|
|
for _, ruleMap := range rulesMapList{
|
|
|
|
passesRule := ""
|
|
|
|
for index, genomeIdentifier := range allGenomeIdentifiersList{
|
|
|
|
currentPassesRule, exists := ruleMap[genomeIdentifier + "_PassesRule"]
|
|
if (exists == false){
|
|
return false, errors.New("Cannot find _PassesRule key when searching for conflicts")
|
|
}
|
|
|
|
if (index == 0){
|
|
|
|
passesRule = currentPassesRule
|
|
continue
|
|
}
|
|
if (currentPassesRule != passesRule){
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
conflictExists, err := getConflictExistsBool()
|
|
if (err != nil) { return nil, nil, err }
|
|
|
|
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
|
|
|
traitAnalysisMap["ConflictExists"] = conflictExistsString
|
|
|
|
return traitAnalysisMap, rulesMapList, nil
|
|
}
|
|
|
|
|