1647 lines
61 KiB
Go
1647 lines
61 KiB
Go
|
||
// calculatedAttributes provides functions to retrieve calculated profile attributes
|
||
// These are attributes that are constructed, rather than being retrieved from a profileMap directly.
|
||
// They can be constructed using profile attributes, a user's desires, and other information.
|
||
// An example is Distance, which requires calculating the distance between another user's coordinates and our own
|
||
|
||
package calculatedAttributes
|
||
|
||
import "seekia/imported/geodist"
|
||
|
||
import "seekia/resources/geneticReferences/monogenicDiseases"
|
||
import "seekia/resources/geneticReferences/polygenicDiseases"
|
||
import "seekia/resources/geneticReferences/traits"
|
||
|
||
import "seekia/internal/badgerDatabase"
|
||
import "seekia/internal/convertCurrencies"
|
||
import "seekia/internal/desires/myLocalDesires"
|
||
import "seekia/internal/desires/myMateDesires"
|
||
import "seekia/internal/encoding"
|
||
import "seekia/internal/genetics/companyAnalysis"
|
||
import "seekia/internal/genetics/createGeneticAnalysis"
|
||
import "seekia/internal/genetics/myChosenAnalysis"
|
||
import "seekia/internal/genetics/readGeneticAnalysis"
|
||
import "seekia/internal/helpers"
|
||
import "seekia/internal/identity"
|
||
import "seekia/internal/messaging/myChatMessages"
|
||
import "seekia/internal/moderation/moderatorControversy"
|
||
import "seekia/internal/moderation/moderatorScores"
|
||
import "seekia/internal/moderation/reviewStorage"
|
||
import "seekia/internal/myContacts"
|
||
import "seekia/internal/myIdentity"
|
||
import "seekia/internal/myIgnoredUsers"
|
||
import "seekia/internal/myLikedUsers"
|
||
import "seekia/internal/myMatchScore"
|
||
import "seekia/internal/profiles/myLocalProfiles"
|
||
import "seekia/internal/profiles/readProfiles"
|
||
|
||
import messagepack "github.com/vmihailenco/msgpack/v5"
|
||
|
||
import "sync"
|
||
import "strings"
|
||
import "errors"
|
||
import "slices"
|
||
|
||
//TODO:
|
||
// -LastActive
|
||
// -OffspringLactoseToleranceProbability
|
||
// Used to sort users based on probability of lactose tolerance
|
||
// This allows the user to sort matches based on whose offspring is most likely to be lactose tolerant
|
||
// -Offspring Probability for all traits
|
||
// -DietSimilarity
|
||
|
||
|
||
var calculatedAttributesList = []string{
|
||
"IdentityHash",
|
||
"IdentityScore",
|
||
"MatchScore",
|
||
"Controversy",
|
||
"BanAdvocates",
|
||
"Distance",
|
||
"IsSameSex",
|
||
"23andMe_OffspringNeanderthalVariants",
|
||
"OffspringProbabilityOfAnyMonogenicDisease",
|
||
"OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested",
|
||
"TotalPolygenicDiseaseRiskScore",
|
||
"TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested",
|
||
"OffspringTotalPolygenicDiseaseRiskScore",
|
||
"OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested",
|
||
"SearchTermsCount",
|
||
"HasMessagedMe",
|
||
"IHaveMessaged",
|
||
"HasRejectedMe",
|
||
"IsLiked",
|
||
"IsIgnored",
|
||
"IsMyContact",
|
||
"WealthInGold",
|
||
"RacialSimilarity",
|
||
"EyeColorSimilarity",
|
||
"EyeColorGenesSimilarity",
|
||
"EyeColorGenesSimilarity_NumberOfSimilarAlleles",
|
||
"HairColorSimilarity",
|
||
"HairColorGenesSimilarity",
|
||
"HairColorGenesSimilarity_NumberOfSimilarAlleles",
|
||
"SkinColorSimilarity",
|
||
"SkinColorGenesSimilarity",
|
||
"SkinColorGenesSimilarity_NumberOfSimilarAlleles",
|
||
"HairTextureSimilarity",
|
||
"HairTextureGenesSimilarity",
|
||
"HairTextureGenesSimilarity_NumberOfSimilarAlleles",
|
||
"FacialStructureGenesSimilarity",
|
||
"FacialStructureGenesSimilarity_NumberOfSimilarAlleles",
|
||
"23andMe_AncestralSimilarity",
|
||
"23andMe_MaternalHaplogroupSimilarity",
|
||
"23andMe_PaternalHaplogroupSimilarity",
|
||
"NumberOfReviews",
|
||
}
|
||
|
||
// We use a map for faster lookups
|
||
var calculatedAttributesMap map[string]struct{}
|
||
|
||
// We use this function to initialize the calculatedAttributesMap
|
||
func init(){
|
||
|
||
calculatedAttributesMap = make(map[string]struct{})
|
||
|
||
for _, attributeName := range calculatedAttributesList{
|
||
|
||
calculatedAttributesMap[attributeName] = struct{}{}
|
||
}
|
||
}
|
||
|
||
// We only use this function for testing
|
||
func GetCalculatedAttributesList()[]string{
|
||
|
||
return calculatedAttributesList
|
||
}
|
||
|
||
|
||
func GetRetrieveAnyProfileAttributeIncludingCalculatedFunction(profileVersion int, rawProfileMap map[int]messagepack.RawMessage)(func(string)(bool, int, string, error), error){
|
||
|
||
// We use this map to store formatted profile values
|
||
// We do this so we only have to format values once for all uses of the getAnyAttributeFunction iteration
|
||
formattedProfileMap := make(map[string]string)
|
||
|
||
// We use this mutex so it is safe to call the getAnyAttributeFunction concurrently
|
||
var formattedProfileMapMutex sync.RWMutex
|
||
|
||
getAttributeFunction := func(attributeName string)(bool, int, string, error){
|
||
|
||
formattedProfileMapMutex.RLock()
|
||
formattedAttributeValue, exists := formattedProfileMap[attributeName]
|
||
formattedProfileMapMutex.RUnlock()
|
||
if (exists == true){
|
||
return true, profileVersion, formattedAttributeValue, nil
|
||
}
|
||
|
||
// Now we check the rawProfileMap
|
||
|
||
attributeExists, formattedAttributeValue, err := readProfiles.GetFormattedProfileAttributeFromRawProfileMap(rawProfileMap, attributeName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (attributeExists == false){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
// We write the formatted value to the formattedProfileMap and return it
|
||
|
||
formattedProfileMapMutex.Lock()
|
||
formattedProfileMap[attributeName] = formattedAttributeValue
|
||
formattedProfileMapMutex.Unlock()
|
||
|
||
return true, profileVersion, formattedAttributeValue, nil
|
||
}
|
||
|
||
getAnyAttributeFunction := func(attributeName string)(bool, int, string, error){
|
||
|
||
attributeExists, _, attributeValue, err := GetAnyProfileAttributeIncludingCalculated(attributeName, getAttributeFunction)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (attributeExists == false){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
return true, profileVersion, attributeValue, nil
|
||
}
|
||
|
||
return getAnyAttributeFunction, nil
|
||
}
|
||
|
||
//Outputs:
|
||
// -bool: Attribute value is known
|
||
// -int: Profile version that attribute was derived from.
|
||
// This does not matter for calculated attributes, which will always return the same kind of result, regardless of profile versions.
|
||
// -string: Attribute value
|
||
// -error
|
||
func GetAnyProfileAttributeIncludingCalculated(attributeName string, getProfileAttributesFunction func(string)(bool, int, string, error))(bool, int, string, error){
|
||
|
||
_, isCalulatedAttribute := calculatedAttributesMap[attributeName]
|
||
if (isCalulatedAttribute == false){
|
||
|
||
exists, profileVersion, retrievedAttribute, err := getProfileAttributesFunction(attributeName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (exists == false) {
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
return true, profileVersion, retrievedAttribute, nil
|
||
}
|
||
|
||
exists, profileVersion, profileType, err := getProfileAttributesFunction("ProfileType")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (exists == false) {
|
||
return false, 0, "", errors.New("GetAnyProfileAttributeIncludingCalculated called with profile missing ProfileType")
|
||
}
|
||
|
||
getUserIdentityHash := func()([16]byte, error){
|
||
|
||
exists, _, identityKeyHex, err := getProfileAttributesFunction("IdentityKey")
|
||
if (err != nil) { return [16]byte{}, err }
|
||
if (exists == false) {
|
||
return [16]byte{}, errors.New("GetAnyProfileAttributeIncludingCalculated called with profile missing IdentityKey")
|
||
}
|
||
|
||
identityKeyBytes, err := encoding.DecodeHexStringToBytes(identityKeyHex)
|
||
if (err != nil){
|
||
return [16]byte{}, errors.New("GetAnyProfileAttributeIncludingCalculated called with profile containing non-hex IdentityKey: " + identityKeyHex)
|
||
}
|
||
|
||
if (len(identityKeyBytes) != 32){
|
||
return [16]byte{}, errors.New("GetAnyProfileAttributeIncludingCalculated called with profile containing invalid IdentityKey: Invalid length: " + identityKeyHex)
|
||
}
|
||
|
||
identityKeyArray := [32]byte(identityKeyBytes)
|
||
|
||
identityHash, err := identity.ConvertIdentityKeyToIdentityHash(identityKeyArray, profileType)
|
||
if (err != nil) { return [16]byte{}, err }
|
||
|
||
return identityHash, nil
|
||
}
|
||
|
||
getUserProfileNetworkType := func()(byte, error){
|
||
|
||
exists, _, networkTypeString, err := getProfileAttributesFunction("NetworkType")
|
||
if (err != nil) { return 0, err }
|
||
if (exists == false) {
|
||
return 0, errors.New("GetAnyProfileAttributeIncludingCalculated called with profile missing NetworkType")
|
||
}
|
||
|
||
networkType, err := helpers.ConvertNetworkTypeStringToByte(networkTypeString)
|
||
if (err != nil) {
|
||
return 0, errors.New("GetAnyProfileAttributeIncludingCalculated called with profile containing invalid NetworkType: " + networkTypeString)
|
||
}
|
||
|
||
return networkType, nil
|
||
}
|
||
|
||
switch attributeName{
|
||
|
||
case "IdentityHash":{
|
||
|
||
identityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
identityHashString, _, err := identity.EncodeIdentityHashBytesToString(identityHash)
|
||
if (err != nil){ return false, 0, "", err }
|
||
|
||
return true, profileVersion, identityHashString, nil
|
||
}
|
||
case "IdentityScore":{
|
||
|
||
if (profileType != "Moderator"){
|
||
return false, 0, "", errors.New("Trying to get identity score for non-moderator identity.")
|
||
}
|
||
|
||
identityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
scoreIsKnown, moderatorScore, _, _, _, err := moderatorScores.GetModeratorIdentityScore(identityHash)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (scoreIsKnown == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
moderatorScoreString := helpers.ConvertFloat64ToString(moderatorScore)
|
||
|
||
return true, profileVersion, moderatorScoreString, nil
|
||
}
|
||
case "MatchScore":{
|
||
|
||
if (profileType != "Mate"){
|
||
return false, 0, "", errors.New("Trying to get match score for non mate identity.")
|
||
}
|
||
|
||
// Calculating match score requires retrieving other calculated attributes
|
||
// We have to recursively call this function
|
||
|
||
getAnyAttributeForMatchScore := func(inputAttributeName string)(bool, int, string, error){
|
||
|
||
// We make sure infinite recursion will not happen accidentally
|
||
if (inputAttributeName == "MatchScore"){
|
||
return false, 0, "", errors.New("Infinite recursion occurs during match score attribute retrieval.")
|
||
}
|
||
|
||
exists, profileVersion, attributeValue, err := GetAnyProfileAttributeIncludingCalculated(inputAttributeName, getProfileAttributesFunction)
|
||
|
||
return exists, profileVersion, attributeValue, err
|
||
}
|
||
|
||
allMyDesiresList := myMateDesires.GetAllMyDesiresList(false)
|
||
|
||
matchScore := 0
|
||
|
||
for _, desireName := range allMyDesiresList{
|
||
|
||
myDesireExists, statusIsKnown, fulfillsDesire, err := myMateDesires.CheckIfMateProfileFulfillsMyDesire(desireName, getAnyAttributeForMatchScore)
|
||
if (err != nil){ return false, 0, "", err }
|
||
if (myDesireExists == false || statusIsKnown == false || fulfillsDesire == false){
|
||
continue
|
||
}
|
||
|
||
pointsToAdd, err := myMatchScore.GetMyMatchScoreDesirePoints(desireName)
|
||
if (err != nil){ return false, 0, "", err }
|
||
|
||
matchScore += pointsToAdd
|
||
}
|
||
|
||
matchScoreString := helpers.ConvertIntToString(matchScore)
|
||
|
||
return true, profileVersion, matchScoreString, nil
|
||
}
|
||
case "Controversy":{
|
||
|
||
if (profileType != "Moderator"){
|
||
return false, 0, "", errors.New("Trying to get Controversy for non-Moderator identity.")
|
||
}
|
||
|
||
identityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
profileNetworkType, err := getUserProfileNetworkType()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
isKnown, controversyRating, err := moderatorControversy.GetModeratorControversyRating(identityHash, profileNetworkType)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (isKnown == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
ratingString := helpers.ConvertInt64ToString(controversyRating)
|
||
|
||
return true, profileVersion, ratingString, nil
|
||
}
|
||
case "BanAdvocates":{
|
||
|
||
// This is the number of moderators who have banned this identity
|
||
// We will return both eligible and banned ban advocates
|
||
|
||
identityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
profileNetworkType, err := getUserProfileNetworkType()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
downloadingRequiredReviews, numberOfBanAdvocates, err := reviewStorage.GetNumberOfBanAdvocatesForIdentity(identityHash, profileNetworkType)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (downloadingRequiredReviews == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
numberOfBanAdvocatesString := helpers.ConvertIntToString(numberOfBanAdvocates)
|
||
|
||
return true, profileVersion, numberOfBanAdvocatesString, nil
|
||
}
|
||
case "Distance":{
|
||
|
||
if (profileType != "Mate"){
|
||
return false, 0, "", errors.New("Trying to get Distance for non-Mate identity.")
|
||
}
|
||
|
||
exists, _, theirLocationLatitudeString, err := getProfileAttributesFunction("PrimaryLocationLatitude")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (exists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
exists, _, theirLocationLongitudeString, err := getProfileAttributesFunction("PrimaryLocationLongitude")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (exists == false){
|
||
return false, 0, "", errors.New("Profile malformed when trying to calculate distance: contains PrimaryLocationLatitude and not PrimaryLocationLongitude")
|
||
}
|
||
|
||
theirLocationLatitudeFloat64, err := helpers.ConvertStringToFloat64(theirLocationLatitudeString)
|
||
if (err != nil) {
|
||
return false, 0, "", errors.New("Profile malformed when trying to calculate distance: contains invalid PrimaryLocationLatitude: " + theirLocationLatitudeString)
|
||
}
|
||
|
||
theirLocationLongitudeFloat64, err := helpers.ConvertStringToFloat64(theirLocationLongitudeString)
|
||
if (err != nil) {
|
||
return false, 0, "", errors.New("Profile malformed when trying to calculate distance: contains invalid PrimaryLocationLongitude: " + theirLocationLongitudeString)
|
||
}
|
||
|
||
exists, myLocationLatitudeString, err := myLocalProfiles.GetProfileData("Mate", "PrimaryLocationLatitude")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (exists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
exists, myLocationLongitudeString, err := myLocalProfiles.GetProfileData("Mate", "PrimaryLocationLongitude")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (exists == false){
|
||
return false, 0, "", errors.New("MyLocalProfiles contains PrimaryLocationLatitude but not PrimaryLocationLongitude")
|
||
}
|
||
|
||
myLocationLatitudeFloat64, err := helpers.ConvertStringToFloat64(myLocationLatitudeString)
|
||
if (err != nil) {
|
||
return false, 0, "", errors.New("MyLocalProfiles contains invalid PrimaryLocationLatitude: " + myLocationLatitudeString)
|
||
}
|
||
myLocationLongitudeFloat64, err := helpers.ConvertStringToFloat64(myLocationLongitudeString)
|
||
if (err != nil) {
|
||
return false, 0, "", errors.New("MyLocalProfiles contains invalid PrimaryLocationLongitude: " + myLocationLongitudeString)
|
||
}
|
||
|
||
distanceInKilometers, err := geodist.GetDistanceBetweenCoordinates(myLocationLatitudeFloat64, myLocationLongitudeFloat64, theirLocationLatitudeFloat64, theirLocationLongitudeFloat64)
|
||
if (err != nil){ return false, 0, "", err }
|
||
|
||
distanceString := helpers.ConvertFloat64ToString(distanceInKilometers)
|
||
|
||
return true, profileVersion, distanceString, nil
|
||
}
|
||
case "IsSameSex":{
|
||
|
||
// We use this to see if the user is the same sex as us
|
||
// We then know if we should display offspring attributes
|
||
// If they are the same sex, reproduction is impossible, so showing the offspring data is pointless
|
||
// We return Unknown if either person is of one of the intersex sexes
|
||
// This way, offspring data will be shown unless a Male is viewing a Female, or vice versa.
|
||
|
||
mySexExists, mySex, err := myLocalProfiles.GetProfileData("Mate", "Sex")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (mySexExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
if (mySex != "Male" && mySex != "Female"){
|
||
return false, 0, "", nil
|
||
}
|
||
userSexExists, _, userSex, err := getProfileAttributesFunction("Sex")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userSexExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
if (userSex != "Male" && userSex != "Female"){
|
||
return false, 0, "", nil
|
||
}
|
||
if (mySex == userSex){
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
case "23andMe_OffspringNeanderthalVariants":{
|
||
|
||
myNeanderthalVariantsExist, myNeanderthalVariants, err := myLocalProfiles.GetProfileData("Mate", "23andMe_NeanderthalVariants")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myNeanderthalVariantsExist == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
myNeanderthalVariantsInt, err := helpers.ConvertStringToInt(myNeanderthalVariants)
|
||
if (err != nil) {
|
||
return false, 0, "", errors.New("MyLocalProfiles malformed: Contains invalid 23andMe_NeanderthalVariants: " + myNeanderthalVariants)
|
||
}
|
||
|
||
userNeanderthalVariantsExist, _, userNeanderthalVariants, err := getProfileAttributesFunction("23andMe_NeanderthalVariants")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userNeanderthalVariantsExist == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userNeanderthalVariantsInt, err := helpers.ConvertStringToInt(userNeanderthalVariants)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("GetAnyProfileAttributeIncludingCalculated called with profile containing invalid 23andMe_NeanderthalVariants: " + userNeanderthalVariants)
|
||
}
|
||
|
||
// We take the average of both
|
||
offspringNeanderthalVariants := (myNeanderthalVariantsInt + userNeanderthalVariantsInt)/2
|
||
|
||
offspringNeanderthalVariantsString := helpers.ConvertIntToString(offspringNeanderthalVariants)
|
||
|
||
return true, profileVersion, offspringNeanderthalVariantsString, nil
|
||
}
|
||
case "OffspringProbabilityOfAnyMonogenicDisease",
|
||
"OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested":{
|
||
|
||
// We say the probability is known if:
|
||
// 1. At least 1 tested variant exists for both people for the same monogenic disease
|
||
// 2. For any monogenic diseases where either person has a non-zero probability, we know the offspring has a 0 probability
|
||
// For users using the 0% desire, they can be sure that:
|
||
// 1. Their matches have at least been analyzed by 1 company.
|
||
// 2. Their matches will never be carriers for the same monogenic disease(s) as them.
|
||
|
||
// TODO Users should eventually be able to filter users based on how many variants the offspring has been tested for
|
||
// For example, If a user has had their entire genome sequenced, they will be able to only show other users who have done the same
|
||
|
||
myPersonChosen, myGenomesExist, myAnalysisIsReady, myGeneticAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
|
||
|
||
// We have not linked a genome person and performed a genetic analysis
|
||
// The total monogenic disease risk is unknown
|
||
// We can still predict disease risk for individual recessive disorders when one person has no variants, but
|
||
// not the total probability for all monogenic diseases
|
||
// This is because everyone is a carrier for at least some recessive monogenic disorders
|
||
//
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
monogenicDiseaseObjectsList, err := monogenicDiseases.GetMonogenicDiseaseObjectsList()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
// This will be true if either we or the user have a non-zero probability of passing a variant,
|
||
// but the opposite person has an unknown probability of passing a variant
|
||
// This is a much higher risk scenario
|
||
// For monogenic diseases where both user probabilities are unknown, this does not apply
|
||
// Basically, we want to exercise a higher level of caution if we are aware of a non-zero potential risk
|
||
// This will also be true if either user has any dominant monogenic diseases
|
||
// Thus, if our final disease probability is 0%, and this bool is true, we will say probability is Unknown
|
||
nonZeroUnknownDiseaseRiskExists := false
|
||
|
||
// This will store the number of diseases we can test for
|
||
// This is displayed to the user in the viewProfileGui
|
||
numberOfDiseasesTested := 0
|
||
|
||
// We use this bool to track if the user has provided any monogenic disease probabilities
|
||
// If they have not, then we will return "Unknown" for the same reason we described for when our own analysis does not exist
|
||
//TODO: Require both probabilities to exist for the same disease at least once?
|
||
anyUserProbabilityIsKnown := false
|
||
|
||
// This stores the probability of the offspring having each tested disease
|
||
// Each float in this list is a value between 0-1
|
||
allDiseaseProbabilitiesList := make([]float64, 0)
|
||
|
||
for _, diseaseObject := range monogenicDiseaseObjectsList{
|
||
|
||
monogenicDiseaseName := diseaseObject.DiseaseName
|
||
diseaseIsDominantOrRecessive := diseaseObject.DominantOrRecessive
|
||
|
||
diseaseNameWithUnderscores := strings.ReplaceAll(monogenicDiseaseName, " ", "_")
|
||
|
||
probabilityOfPassingAVariantAttributeName := "MonogenicDisease_" + diseaseNameWithUnderscores + "_ProbabilityOfPassingAVariant"
|
||
|
||
userProbabilityIsKnown, _, userProbabilityOfPassingADiseaseVariant, err := getProfileAttributesFunction(probabilityOfPassingAVariantAttributeName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
if (userProbabilityIsKnown == true){
|
||
anyUserProbabilityIsKnown = true
|
||
}
|
||
|
||
myProbabilityIsKnown, _, myProbabilityOfPassingADiseaseVariant, _, _, _, _, _, err := readGeneticAnalysis.GetPersonMonogenicDiseaseInfoFromGeneticAnalysis(myGeneticAnalysisObject, monogenicDiseaseName, myGenomeIdentifier)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
if (userProbabilityIsKnown == false && myProbabilityIsKnown == false){
|
||
continue
|
||
}
|
||
|
||
getUserProbabilityOfPassingADiseaseVariantInt := func()(int, error){
|
||
|
||
if (userProbabilityIsKnown == false){
|
||
return 0, nil
|
||
}
|
||
|
||
userProbabilityOfPassingADiseaseVariantInt, err := helpers.ConvertStringToInt(userProbabilityOfPassingADiseaseVariant)
|
||
if (err != nil){
|
||
return 0, errors.New("GetAnyProfileAttributeIncludingCalculated called with profile containing invalid " + probabilityOfPassingAVariantAttributeName + ": " + userProbabilityOfPassingADiseaseVariant)
|
||
}
|
||
|
||
return userProbabilityOfPassingADiseaseVariantInt, nil
|
||
}
|
||
|
||
userProbabilityOfPassingADiseaseVariantInt, err := getUserProbabilityOfPassingADiseaseVariantInt()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
probabilityOffspringHasDiseaseIsKnown, offspringPercentageProbabilityOfDisease, _, _, err := createGeneticAnalysis.GetOffspringMonogenicDiseaseProbabilities(diseaseIsDominantOrRecessive, myProbabilityIsKnown, myProbabilityOfPassingADiseaseVariant, userProbabilityIsKnown, userProbabilityOfPassingADiseaseVariantInt)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (probabilityOffspringHasDiseaseIsKnown == false){
|
||
|
||
// We do not know the probability the offspring will have this disease
|
||
// We check to see if either person has a non-zero risk
|
||
// If so, the probability of the offspring having the disease is potentially >0
|
||
|
||
getNonZeroUnknownDiseaseRiskExistsBool := func()bool{
|
||
|
||
if (diseaseIsDominantOrRecessive == "Recessive"){
|
||
// We know there exists a non-zero risk
|
||
// We know that at least 1 of the two people has a known pass-the-variant probability
|
||
// If either person had a 0% probability of passing a variant, the
|
||
// GetOffspringMonogenicDiseaseProbabilities function would have returned a 0% risk of
|
||
/// the offspring having the disease.
|
||
// Thus, there exists the possibility of the offspring having the disease
|
||
// We will warn the user about this, so they can get tested and make sure they are not
|
||
// a carrier for the same disease
|
||
|
||
return true
|
||
}
|
||
// diseaseIsDominantOrRecessive == "Dominant"
|
||
|
||
if (userProbabilityIsKnown == true && userProbabilityOfPassingADiseaseVariantInt != 0){
|
||
return true
|
||
}
|
||
|
||
if (myProbabilityIsKnown == true && myProbabilityOfPassingADiseaseVariant != 0){
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
nonZeroUnknownDiseaseRiskExists = getNonZeroUnknownDiseaseRiskExistsBool()
|
||
|
||
continue
|
||
}
|
||
|
||
numberOfDiseasesTested += 1
|
||
|
||
if (attributeName == "OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested"){
|
||
// We don't care about retrieving the total probability, so we can skip to the next disease
|
||
continue
|
||
}
|
||
|
||
if (offspringPercentageProbabilityOfDisease == 100){
|
||
// Probability that offspring will have a disease is 100%
|
||
|
||
return true, profileVersion, "100", nil
|
||
}
|
||
|
||
offspringProbabilityOfDisease := float64(offspringPercentageProbabilityOfDisease)/float64(100)
|
||
|
||
allDiseaseProbabilitiesList = append(allDiseaseProbabilitiesList, offspringProbabilityOfDisease)
|
||
}
|
||
|
||
if (anyUserProbabilityIsKnown == false){
|
||
// We need at least 1 disease probability from the user for this attribute's status to be known
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
if (attributeName == "OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested"){
|
||
|
||
numberOfDiseasesTestedString := helpers.ConvertIntToString(numberOfDiseasesTested)
|
||
|
||
return true, profileVersion, numberOfDiseasesTestedString, nil
|
||
}
|
||
|
||
if (len(allDiseaseProbabilitiesList) == 0){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
// We need to find the probability of any of the probabilities occurring
|
||
// Inclusive OR for all probabilities in the list
|
||
// P(At least one disease) = 1 − P(No disease)
|
||
|
||
probabilityOfNoMonogenicDiseases := float64(1)
|
||
|
||
for _, diseaseProbability := range allDiseaseProbabilitiesList{
|
||
|
||
if (diseaseProbability < 0 || diseaseProbability > 1){
|
||
return false, 0, "", errors.New("allDiseaseProbabilitiesList contains invalid disease probability.")
|
||
}
|
||
|
||
// We multiply by the probability of no disease
|
||
probabilityOfNoMonogenicDiseases *= (1 - diseaseProbability)
|
||
}
|
||
|
||
probabilityOfAtLeast1Disease := 1 - probabilityOfNoMonogenicDiseases
|
||
|
||
if (probabilityOfAtLeast1Disease == 0 && nonZeroUnknownDiseaseRiskExists == true){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
totalRiskProbabilityPercentage := probabilityOfAtLeast1Disease * 100
|
||
|
||
totalRiskProbabilityPercentageString := helpers.ConvertFloat64ToStringRounded(totalRiskProbabilityPercentage, 0)
|
||
|
||
return true, profileVersion, totalRiskProbabilityPercentageString, nil
|
||
}
|
||
case "TotalPolygenicDiseaseRiskScore",
|
||
"TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested":{
|
||
|
||
// This attribute describes the user's total polygenic disease risk score
|
||
// This enables users to choose a mate who has a low risk of polygenic diseases
|
||
// The value is a number between 0 and 100
|
||
|
||
//TODO: Users should be able to filter by the number of loci and diseases tested
|
||
|
||
polygenicDiseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
numberOfDiseasesTested := 0
|
||
|
||
// This variable adds up the risk score for each disease
|
||
// Each risk score is a number between 0 and 1
|
||
allDiseasesAverageRiskScoreNumerator := float64(0)
|
||
|
||
for _, diseaseObject := range polygenicDiseaseObjectsList{
|
||
|
||
diseaseLociList := diseaseObject.LociList
|
||
|
||
userRiskWeightSum := 0
|
||
userMinimumPossibleRiskWeightSum := 0
|
||
userMaximumPossibleRiskWeightSum := 0
|
||
userNumberOfLociTested := 0
|
||
|
||
for _, locusObject := range diseaseLociList{
|
||
|
||
locusRSID := locusObject.LocusRSID
|
||
|
||
locusRSIDString := helpers.ConvertInt64ToString(locusRSID)
|
||
|
||
locusRiskWeightsMap := locusObject.RiskWeightsMap
|
||
locusMinimumRiskWeight := locusObject.MinimumRiskWeight
|
||
locusMaximumRiskWeight := locusObject.MaximumRiskWeight
|
||
|
||
locusValueAttributeName := "LocusValue_rs" + locusRSIDString
|
||
|
||
userLocusBasePairExists, _, userLocusBasePair, err := getProfileAttributesFunction(locusValueAttributeName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userLocusBasePairExists == false){
|
||
continue
|
||
}
|
||
|
||
userNumberOfLociTested += 1
|
||
|
||
userMinimumPossibleRiskWeightSum += locusMinimumRiskWeight
|
||
userMaximumPossibleRiskWeightSum += locusMaximumRiskWeight
|
||
|
||
userLocusRiskWeight, exists := locusRiskWeightsMap[userLocusBasePair]
|
||
if (exists == false){
|
||
// We do not know the risk weight for this base pair
|
||
// We treat this as a 0 risk weight
|
||
} else {
|
||
userRiskWeightSum += userLocusRiskWeight
|
||
}
|
||
}
|
||
|
||
if (userNumberOfLociTested == 0){
|
||
continue
|
||
}
|
||
|
||
numberOfDiseasesTested += 1
|
||
|
||
userDiseaseRiskScore, err := helpers.ScaleNumberProportionally(true, userRiskWeightSum, userMinimumPossibleRiskWeightSum, userMaximumPossibleRiskWeightSum, 0, 100)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
userRiskScoreFraction := float64(userDiseaseRiskScore)/float64(100)
|
||
|
||
allDiseasesAverageRiskScoreNumerator += userRiskScoreFraction
|
||
}
|
||
|
||
if (attributeName == "TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested"){
|
||
numberOfDiseasesTestedString := helpers.ConvertIntToString(numberOfDiseasesTested)
|
||
|
||
return true, profileVersion, numberOfDiseasesTestedString, nil
|
||
}
|
||
|
||
if (numberOfDiseasesTested == 0){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
allDiseasesAverageRiskScore := (float64(allDiseasesAverageRiskScoreNumerator)/float64(numberOfDiseasesTested)) * 100
|
||
|
||
allDiseasesAverageRiskScoreString := helpers.ConvertFloat64ToStringRounded(allDiseasesAverageRiskScore, 0)
|
||
|
||
return true, profileVersion, allDiseasesAverageRiskScoreString, nil
|
||
}
|
||
case "OffspringTotalPolygenicDiseaseRiskScore",
|
||
"OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested":{
|
||
|
||
// This attribute is used to show the offspring's total risk score for all polygenic diseases
|
||
// This enables users to choose a mate whose offspring will have the lowest polygenic disease risk
|
||
// The value is a number between 0 and 100
|
||
|
||
// TODO Users should eventually be able to filter users based on how many loci/diseases the offspring has been tested for
|
||
// For example, if a user has had their entire genome sequenced, they will be able to only show other users who have done the same
|
||
|
||
//TODO: We should also be weighting the diseases based on how bad they are.
|
||
// For example, Breast Cancer is not as bad as Epilepsy
|
||
// LifeView.com weights each disease in their polygenic disease risk score calculation
|
||
|
||
myPersonChosen, myGenomesExist, myAnalysisIsReady, myGeneticAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
|
||
|
||
// We have not linked a genome person and performed a genetic analysis
|
||
// The total monogenic disease risk is unknown
|
||
// We can still predict disease risk for individual recessive disorders when one person has no variants, but
|
||
// not the total probability for all monogenic diseases
|
||
// This is because everyone is a carrier for at least some recessive monogenic disorders
|
||
//
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
polygenicDiseaseObjectsList, err := polygenicDiseases.GetPolygenicDiseaseObjectsList()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
numberOfDiseasesTested := 0
|
||
|
||
// This variable adds up the risk score for each disease
|
||
// Each risk score is a number between 0 and 1
|
||
allDiseasesAverageRiskScoreNumerator := float64(0)
|
||
|
||
for _, diseaseObject := range polygenicDiseaseObjectsList{
|
||
|
||
diseaseName := diseaseObject.DiseaseName
|
||
diseaseLociList := diseaseObject.LociList
|
||
|
||
offspringRiskWeightSum := 0
|
||
offspringMinimumPossibleRiskWeightSum := 0
|
||
offspringMaximumPossibleRiskWeightSum := 0
|
||
offspringNumberOfLociTested := 0
|
||
|
||
for _, locusObject := range diseaseLociList{
|
||
|
||
locusIdentifierHex := locusObject.LocusIdentifier
|
||
|
||
locusIdentifier, err := encoding.DecodeHexStringTo3ByteArray(locusIdentifierHex)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
locusRSID := locusObject.LocusRSID
|
||
|
||
locusRSIDString := helpers.ConvertInt64ToString(locusRSID)
|
||
|
||
locusRiskWeightsMap := locusObject.RiskWeightsMap
|
||
locusOddsRatiosMap := locusObject.OddsRatiosMap
|
||
locusMinimumRiskWeight := locusObject.MinimumRiskWeight
|
||
locusMaximumRiskWeight := locusObject.MaximumRiskWeight
|
||
|
||
locusValueAttributeName := "LocusValue_rs" + locusRSIDString
|
||
|
||
myLocusInfoIsKnown, _, myLocusBase1, myLocusBase2, _, _, _, err := readGeneticAnalysis.GetPersonPolygenicDiseaseLocusInfoFromGeneticAnalysis(myGeneticAnalysisObject, diseaseName, locusIdentifier, myGenomeIdentifier)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myLocusInfoIsKnown == false){
|
||
continue
|
||
}
|
||
|
||
userLocusBasePairExists, _, userLocusBasePair, err := getProfileAttributesFunction(locusValueAttributeName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userLocusBasePairExists == false){
|
||
continue
|
||
}
|
||
|
||
userLocusBase1, userLocusBase2, semicolonFound := strings.Cut(userLocusBasePair, ";")
|
||
if (semicolonFound == false){
|
||
return false, 0, "", errors.New("GetAnyProfileAttributeIncludingCalculated called with profile containing invalid " + locusValueAttributeName + ": " + userLocusBasePair)
|
||
}
|
||
|
||
offspringLocusRiskWeight, _, _, _, err := createGeneticAnalysis.GetOffspringPolygenicDiseaseLocusInfo(locusRiskWeightsMap, locusOddsRatiosMap, myLocusBase1, myLocusBase2, userLocusBase1, userLocusBase2)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
offspringNumberOfLociTested += 1
|
||
|
||
offspringMinimumPossibleRiskWeightSum += locusMinimumRiskWeight
|
||
offspringMaximumPossibleRiskWeightSum += locusMaximumRiskWeight
|
||
|
||
offspringRiskWeightSum += offspringLocusRiskWeight
|
||
}
|
||
|
||
if (offspringNumberOfLociTested == 0){
|
||
continue
|
||
}
|
||
|
||
numberOfDiseasesTested += 1
|
||
|
||
offspringRiskScore, err := helpers.ScaleNumberProportionally(true, offspringRiskWeightSum, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 100)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
offspringRiskScoreFraction := float64(offspringRiskScore)/float64(100)
|
||
|
||
allDiseasesAverageRiskScoreNumerator += offspringRiskScoreFraction
|
||
}
|
||
|
||
if (attributeName == "OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested"){
|
||
numberOfDiseasesTestedString := helpers.ConvertIntToString(numberOfDiseasesTested)
|
||
|
||
return true, profileVersion, numberOfDiseasesTestedString, nil
|
||
}
|
||
|
||
if (numberOfDiseasesTested == 0){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
allDiseasesAverageRiskScore := (float64(allDiseasesAverageRiskScoreNumerator)/float64(numberOfDiseasesTested)) * 100
|
||
|
||
allDiseasesAverageRiskScoreString := helpers.ConvertFloat64ToStringRounded(allDiseasesAverageRiskScore, 0)
|
||
|
||
return true, profileVersion, allDiseasesAverageRiskScoreString, nil
|
||
}
|
||
case "SearchTermsCount":{
|
||
|
||
myDesireExists, myDesiredChoicesListString, err := myLocalDesires.GetDesire("SearchTerms")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myDesireExists == false){
|
||
// No terms are selected
|
||
return true, profileVersion, "0", nil
|
||
}
|
||
myDesiredChoicesList := strings.Split(myDesiredChoicesListString, "+")
|
||
|
||
myDesiredSearchTermsList := make([]string, 0, len(myDesiredChoicesList))
|
||
|
||
for _, searchTermBase64 := range myDesiredChoicesList{
|
||
|
||
searchTerm, err := encoding.DecodeBase64StringToUnicodeString(searchTermBase64)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("My search term desires contains invalid term: " + searchTermBase64)
|
||
}
|
||
|
||
myDesiredSearchTermsList = append(myDesiredSearchTermsList, searchTerm)
|
||
}
|
||
|
||
// We count the number of occurances within the user's profile
|
||
|
||
searchTermCount := 0
|
||
|
||
profileAttributesToCheckList := []string{"Description", "Tags", "Hobbies", "Beliefs"}
|
||
|
||
for _, attributeName := range profileAttributesToCheckList{
|
||
|
||
attributeExists, _, attributeValue, err := getProfileAttributesFunction(attributeName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (attributeExists == false){
|
||
continue
|
||
}
|
||
for _, searchTerm := range myDesiredSearchTermsList{
|
||
numberOfOccurances := strings.Count(attributeValue, searchTerm)
|
||
searchTermCount += numberOfOccurances
|
||
}
|
||
}
|
||
|
||
searchTermCountString := helpers.ConvertIntToString(searchTermCount)
|
||
|
||
return true, profileVersion, searchTermCountString, nil
|
||
}
|
||
case "HasMessagedMe":{
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
profileNetworkType, err := getUserProfileNetworkType()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
hasMessagedMe, err := myChatMessages.CheckIfUserHasMessagedMe(userIdentityHash, profileNetworkType)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
if (hasMessagedMe == false){
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
case "IHaveMessaged":{
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
profileNetworkType, err := getUserProfileNetworkType()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
iHaveMessaged, err := myChatMessages.CheckIfIHaveMessagedUser(userIdentityHash, profileNetworkType)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
if (iHaveMessaged == false){
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
case "HasRejectedMe":{
|
||
|
||
if (profileType != "Mate"){
|
||
// Only Mate users can reject other users
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
|
||
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(profileType)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myIdentityExists == false){
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
profileNetworkType, err := getUserProfileNetworkType()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
myIdentityFound, anyMessagesFound, _, _, _, _, _, theyHaveContactedMe, theyHaveRejectedMe, _, err := myChatMessages.GetMyConversationInfoAndSortedMessagesList(myIdentityHash, userIdentityHash, profileNetworkType)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myIdentityFound == false){
|
||
return false, 0, "", errors.New("My Identity not found after being found already.")
|
||
}
|
||
if (anyMessagesFound == false){
|
||
// No messages exist between us
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
if (theyHaveContactedMe == true && theyHaveRejectedMe == true){
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
case "IsLiked":{
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
isLiked, _, err := myLikedUsers.CheckIfUserIsLiked(userIdentityHash)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (isLiked == true){
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
case "IsIgnored":{
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
isIgnored, _, _, _, err := myIgnoredUsers.CheckIfUserIsIgnored(userIdentityHash)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (isIgnored == true){
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
case "IsMyContact":{
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
isMyContact, err := myContacts.CheckIfUserIsMyContact(userIdentityHash)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (isMyContact == true){
|
||
return true, profileVersion, "Yes", nil
|
||
}
|
||
return true, profileVersion, "No", nil
|
||
}
|
||
case "WealthInGold":{
|
||
|
||
wealthExists, _, userWealth, err := getProfileAttributesFunction("Wealth")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (wealthExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
currencyExists, _, userCurrency, err := getProfileAttributesFunction("WealthCurrency")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (currencyExists == false){
|
||
return false, 0, "", errors.New("Database corrupt: Contains mate profile with Wealth but missing WealthCurrency")
|
||
}
|
||
|
||
userWealthFloat64, err := helpers.ConvertStringToFloat64(userWealth)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("Database corrupt: Contains mate profile with invalid wealth: " + userWealth)
|
||
}
|
||
|
||
profileNetworkType, err := getUserProfileNetworkType()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
_, wealthInGold, err := convertCurrencies.ConvertCurrencyToKilogramsOfGold(profileNetworkType, userCurrency, userWealthFloat64)
|
||
if (err != nil){ return false, 0, "", err }
|
||
|
||
wealthInGoldString := helpers.ConvertFloat64ToString(wealthInGold)
|
||
|
||
return true, profileVersion, wealthInGoldString, nil
|
||
}
|
||
case "RacialSimilarity":{
|
||
|
||
// Calculating racial similarity requires retrieving other calculated attributes
|
||
// We have to recursively call this function
|
||
|
||
getProfileAttributesFunctionForRacialSimilarity := func(inputAttributeName string)(bool, int, string, error){
|
||
|
||
// We make sure infinite recursion will not happen accidentally
|
||
if (inputAttributeName == "RacialSimilarity"){
|
||
return false, 0, "", errors.New("Infinite recursion occurs during racial similarity attribute retrieval.")
|
||
}
|
||
|
||
exists, profileVersion, attributeValue, err := getProfileAttributesFunction(inputAttributeName)
|
||
|
||
return exists, profileVersion, attributeValue, err
|
||
}
|
||
|
||
// RacialSimilarity is a value which aims to represent how racially similar 2 users are
|
||
//
|
||
// The calculation currently only adds points when the user has shared racial information such as genes and ancestry
|
||
// If users have not shared their genes, they may still be racially similar
|
||
// We want to prevent users who have not shared genes from being ranked too low by users who are sorting by racial similarity
|
||
// We should accomplish this by deducting points if similarity is low, and adding points if similarity exists
|
||
// Fine-tuning the algorithm requires real-world testing
|
||
// We can see how well the algorithm is working by creating fake profiles with real photos of people along with their genomes
|
||
racialSimilarity := float64(0)
|
||
|
||
anyValueIsKnown := false
|
||
|
||
addSimilarityAttributeToTotal := func(similarityAttributeName string, importanceFactor int)error{
|
||
|
||
valueIsKnown, _, similarityValueString, err := GetAnyProfileAttributeIncludingCalculated(similarityAttributeName, getProfileAttributesFunctionForRacialSimilarity)
|
||
if (err != nil) { return err }
|
||
if (valueIsKnown == true){
|
||
|
||
anyValueIsKnown = true
|
||
|
||
// The input attribute value is a percentage between 0 - 100
|
||
|
||
similarityValue, err := helpers.ConvertStringToFloat64(similarityValueString)
|
||
if (err != nil){
|
||
return errors.New("GetAnyProfileAttributeIncludingCalculated returning invalid " + similarityAttributeName + ": " + similarityValueString)
|
||
}
|
||
|
||
valueToAdd := similarityValue * float64(importanceFactor)
|
||
|
||
racialSimilarity += valueToAdd
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
err = addSimilarityAttributeToTotal("EyeColorSimilarity", 4)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("EyeColorGenesSimilarity", 4)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err := addSimilarityAttributeToTotal("HairColorSimilarity", 4)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("HairColorGenesSimilarity", 4)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("SkinColorSimilarity", 4)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("SkinColorGenesSimilarity", 4)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("HairTextureSimilarity", 2)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("HairTextureGenesSimilarity", 2)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
// TODO: Facial structure similarity (Comparison between user profile photos is needed)
|
||
|
||
err = addSimilarityAttributeToTotal("FacialStructureGenesSimilarity", 3)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("23andMe_AncestralSimilarity", 5)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("23andMe_MaternalHaplogroupSimilarity", 1)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
err = addSimilarityAttributeToTotal("23andMe_PaternalHaplogroupSimilarity", 1)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
if (anyValueIsKnown == false){
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
racialSimilarityInt, err := helpers.FloorFloat64ToInt(racialSimilarity)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
racialSimilarityString := helpers.ConvertIntToString(racialSimilarityInt)
|
||
|
||
return true, profileVersion, racialSimilarityString, nil
|
||
}
|
||
case "HairColorSimilarity":{
|
||
|
||
// HairColor is a "+" delimited string consisting of "Brown", "Black", "Blonde", "Orange"
|
||
// The list must contain at least 1 color and cannot contain more than 2 colors. Repeats are not allowed.
|
||
|
||
myHairColorExists, myHairColorAttribute, err := myLocalProfiles.GetProfileData("Mate", "HairColor")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myHairColorExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userHairColorExists, _, userHairColorAttribute, err := getProfileAttributesFunction("HairColor")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userHairColorExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
if (myHairColorAttribute == userHairColorAttribute){
|
||
return true, profileVersion, "100", nil
|
||
}
|
||
|
||
myHairColorList := strings.Split(myHairColorAttribute, "+")
|
||
userHairColorList := strings.Split(userHairColorAttribute, "+")
|
||
|
||
areEqual := helpers.CheckIfTwoListsContainIdenticalItems(myHairColorList, userHairColorList)
|
||
if (areEqual == true){
|
||
return true, profileVersion, "100", nil
|
||
}
|
||
|
||
// Lists are not equal
|
||
// 1 shared color == 50% similar.
|
||
// No shared colors == 0% similar.
|
||
|
||
for _, colorName := range myHairColorList{
|
||
|
||
containsColor := slices.Contains(userHairColorList, colorName)
|
||
if (containsColor == true){
|
||
return true, profileVersion, "50", nil
|
||
}
|
||
}
|
||
|
||
return true, profileVersion, "0", nil
|
||
}
|
||
case "SkinColorSimilarity":{
|
||
|
||
// Skin color is an integer between 1-6
|
||
|
||
mySkinColorExists, mySkinColorAttribute, err := myLocalProfiles.GetProfileData("Mate", "SkinColor")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (mySkinColorExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userSkinColorExists, _, userSkinColorAttribute, err := getProfileAttributesFunction("SkinColor")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userSkinColorExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
mySkinColorInt, err := helpers.ConvertStringToInt(mySkinColorAttribute)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("myLocalProfiles contains invalid SkinColor: " + mySkinColorAttribute)
|
||
}
|
||
|
||
userSkinColorInt, err := helpers.ConvertStringToInt(userSkinColorAttribute)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("User profile contains invalid SkinColor: " + userSkinColorAttribute)
|
||
}
|
||
|
||
getSkinColorSimilarity := func()int{
|
||
|
||
if (mySkinColorInt == userSkinColorInt){
|
||
return 100
|
||
}
|
||
|
||
lesserValue := min(mySkinColorInt, userSkinColorInt)
|
||
greaterValue := max(mySkinColorInt, userSkinColorInt)
|
||
|
||
difference := greaterValue - lesserValue
|
||
|
||
if (difference == 1){
|
||
return 80
|
||
|
||
} else if (difference == 2){
|
||
return 60
|
||
|
||
} else if (difference == 3){
|
||
return 20
|
||
}
|
||
|
||
return 0
|
||
}
|
||
|
||
skinColorSimilarity := getSkinColorSimilarity()
|
||
|
||
skinColorSimilarityString := helpers.ConvertIntToString(skinColorSimilarity)
|
||
|
||
return true, profileVersion, skinColorSimilarityString, nil
|
||
}
|
||
case "EyeColorSimilarity":{
|
||
|
||
// The EyeColor attribute is a "+" delimited string consisting of: "Blue", "Green", "Hazel", Brown"
|
||
// It must contain at least 1 color, cannot contain more than 4 colors. Repeats are not allowed.
|
||
// Example: "Blue+Green", "Blue"
|
||
|
||
myEyeColorExists, myEyeColorAttribute, err := myLocalProfiles.GetProfileData("Mate", "EyeColor")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myEyeColorExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userEyeColorExists, _, userEyeColorAttribute, err := getProfileAttributesFunction("EyeColor")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userEyeColorExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
getEyeColorSimilarity := func()string{
|
||
|
||
if (myEyeColorAttribute == userEyeColorAttribute){
|
||
return "100"
|
||
}
|
||
|
||
myEyeColorAttributeList := strings.Split(myEyeColorAttribute, "+")
|
||
userEyeColorAttributeList := strings.Split(userEyeColorAttribute, "+")
|
||
|
||
listsContainIdenticalItems := helpers.CheckIfTwoListsContainIdenticalItems(myEyeColorAttributeList, userEyeColorAttributeList)
|
||
if (listsContainIdenticalItems == true){
|
||
return "100"
|
||
}
|
||
|
||
// This method could possibly be improved.
|
||
|
||
numberOfSharedColors := 0
|
||
|
||
for _, colorName := range myEyeColorAttributeList{
|
||
|
||
containsColor := slices.Contains(userEyeColorAttributeList, colorName)
|
||
if (containsColor == true){
|
||
numberOfSharedColors += 1
|
||
}
|
||
}
|
||
|
||
if (numberOfSharedColors == 0){
|
||
return "0"
|
||
}
|
||
if (numberOfSharedColors == 1){
|
||
return "50"
|
||
}
|
||
if (numberOfSharedColors == 2){
|
||
return "75"
|
||
}
|
||
// numberOfSharedColors == 3
|
||
// numberOfSharedColors cannot be 4
|
||
// If it was, then the lists would contain identical items, which we already checked for
|
||
return "90"
|
||
}
|
||
|
||
eyeColorSimilarity := getEyeColorSimilarity()
|
||
|
||
return true, profileVersion, eyeColorSimilarity, nil
|
||
}
|
||
case "HairTextureSimilarity":{
|
||
|
||
// Hair Texture is an integer between 1-6
|
||
|
||
myHairTextureExists, myHairTextureAttribute, err := myLocalProfiles.GetProfileData("Mate", "HairTexture")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myHairTextureExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userHairTextureExists, _, userHairTextureAttribute, err := getProfileAttributesFunction("HairTexture")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userHairTextureExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
myHairTextureInt, err := helpers.ConvertStringToInt(myHairTextureAttribute)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("myLocalProfiles contains invalid HairTexture: " + myHairTextureAttribute)
|
||
}
|
||
|
||
userHairTextureInt, err := helpers.ConvertStringToInt(userHairTextureAttribute)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("User profile contains invalid HairTexture: " + userHairTextureAttribute)
|
||
}
|
||
|
||
getHairTextureSimilarity := func()int{
|
||
|
||
if (myHairTextureInt == userHairTextureInt){
|
||
return 100
|
||
}
|
||
|
||
lesserValue := min(myHairTextureInt, userHairTextureInt)
|
||
greaterValue := max(myHairTextureInt, userHairTextureInt)
|
||
|
||
difference := greaterValue - lesserValue
|
||
|
||
if (difference == 1){
|
||
return 80
|
||
|
||
} else if (difference == 2){
|
||
return 70
|
||
|
||
} else if (difference == 3){
|
||
return 20
|
||
}
|
||
|
||
return 0
|
||
}
|
||
|
||
hairTextureSimilarity := getHairTextureSimilarity()
|
||
|
||
hairTextureSimilarityString := helpers.ConvertIntToString(hairTextureSimilarity)
|
||
|
||
return true, profileVersion, hairTextureSimilarityString, nil
|
||
}
|
||
case "EyeColorGenesSimilarity",
|
||
"EyeColorGenesSimilarity_NumberOfSimilarAlleles",
|
||
"HairColorGenesSimilarity",
|
||
"HairColorGenesSimilarity_NumberOfSimilarAlleles",
|
||
"SkinColorGenesSimilarity",
|
||
"SkinColorGenesSimilarity_NumberOfSimilarAlleles",
|
||
"HairTextureGenesSimilarity",
|
||
"HairTextureGenesSimilarity_NumberOfSimilarAlleles",
|
||
"FacialStructureGenesSimilarity",
|
||
"FacialStructureGenesSimilarity_NumberOfSimilarAlleles":{
|
||
|
||
// Disclaimer: I'm not sure how well this comparison will work
|
||
// TODO Compare magnitude of rsIDs, because some rsIDs are more causal than others.
|
||
// We may want to also calculate the outcomes for each user's genes, not just the raw genes.
|
||
// That calcuation would be a lot slower.
|
||
//
|
||
// We want this similarity comparison to aid users in their search for mates who look like them and with whom
|
||
// they are likely to produce offspring who look like them
|
||
|
||
myPersonChosen, myGenomesExist, myAnalysisIsReady, myGeneticAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
|
||
|
||
// We have not linked a genome person and performed a genetic analysis
|
||
// All genetic information is unknown
|
||
return false, profileVersion, "", nil
|
||
}
|
||
|
||
getTraitName := func()string{
|
||
|
||
isEyeColor := strings.HasPrefix(attributeName, "EyeColor")
|
||
if (isEyeColor == true){
|
||
|
||
return "Eye Color"
|
||
}
|
||
isHairColor := strings.HasPrefix(attributeName, "HairColor")
|
||
if (isHairColor == true){
|
||
|
||
return "Hair Color"
|
||
}
|
||
isSkinColor := strings.HasPrefix(attributeName, "SkinColor")
|
||
if (isSkinColor == true){
|
||
|
||
return "Skin Color"
|
||
}
|
||
isHairTexture := strings.HasPrefix(attributeName, "HairTexture")
|
||
if (isHairTexture == true){
|
||
|
||
return "Hair Texture"
|
||
}
|
||
|
||
// attributeName prefix == "FacialStructure"
|
||
|
||
return "Facial Structure"
|
||
}
|
||
|
||
traitName := getTraitName()
|
||
|
||
myTraitLociMap, _, _, _, _, err := readGeneticAnalysis.GetPersonTraitInfoFromGeneticAnalysis(myGeneticAnalysisObject, traitName, myGenomeIdentifier)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
traitObject, err := traits.GetTraitObject(traitName)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
traitLociList := traitObject.LociList
|
||
|
||
// This keeps track of the number of alleles for which both us and the user have a known value
|
||
numberOfKnownAlleles := 0
|
||
|
||
// This keeps track of the number of alleles for which us and the user have the same value
|
||
numberOfSimilarAlleles := 0
|
||
|
||
for _, rsID := range traitLociList{
|
||
|
||
myLocusValue, myLocusValueExists := myTraitLociMap[rsID]
|
||
if (myLocusValueExists == false){
|
||
continue
|
||
}
|
||
|
||
rsIDString := helpers.ConvertInt64ToString(rsID)
|
||
|
||
userLocusValueExists, _, userLocusValue, err := getProfileAttributesFunction("LocusValue_rs" + rsIDString)
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userLocusValueExists == false){
|
||
continue
|
||
}
|
||
|
||
numberOfKnownAlleles += 2
|
||
|
||
//TODO: Deal with locus base pair phase information
|
||
// We may want to require all loci to be phased for the comparison
|
||
// Users who want to use this feature would have to get a genetic sequence which has phase information
|
||
// This would save space on profiles because we wouldn't have to share an IsPhased bool for each locus (because all loci would be phased)
|
||
// People who are more knowledgeable about genetics should share their opinions.
|
||
|
||
myLocusValueBase1 := myLocusValue.Base1Value
|
||
myLocusValueBase2 := myLocusValue.Base2Value
|
||
|
||
userLocusValueBase1, userLocusValueBase2, semicolonExists := strings.Cut(userLocusValue, ";")
|
||
if (semicolonExists == false){
|
||
return false, 0, "", errors.New("Database contains invalid profile: Profile contains invalid LocusValue_rs" + rsIDString + " value: " + userLocusValue)
|
||
}
|
||
|
||
// We are counting how many shared alleles we have for this locus, irrespective of locus phase
|
||
//
|
||
// For example:
|
||
//
|
||
// User1: TT, User2: GT = 1 shared allele
|
||
// User1: AG, User2: GA = 2 shared alleles
|
||
// User1: CC, User2: GG = 0 shared alleles
|
||
//
|
||
// We will possibly change this strategy once we include locus phase information.
|
||
|
||
if (myLocusValueBase1 == userLocusValueBase1){
|
||
|
||
numberOfSimilarAlleles += 1
|
||
|
||
if (myLocusValueBase2 == userLocusValueBase2){
|
||
numberOfSimilarAlleles += 1
|
||
}
|
||
|
||
continue
|
||
}
|
||
if (myLocusValueBase1 == userLocusValueBase2){
|
||
|
||
numberOfSimilarAlleles += 1
|
||
|
||
if (myLocusValueBase2 == userLocusValueBase1){
|
||
numberOfSimilarAlleles += 1
|
||
}
|
||
continue
|
||
}
|
||
if (myLocusValueBase2 == userLocusValueBase1){
|
||
|
||
numberOfSimilarAlleles += 1
|
||
|
||
// We already know that myLocusValueBase1 != userLocusValueBase2
|
||
continue
|
||
}
|
||
if (myLocusValueBase2 == userLocusValueBase2){
|
||
|
||
numberOfSimilarAlleles += 1
|
||
|
||
// We already know that myLocusValueBase1 != userLocusValueBase1
|
||
continue
|
||
}
|
||
}
|
||
|
||
if (numberOfKnownAlleles < 15){
|
||
// We don't know enough loci values to be able to calculate genetic similarity
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
attributeIsNumberOfSimilarAlleles := strings.HasSuffix(attributeName, "_NumberOfSimilarAlleles")
|
||
if (attributeIsNumberOfSimilarAlleles == true){
|
||
|
||
numberOfSimilarAllelesString := helpers.ConvertIntToString(numberOfSimilarAlleles)
|
||
numberOfKnownAllelesString := helpers.ConvertIntToString(numberOfKnownAlleles)
|
||
|
||
result := numberOfSimilarAllelesString + "/" + numberOfKnownAllelesString
|
||
|
||
return true, profileVersion, result, nil
|
||
}
|
||
|
||
genesSimilarity := (float64(numberOfSimilarAlleles)/float64(numberOfKnownAlleles)) * 100
|
||
|
||
genesSimilarityInt, err := helpers.FloorFloat64ToInt(genesSimilarity)
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
if (genesSimilarityInt > 100){
|
||
return true, profileVersion, "100", nil
|
||
}
|
||
|
||
genesSimilarityString := helpers.ConvertIntToString(genesSimilarityInt)
|
||
|
||
return true, profileVersion, genesSimilarityString, nil
|
||
}
|
||
case "23andMe_AncestralSimilarity":{
|
||
|
||
// Ancestral similarity aims to compare how closely related a user's ancestors are.
|
||
|
||
// Ancestral similarity could be improved by comparing categories based on their genetic distance.
|
||
// If one pair of categories only diverged genetically 5,000 years ago, and the other diverged 30,000 years ago,
|
||
// we would count the more recently diverged group as being more similar.
|
||
// This may not always be the best measurement, because we must also take into account the genetic
|
||
// similarity between different groups.
|
||
// For example, a population which diverged a longer time ago might still be more similar due to factors
|
||
// such as gene flow and a more similar evolution.
|
||
// People who are more knowledgeable about these topics should share their thoughts.
|
||
|
||
myAncestryCompositionExists, myAncestryCompositionAttribute, err := myLocalProfiles.GetProfileData("Mate", "23andMe_AncestryComposition")
|
||
if (err != nil){ return false, 0, "", err }
|
||
if (myAncestryCompositionExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userAncestryCompositionExists, _, userAncestryCompositionAttribute, err := getProfileAttributesFunction("23andMe_AncestryComposition")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (userAncestryCompositionExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
ancestralSimilarity, err := companyAnalysis.GetAncestralSimilarity_23andMe(false, myAncestryCompositionAttribute, userAncestryCompositionAttribute)
|
||
if (err != nil){
|
||
return false, 0, "", errors.New("GetAncestralSimilarity_23andMe failed when calculating my 23andMe_AncestralSimilarity with another user: " + err.Error())
|
||
}
|
||
|
||
ancestralSimilarityString := helpers.ConvertIntToString(ancestralSimilarity)
|
||
|
||
return true, profileVersion, ancestralSimilarityString, nil
|
||
}
|
||
case "23andMe_MaternalHaplogroupSimilarity",
|
||
"23andMe_PaternalHaplogroupSimilarity":{
|
||
|
||
profileAttributeName := strings.TrimSuffix(attributeName, "Similarity")
|
||
|
||
myHaplogroupExists, myHaplogroup, err := myLocalProfiles.GetProfileData("Mate", profileAttributeName)
|
||
if (err != nil){ return false, 0, "", err }
|
||
if (myHaplogroupExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
userHaplogroupExists, _, userHaplogroup, err := getProfileAttributesFunction(profileAttributeName)
|
||
if (err != nil){ return false, 0, "", err }
|
||
if (userHaplogroupExists == false){
|
||
return false, 0, "", nil
|
||
}
|
||
|
||
//TODO: Rank haplogroups based on genetic similarity and incorporate into calculation
|
||
|
||
if (myHaplogroup == userHaplogroup){
|
||
return true, profileVersion, "100", nil
|
||
}
|
||
return true, profileVersion, "0", nil
|
||
}
|
||
case "NumberOfReviews":{
|
||
|
||
if (profileType != "Moderator"){
|
||
return false, 0, "", errors.New("GetAnyProfileAttributeIncludingCalculated called with NumberOfReviews attribute for non-Moderator profileType: " + profileType)
|
||
}
|
||
|
||
userIdentityHash, err := getUserIdentityHash()
|
||
if (err != nil) { return false, 0, "", err }
|
||
|
||
numberOfReviews := 0
|
||
|
||
anyExist, identityReviewHashesList, err := badgerDatabase.GetReviewerReviewHashesList(userIdentityHash, "Identity")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (anyExist == true){
|
||
numberOfReviews += len(identityReviewHashesList)
|
||
}
|
||
|
||
anyExist, profileReviewHashesList, err := badgerDatabase.GetReviewerReviewHashesList(userIdentityHash, "Profile")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (anyExist == true){
|
||
numberOfReviews += len(profileReviewHashesList)
|
||
}
|
||
|
||
anyExist, attributeReviewHashesList, err := badgerDatabase.GetReviewerReviewHashesList(userIdentityHash, "Attribute")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (anyExist == true){
|
||
numberOfReviews += len(attributeReviewHashesList)
|
||
}
|
||
|
||
anyExist, messageReviewHashesList, err := badgerDatabase.GetReviewerReviewHashesList(userIdentityHash, "Message")
|
||
if (err != nil) { return false, 0, "", err }
|
||
if (anyExist == true){
|
||
numberOfReviews += len(messageReviewHashesList)
|
||
}
|
||
|
||
numberOfReviewsString := helpers.ConvertIntToString(numberOfReviews)
|
||
|
||
return true, profileVersion, numberOfReviewsString, nil
|
||
}
|
||
}
|
||
|
||
return false, 0, "", errors.New("GetAnyProfileAttributeIncludingCalculated called with unknown attribute: " + attributeName)
|
||
}
|
||
|
||
|
||
|
||
|
||
|