
// 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/internal/badgerDatabase"
import "seekia/internal/convertCurrencies"
import "seekia/internal/desires/myLocalDesires"
import "seekia/internal/desires/myMateDesires"
import "seekia/internal/encoding"
import "seekia/internal/genetics/geneticReferences/monogenicDiseases"
import "seekia/internal/genetics/geneticReferences/polygenicDiseases"
import "seekia/internal/genetics/geneticReferences/traits"
import "seekia/internal/genetics/companyAnalysis"
import "seekia/internal/genetics/createCoupleGeneticAnalysis"
import "seekia/internal/genetics/createPersonGeneticAnalysis"
import "seekia/internal/genetics/locusValue"
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
//	-DietSimilarity


var calculatedAttributesList = []string{
	"IdentityHash",
	"IdentityScore",
	"MatchScore",
	"Controversy",
	"BanAdvocates",
	"Distance",
	"IsSameSex",
	"23andMe_OffspringNeanderthalVariants",
	"SearchTermsCount",
	"HasMessagedMe",
	"IHaveMessaged",
	"HasRejectedMe",
	"IsLiked",
	"IsIgnored",
	"IsMyContact",
	"WealthInGold",
	"DeepAppearanceSimilarity",
	"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",

	"OffspringProbabilityOfAnyMonogenicDisease",
	"OffspringProbabilityOfAnyMonogenicDisease_NumberOfDiseasesTested",

	// Polygenic Diseases:

	"TotalPolygenicDiseaseRiskScore",
	"TotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested",

	"OffspringTotalPolygenicDiseaseRiskScore",
	"OffspringTotalPolygenicDiseaseRiskScore_NumberOfDiseasesTested",

	"AutismRiskScore",
	"OffspringAutismRiskScore",

	"ObesityRiskScore",
	"OffspringObesityRiskScore",

	// Discrete Traits:

	"PredictedEyeColor",
	"OffspringBlueEyesProbability",
	"OffspringGreenEyesProbability",
	"OffspringHazelEyesProbability",
	"OffspringBrownEyesProbability",

	"PredictedLactoseTolerance",
	"OffspringLactoseToleranceProbability",

	"PredictedHairTexture",
	"OffspringStraightHairProbability",
	"OffspringCurlyHairProbability",

	// Numeric Traits:

	"HomosexualnessScore",
	"OffspringHomosexualnessScore",

	"PredictedHeight",
	"OffspringPredictedHeight",
}

// 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 := createCoupleGeneticAnalysis.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

				userDiseaseLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(diseaseLociList, getProfileAttributesFunction)
				if (err != nil) { return false, 0, "", err }

				neuralNetworkExists, anyLocusTested, userDiseaseRiskScore, _, _, _, err := createPersonGeneticAnalysis.GetPersonGenomePolygenicDiseaseAnalysis(diseaseObject, userDiseaseLocusValuesMap, true)
				if (err != nil) { return false, 0, "", err }
				if (neuralNetworkExists == false || anyLocusTested == false){
					continue
				}

				numberOfDiseasesTested += 1

				userRiskScoreFraction := float64(userDiseaseRiskScore)/float64(10)

				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 polygenic disease risk is unknown

				return false, profileVersion, "", nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myGeneticAnalysisObject)
			if (err != nil) { return false, 0, "", err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return false, 0, "", errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis which has GenomesMap which is missing my genome identifier.")
			}

			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

				userDiseaseLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(diseaseLociList, getProfileAttributesFunction)
				if (err != nil) { return false, 0, "", err }

				neuralNetworkExists, anyLocusValuesTested, offspringAverageRiskScore, _, _, _, _, err := createCoupleGeneticAnalysis.GetOffspringPolygenicDiseaseAnalysis(diseaseObject, myGenomeLocusValuesMap, userDiseaseLocusValuesMap)
				if (err != nil) { return false, 0, "", err }
				if (neuralNetworkExists == false || anyLocusValuesTested == false){
					continue
				}

				numberOfDiseasesTested += 1

				offspringRiskScoreFraction := float64(offspringAverageRiskScore)/float64(10)

				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 "AutismRiskScore",
			"ObesityRiskScore":{

			// These are polygenic diseases
			// We get the risk score for the user

			diseaseName := strings.TrimSuffix(attributeName, "RiskScore")

			diseaseObject, err := polygenicDiseases.GetPolygenicDiseaseObject(diseaseName)
			if (err != nil){ return false, 0, "", err }

			diseaseLociList := diseaseObject.LociList

			userDiseaseLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(diseaseLociList, getProfileAttributesFunction)
			if (err != nil) { return false, 0, "", err }

			neuralNetworkExists, anyLocusTested, userDiseaseRiskScore, _, _, _, err := createPersonGeneticAnalysis.GetPersonGenomePolygenicDiseaseAnalysis(diseaseObject, userDiseaseLocusValuesMap, true)
			if (err != nil) { return false, 0, "", err }
			if (neuralNetworkExists == false){
				return false, 0, "", errors.New("Neural network missing for disease: " + diseaseName)
			}
			if (anyLocusTested == false){
				// Disease risk is unknown
				return false, profileVersion, "", nil
			}

			riskScoreString := helpers.ConvertIntToString(userDiseaseRiskScore)

			return true, profileVersion, riskScoreString, nil
		}
		case "OffspringAutismRiskScore",
			"OffspringObesityRiskScore":{

			// These are polygenic diseases
			// We get the risk score for the offspring

			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 offspring polygenic disease risks are unknown

				return false, profileVersion, "", nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myGeneticAnalysisObject)
			if (err != nil) { return false, 0, "", err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return false, 0, "", errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis which has GenomesMap which is missing my genome identifier.")
			}


			diseaseNameWithOffspring := strings.TrimSuffix(attributeName, "RiskScore")

			diseaseName := strings.TrimPrefix(diseaseNameWithOffspring, "Offspring")

			diseaseObject, err := polygenicDiseases.GetPolygenicDiseaseObject(diseaseName)
			if (err != nil){ return false, 0, "", err }

			diseaseLociList := diseaseObject.LociList

			userDiseaseLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(diseaseLociList, getProfileAttributesFunction)
			if (err != nil) { return false, 0, "", err }

			neuralNetworkExists, anyLocusValuesTested, offspringAverageRiskScore, _, _, _, _, err := createCoupleGeneticAnalysis.GetOffspringPolygenicDiseaseAnalysis(diseaseObject, myGenomeLocusValuesMap, userDiseaseLocusValuesMap)
			if (err != nil) { return false, 0, "", err }
			if (neuralNetworkExists == false){
				return false, 0, "", errors.New("No neural network exists for disease: " + diseaseName)	
			}
			if (anyLocusValuesTested == false){
				// No disease loci are known
				return false, profileVersion, "", nil
			}

			offspringAverageRiskScoreString := helpers.ConvertIntToString(offspringAverageRiskScore)

			return true, profileVersion, offspringAverageRiskScoreString, nil
		}
		case "PredictedEyeColor",
			"PredictedLactoseTolerance",
			"PredictedHairTexture":{

			//Outputs:
			//	-string: Trait name
			getTraitName := func()(string, error){

				switch attributeName{

					case "PredictedEyeColor":{
						return "Eye Color", nil
					}
					case "PredictedLactoseTolerance":{
						return "Lactose Tolerance", nil
					}
					case "PredictedHairTexture":{
						return "Hair Texture", nil
					}
				}

				return "", errors.New("Discrete trait calculated attribute reached with unknown attributeName: " + attributeName)
			}

			traitName, err := getTraitName()
			if (err != nil) { return false, 0, "", err }

			traitObject, err := traits.GetTraitObject(traitName)
			if (err != nil) { return false, 0, "", err }

			traitLociList := traitObject.LociList

			userTraitLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(traitLociList, getProfileAttributesFunction)
			if (err != nil) { return false, 0, "", err }

			neuralNetworkExists, neuralNetworkOutcomeIsKnown, predictedOutcome, _, _, _, err := createPersonGeneticAnalysis.GetGenomeDiscreteTraitAnalysis_NeuralNetwork(traitObject, userTraitLocusValuesMap, true)
			if (err != nil) { return false, 0, "", err }
			if (neuralNetworkExists == true){
				if (neuralNetworkOutcomeIsKnown == false){
					return false, 0, "", nil
				}

				return true, profileVersion, predictedOutcome, nil
			}

			anyRulesExist, _, _, _, predictedOutcomeExists, predictedOutcome, err := createPersonGeneticAnalysis.GetGenomeDiscreteTraitAnalysis_Rules(traitObject, userTraitLocusValuesMap, true)
			if (err != nil) { return false, 0, "", err }
			if (anyRulesExist == false){
				return false, 0, "", errors.New("Discrete trait calculated attribute exists for trait without a neural network or rules: " + traitName)
			}
			if (predictedOutcomeExists == false){
				return false, 0, "", nil
			}
			
			return true, profileVersion, predictedOutcome, nil
		}
		case "OffspringBlueEyesProbability",
			"OffspringGreenEyesProbability",
			"OffspringHazelEyesProbability",
			"OffspringBrownEyesProbability",
			"OffspringLactoseToleranceProbability",
			"OffspringStraightHairProbability",
			"OffspringCurlyHairProbability":{

			//TODO: Add ability to retrieve confidence/quantity of loci known and sort/filter by those attributes

			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 offspring trait predictions are unknown

				return false, profileVersion, "", nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myGeneticAnalysisObject)
			if (err != nil) { return false, 0, "", err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return false, 0, "", errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis which has GenomesMap which is missing my genome identifier.")
			}

			// These are discrete traits
			// We get the outcome probability for the offspring

			//Outputs:
			//	-string: Trait name
			//	-string: Outcome name
			getTraitAndOutcomeName := func()(string, string, error){

				switch attributeName{

					case "OffspringBlueEyesProbability":{
						return "Eye Color", "Blue", nil
					}
					case "OffspringGreenEyesProbability":{
						return "Eye Color", "Green", nil
					}
					case "OffspringHazelEyesProbability":{
						return "Eye Color", "Hazel", nil
					}
					case "OffspringBrownEyesProbability":{
						return "Eye Color", "Brown", nil
					}
					case "OffspringLactoseToleranceProbability":{
						return "Lactose Tolerance", "Tolerant", nil
					}
					case "OffspringStraightHairProbability":{
						return "Hair Texture", "Straight", nil
					}
					case "OffspringCurlyHairProbability":{
						return "Hair Texture", "Curly", nil
					}
				}

				return "", "", errors.New("Offspring discrete trait calculated attribute reached with unknown attributeName: " + attributeName)
			}

			traitName, outcomeName, err := getTraitAndOutcomeName()
			if (err != nil) { return false, 0, "", err }

			traitObject, err := traits.GetTraitObject(traitName)
			if (err != nil) { return false, 0, "", err }

			traitLociList := traitObject.LociList

			userTraitLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(traitLociList, getProfileAttributesFunction)
			if (err != nil) { return false, 0, "", err }

			neuralNetworkExists, anyLociKnown, outcomeProbabilitiesMap, _, _, _, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_NeuralNetwork(traitObject, myGenomeLocusValuesMap, userTraitLocusValuesMap)
			if (err != nil) { return false, 0, "", err }
			if (neuralNetworkExists == true){
				if (anyLociKnown == false){

					// Trait prediction is not possible
					return false, 0, "", nil
				}

				outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName]
				if (exists == false){
					return true, profileVersion, "0", nil
				}

				outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability)

				return true, profileVersion, outcomeProbabilityString, nil
			}

			anyRulesExist, rulesAnalysisExists, _, _, _, outcomeProbabilitiesMap, err := createCoupleGeneticAnalysis.GetOffspringDiscreteTraitAnalysis_Rules(traitObject, myGenomeLocusValuesMap, userTraitLocusValuesMap)
			if (err != nil) { return false, 0, "", err }
			if (anyRulesExist == false){
				return false, 0, "", errors.New("Calculation of offspring attribute for discrete trait called with trait missing neural network or rules: " + traitName)
			}
			if (rulesAnalysisExists == false){
				// Analysis is impossible for this trait
				return false, 0, "", nil
			}

			outcomeProbability, exists := outcomeProbabilitiesMap[outcomeName]
			if (exists == false){
				return true, profileVersion, "0", nil
			}

			outcomeProbabilityString := helpers.ConvertIntToString(outcomeProbability)

			return true, profileVersion, outcomeProbabilityString, nil
		}
		case "HomosexualnessScore",
			"PredictedHeight":{

			// These are numeric traits
			// We calculate the value for the user

			//Outputs:
			//	-string: Trait name
			//	-bool: Is a score between 0-10
			//	-error
			getTraitNameAndIsAScoreBool := func()(string, bool, error){

				switch attributeName{
					case "HomosexualnessScore":{
						return "Homosexualness", true, nil
					}
					case "PredictedHeight":{
						return "Height", false, nil
					}
				}

				return "", false, errors.New("User numeric trait value calculation called with unknown attributeName: " + attributeName)
			}

			traitName, traitIsAScore, err := getTraitNameAndIsAScoreBool()
			if (err != nil) { return false, 0, "", err }

			traitObject, err := traits.GetTraitObject(traitName)
			if (err != nil) { return false, 0, "", err }		

			traitLociList := traitObject.LociList

			userTraitLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(traitLociList, getProfileAttributesFunction)
			if (err != nil) { return false, 0, "", err }

			traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, _, _, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, true)
			if (err != nil) { return false, 0, "", err }
			if (traitNeuralNetworkExists == false){
				return false, 0, "", errors.New("Numeric trait attribute calculation reached for trait with no neural network: " + traitName)
			}
			if (anyLocusValuesAreKnown == false){
				// Trait prediction is impossible
				return false, 0, "", nil
			}

			if (traitIsAScore == true){

				predictedScoreString := helpers.ConvertIntToString(int(predictedOutcome))	

				return true, profileVersion, predictedScoreString, nil
			}

			predictedOutcomeString := helpers.ConvertFloat64ToString(predictedOutcome)

			return true, profileVersion, predictedOutcomeString, nil
		}
		case "OffspringHomosexualnessScore",
			"OffspringPredictedHeight":{

			// These are numeric traits
			// We calculate the value for the offspring

			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 offspring trait predictions are unknown

				return false, profileVersion, "", nil
			}

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myGeneticAnalysisObject)
			if (err != nil) { return false, 0, "", err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return false, 0, "", errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis which has GenomesMap which is missing my genome identifier.")
			}

			//Outputs:
			//	-string: Trait name
			//	-bool: Is a score between 0-10
			//	-error
			getTraitNameAndIsAScoreBool := func()(string, bool, error){

				switch attributeName{
					case "OffspringHomosexualnessScore":{
						return "Homosexualness", true, nil
					}
					case "OffspringPredictedHeight":{
						return "Height", false, nil
					}
				}

				return "", false, errors.New("Offspring numeric trait value calculation called with unknown attributeName: " + attributeName)
			}

			traitName, traitIsAScore, err := getTraitNameAndIsAScoreBool()
			if (err != nil) { return false, 0, "", err }

			traitObject, err := traits.GetTraitObject(traitName)
			if (err != nil) { return false, 0, "", err }

			traitLociList := traitObject.LociList

			userTraitLocusValuesMap, err := GetUserGenomeLocusValuesMapFromProfile(traitLociList, getProfileAttributesFunction)
			if (err != nil) { return false, 0, "", err }

			neuralNetworkExists, anyLociKnown, predictedOutcome, _, _, _, _, err := createCoupleGeneticAnalysis.GetOffspringNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap)
			if (err != nil) { return false, 0, "", err }
			if (neuralNetworkExists == false){
				return false, 0, "", errors.New("Offspring Numeric trait attribute calculation reached for trait with no neural network: " + traitName)
			}
			if (anyLociKnown == false){
				// Prediction is impossible
				return false, profileVersion, "", nil
			}

			if (traitIsAScore == true){

				predictedScoreString := helpers.ConvertIntToString(int(predictedOutcome))	

				return true, profileVersion, predictedScoreString, nil
			}

			predictedOutcomeString := helpers.ConvertFloat64ToString(predictedOutcome)

			return true, profileVersion, predictedOutcomeString, 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 "DeepAppearanceSimilarity":{

			// Calculating deep appearance similarity requires retrieving other calculated attributes
			// We have to recursively call this function

			getProfileAttributesFunctionForDeepAppearanceSimilarity := func(inputAttributeName string)(bool, int, string, error){

				// We make sure infinite recursion will not happen accidentally
				if (inputAttributeName == "DeepAppearanceSimilarity"){
					return false, 0, "", errors.New("Infinite recursion occurs during deep appearance similarity attribute retrieval.")		
				}

				exists, profileVersion, attributeValue, err := getProfileAttributesFunction(inputAttributeName)

				return exists, profileVersion, attributeValue, err
			}

			// Deep Appearance Similarity is a value which aims to represent how similar 2 users are in appearance
			//
			// If users have not shared their genes, they may still be similar in appearance
			// We want to prevent users who have not shared genes from being ranked too low by users who are sorting by appearance 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
			deepAppearanceSimilarity := float64(0)

			anyValueIsKnown := false

			addSimilarityAttributeToTotal := func(similarityAttributeName string, importanceFactor int)error{

				valueIsKnown, _, similarityValueString, err := GetAnyProfileAttributeIncludingCalculated(similarityAttributeName, getProfileAttributesFunctionForDeepAppearanceSimilarity)
				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)

					deepAppearanceSimilarity += 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 }

			if (anyValueIsKnown == false){
				return false, profileVersion, "", nil
			}

			deepAppearanceSimilarityInt, err := helpers.FloorFloat64ToInt(deepAppearanceSimilarity)
			if (err != nil) { return false, 0, "", err }

			deepAppearanceSimilarityString := helpers.ConvertIntToString(deepAppearanceSimilarityInt)

			return true, profileVersion, deepAppearanceSimilarityString, 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()

			_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myGeneticAnalysisObject)
			if (err != nil) { return false, 0, "", err }

			myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
			if (exists == false){
				return false, 0, "", errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis with a GenomesMap which is missing my genome identifier.")
			}

			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 := myGenomeLocusValuesMap[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)
}

// This function constructs a traitLocusValuesMap from a user's profile and a set of loci
//Outputs:
//	-map[int64]locusValue.LocusValue: Genome map for provided loci
//	-error
func GetUserGenomeLocusValuesMapFromProfile(lociList []int64, getProfileAttributesFunction func(string)(bool, int, string, error))(map[int64]locusValue.LocusValue, error){

	// We construct the user's locus values map
	// Map Structure: Locus rsID -> locusValue.LocusValue
	userTraitLocusValuesMap := make(map[int64]locusValue.LocusValue)

	for _, rsID := range lociList{

		rsIDString := helpers.ConvertInt64ToString(rsID)

		userLocusValueAttributeName := "LocusValue_rs" + rsIDString

		userLocusValueIsKnown, _, userLocusValue, err := getProfileAttributesFunction(userLocusValueAttributeName)
		if (err != nil) { return nil, err }
		if (userLocusValueIsKnown == false){
			continue
		}

		userLocusBase1, userLocusBase2, semicolonFound := strings.Cut(userLocusValue, ";")
		if (semicolonFound == false){
			return nil, errors.New("Database corrupt: Contains profile with invalid " + userLocusValueAttributeName + " value: " + userLocusValue)
		}

		userLocusIsPhasedAttributeName := "LocusIsPhased_rs" + rsIDString

		userLocusIsPhasedExists, _, userLocusIsPhasedString, err := getProfileAttributesFunction(userLocusIsPhasedAttributeName)
		if (err != nil) { return nil, err }
		if (userLocusIsPhasedExists == false){
			return nil, errors.New("Database corrupt: Contains profile with locusValue but not locusIsPhased status for locus: " + rsIDString)
		}

		userLocusIsPhased, err := helpers.ConvertYesOrNoStringToBool(userLocusIsPhasedString)
		if (err != nil) { return nil, err }

		userLocusValueObject := locusValue.LocusValue{
			Base1Value: userLocusBase1,
			Base2Value: userLocusBase2,
			LocusIsPhased: userLocusIsPhased,
		}

		userTraitLocusValuesMap[rsID] = userLocusValueObject
	}

	return userTraitLocusValuesMap, nil
}



