990 lines
35 KiB
Go
990 lines
35 KiB
Go
|
|
||
|
// myAnalyses provides functions to manage genome analyses for People and Couples
|
||
|
// Analyses are created using the createGeneticAnalysis package
|
||
|
|
||
|
package myAnalyses
|
||
|
|
||
|
import "seekia/internal/helpers"
|
||
|
import "seekia/internal/appMemory"
|
||
|
import "seekia/internal/appValues"
|
||
|
import "seekia/internal/localFilesystem"
|
||
|
import "seekia/internal/myDatastores/myMapList"
|
||
|
import "seekia/internal/genetics/createGeneticAnalysis"
|
||
|
import "seekia/internal/genetics/prepareRawGenomes"
|
||
|
import "seekia/internal/genetics/readGeneticAnalysis"
|
||
|
import "seekia/internal/genetics/myGenomes"
|
||
|
|
||
|
import "path/filepath"
|
||
|
import "strings"
|
||
|
import "time"
|
||
|
import "sync"
|
||
|
import "errors"
|
||
|
|
||
|
// This will be locked anytime analyses are being added/deleted to the myMapList datastores
|
||
|
var updatingMyAnalysesMutex sync.Mutex
|
||
|
|
||
|
var myPersonAnalysesMapListDatastore *myMapList.MyMapList
|
||
|
var myCoupleAnalysesMapListDatastore *myMapList.MyMapList
|
||
|
|
||
|
// This function must be called whenever an app user signs in
|
||
|
func InitializeMyAnalysesDatastores()error{
|
||
|
|
||
|
updatingMyAnalysesMutex.Lock()
|
||
|
defer updatingMyAnalysesMutex.Unlock()
|
||
|
|
||
|
newMyPersonAnalysesMapListDatastore, err := myMapList.CreateNewMapList("MyPersonAnalyses")
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
newMyCoupleAnalysesMapListDatastore, err := myMapList.CreateNewMapList("MyCoupleAnalyses")
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
myPersonAnalysesMapListDatastore = newMyPersonAnalysesMapListDatastore
|
||
|
myCoupleAnalysesMapListDatastore = newMyCoupleAnalysesMapListDatastore
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// This function must be called whenever an app user signs in
|
||
|
func CreateMyAnalysesFolder() error{
|
||
|
|
||
|
userDirectory, err := localFilesystem.GetAppUserFolderPath()
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
|
||
|
|
||
|
_, err = localFilesystem.CreateFolder(myAnalysesFolderPath)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// This function should be called whenever an app user signs in
|
||
|
func PruneOldAnalyses()error{
|
||
|
|
||
|
updatingMyAnalysesMutex.Lock()
|
||
|
defer updatingMyAnalysesMutex.Unlock()
|
||
|
|
||
|
//TODO: This function should delete analyses we don't need anymore
|
||
|
// These include analyses for people who dont exist anymore, and analyses for people/couples whom have newer analyses
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
|
||
|
func DeleteAllAnalysesForPerson(personIdentifier string)error{
|
||
|
|
||
|
updatingMyAnalysesMutex.Lock()
|
||
|
defer updatingMyAnalysesMutex.Unlock()
|
||
|
|
||
|
mapToDelete := map[string]string{
|
||
|
"PersonIdentifer": personIdentifier,
|
||
|
}
|
||
|
|
||
|
err := myPersonAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
mapToDelete = map[string]string{
|
||
|
"PersonAIdentifier": personIdentifier,
|
||
|
}
|
||
|
|
||
|
err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
mapToDelete = map[string]string{
|
||
|
"PersonBIdentifier": personIdentifier,
|
||
|
}
|
||
|
|
||
|
err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func DeleteAllAnalysesForCouple(inputPersonAIdentifier string, inputPersonBIdentifier string)error{
|
||
|
|
||
|
updatingMyAnalysesMutex.Lock()
|
||
|
defer updatingMyAnalysesMutex.Unlock()
|
||
|
|
||
|
personAIdentifier, personBIdentifier, err := GetPeopleIdentifiersSortedForCouple(inputPersonAIdentifier, inputPersonBIdentifier)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
mapToDelete := map[string]string{
|
||
|
"PersonAIdentifier": personAIdentifier,
|
||
|
"PersonBIdentifier": personBIdentifier,
|
||
|
}
|
||
|
|
||
|
err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// This function is used to sort the person identifiers, so that each pair of identifiers will only map to a single couple
|
||
|
// PersonA and PersonB will always follow this order within a couple analysis
|
||
|
// The sorting method has no significance
|
||
|
func GetPeopleIdentifiersSortedForCouple(personAIdentifier string, personBIdentifier string)(string, string, error){
|
||
|
|
||
|
if (personAIdentifier == personBIdentifier){
|
||
|
return "", "", errors.New("GetPeopleIdentifiersSortedForCouple called with identical person identifiers: " + personAIdentifier)
|
||
|
}
|
||
|
|
||
|
if (personAIdentifier < personBIdentifier){
|
||
|
return personAIdentifier, personBIdentifier, nil
|
||
|
}
|
||
|
return personBIdentifier, personAIdentifier, nil
|
||
|
}
|
||
|
|
||
|
|
||
|
//Outputs:
|
||
|
// -bool: Any analysis exists
|
||
|
// -string: Newest analysis identifier
|
||
|
// -int64: Time newest analysis was performed
|
||
|
// -[]string: List of genomes analyzed
|
||
|
// -bool: Newer analysis version available
|
||
|
// -This is not the same as new genomes being available. New version can be for the same genomes.
|
||
|
// -To fully determine if the analysis is up to date, we must check if new (or less) genomes are available to analyse for the person
|
||
|
// -error
|
||
|
func GetPersonNewestGeneticAnalysisInfo(personIdentifier string)(bool, string, int64, []string, bool, error){
|
||
|
|
||
|
lookupMap := map[string]string{
|
||
|
"PersonIdentifier": personIdentifier,
|
||
|
}
|
||
|
|
||
|
anyItemsFound, analysisItemsMapList, err := myPersonAnalysesMapListDatastore.GetMapListItems(lookupMap)
|
||
|
if (err != nil) { return false, "", 0, nil, false, err }
|
||
|
if (anyItemsFound == false){
|
||
|
return false, "", 0, nil, false, nil
|
||
|
}
|
||
|
|
||
|
// Below is the newest analysis version, which would update if the user had updated their Seekia client
|
||
|
appAnalysisVersion := appValues.GetGeneticAnalysisVersion()
|
||
|
|
||
|
newestAnalysisFound := false
|
||
|
newestAnalysisCreatedTime := int64(0)
|
||
|
newestAnalysisIdentifier := ""
|
||
|
newestAnalysisAnalyzedGenomesList := []string{}
|
||
|
newerAnalysisVersionAvailable := false
|
||
|
|
||
|
for _, analysisMap := range analysisItemsMapList{
|
||
|
|
||
|
currentPersonIdentifier, exists := analysisMap["PersonIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonIdentifier")
|
||
|
}
|
||
|
|
||
|
if (personIdentifier != currentPersonIdentifier){
|
||
|
return false, "", 0, nil, false, errors.New("GetMapListItems returning map with different personIdentifier.")
|
||
|
}
|
||
|
|
||
|
timeCreated, exists := analysisMap["TimeCreated"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing TimeCreated")
|
||
|
}
|
||
|
|
||
|
timeCreatedInt64, err := helpers.ConvertStringToInt64(timeCreated)
|
||
|
if (err != nil) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid timeCreated: " + timeCreated)
|
||
|
}
|
||
|
|
||
|
if (newestAnalysisFound == true && newestAnalysisCreatedTime > timeCreatedInt64){
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
newestAnalysisFound = true
|
||
|
|
||
|
analysisIdentifier, exists := analysisMap["AnalysisIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisIdentifier")
|
||
|
}
|
||
|
|
||
|
analyzedGenomes, exists := analysisMap["AnalyzedGenomes"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalyzedGenomes")
|
||
|
}
|
||
|
|
||
|
analyzedGenomesList := strings.Split(analyzedGenomes, "+")
|
||
|
|
||
|
analysisVersion, exists := analysisMap["AnalysisVersion"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisVersion")
|
||
|
}
|
||
|
|
||
|
analysisVersionInt, err := helpers.ConvertStringToInt(analysisVersion)
|
||
|
if (err != nil) {
|
||
|
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalysisVersion: " + analysisVersion)
|
||
|
}
|
||
|
|
||
|
if (appAnalysisVersion > analysisVersionInt){
|
||
|
// This analysis is not of the newest analysis version
|
||
|
// The user should run a new analysis on the same genomes to get the newest, most up to date and informative results
|
||
|
newerAnalysisVersionAvailable = true
|
||
|
} else {
|
||
|
newerAnalysisVersionAvailable = false
|
||
|
}
|
||
|
|
||
|
newestAnalysisCreatedTime = timeCreatedInt64
|
||
|
newestAnalysisIdentifier = analysisIdentifier
|
||
|
newestAnalysisAnalyzedGenomesList = analyzedGenomesList
|
||
|
}
|
||
|
|
||
|
if (newestAnalysisFound == false){
|
||
|
return false, "", 0, nil, false, errors.New("GetMapListItems not returning any items when anyItemsFound == true.")
|
||
|
}
|
||
|
|
||
|
return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisAnalyzedGenomesList, newerAnalysisVersionAvailable, nil
|
||
|
}
|
||
|
|
||
|
//Outputs:
|
||
|
// -bool: Analysis exists
|
||
|
// -string: Newest analysis identifier
|
||
|
// -int64: Time newest analysis was performed
|
||
|
// -[]string: Person A list of genomes analyzed
|
||
|
// -[]string: Person B list of genomes analyzed
|
||
|
// -bool: Newer analysis version available
|
||
|
// -This is not the same as new genomes being available. New version can be for the same genomes.
|
||
|
// -To fully determine if the analysis is up to date, we must check if new (or less) genomes are available to analyse for the person
|
||
|
// -error
|
||
|
func GetCoupleNewestGeneticAnalysisInfo(inputPersonAIdentifier string, inputPersonBIdentifier string)(bool, string, int64, []string, []string, bool, error){
|
||
|
|
||
|
personAIdentifier, personBIdentifier, err := GetPeopleIdentifiersSortedForCouple(inputPersonAIdentifier, inputPersonBIdentifier)
|
||
|
if (err != nil) { return false, "", 0, nil, nil, false, err }
|
||
|
|
||
|
lookupMap := map[string]string{
|
||
|
"PersonAIdentifier": personAIdentifier,
|
||
|
"PersonBIdentifier": personBIdentifier,
|
||
|
}
|
||
|
|
||
|
anyItemsFound, analysisItemsMapList, err := myCoupleAnalysesMapListDatastore.GetMapListItems(lookupMap)
|
||
|
if (err != nil) { return false, "", 0, nil, nil, false, err }
|
||
|
if (anyItemsFound == false){
|
||
|
return false, "", 0, nil, nil, false, nil
|
||
|
}
|
||
|
|
||
|
// Below is the newest analysis version, which would update if the user had updated their Seekia client
|
||
|
appAnalysisVersion := appValues.GetGeneticAnalysisVersion()
|
||
|
|
||
|
newestAnalysisFound := false
|
||
|
newestAnalysisCreatedTime := int64(0)
|
||
|
newestAnalysisIdentifier := ""
|
||
|
newestAnalysisPersonAAnalyzedGenomesList := []string{}
|
||
|
newestAnalysisPersonBAnalyzedGenomesList := []string{}
|
||
|
newerAnalysisVersionAvailable := false
|
||
|
|
||
|
for _, analysisMap := range analysisItemsMapList{
|
||
|
|
||
|
currentPersonAIdentifier, exists := analysisMap["PersonAIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonAIdentifier")
|
||
|
}
|
||
|
|
||
|
if (personAIdentifier != currentPersonAIdentifier){
|
||
|
return false, "", 0, nil, nil, false, errors.New("GetMapListItems returning map with different personAIdentifier.")
|
||
|
}
|
||
|
|
||
|
currentPersonBIdentifier, exists := analysisMap["PersonBIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonBIdentifier")
|
||
|
}
|
||
|
|
||
|
if (personBIdentifier != currentPersonBIdentifier){
|
||
|
return false, "", 0, nil, nil, false, errors.New("GetMapListItems returning map with different personBIdentifier.")
|
||
|
}
|
||
|
|
||
|
timeCreated, exists := analysisMap["TimeCreated"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing TimeCreated")
|
||
|
}
|
||
|
|
||
|
timeCreatedInt64, err := helpers.ConvertStringToInt64(timeCreated)
|
||
|
if (err != nil) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid timeCreated: " + timeCreated)
|
||
|
}
|
||
|
|
||
|
if (newestAnalysisFound == true && newestAnalysisCreatedTime > timeCreatedInt64){
|
||
|
continue
|
||
|
}
|
||
|
newestAnalysisFound = true
|
||
|
|
||
|
analysisIdentifier, exists := analysisMap["AnalysisIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisIdentifier")
|
||
|
}
|
||
|
|
||
|
personAAnalyzedGenomes, exists := analysisMap["PersonAAnalyzedGenomes"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonAAnalyzedGenomes")
|
||
|
}
|
||
|
|
||
|
personAAnalyzedGenomesList := strings.Split(personAAnalyzedGenomes, "+")
|
||
|
|
||
|
personBAnalyzedGenomes, exists := analysisMap["PersonBAnalyzedGenomes"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonBAnalyzedGenomes")
|
||
|
}
|
||
|
|
||
|
personBAnalyzedGenomesList := strings.Split(personBAnalyzedGenomes, "+")
|
||
|
|
||
|
analysisVersion, exists := analysisMap["AnalysisVersion"]
|
||
|
if (exists == false) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisVersion")
|
||
|
}
|
||
|
|
||
|
analysisVersionInt, err := helpers.ConvertStringToInt(analysisVersion)
|
||
|
if (err != nil) {
|
||
|
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalysisVersion: " + analysisVersion)
|
||
|
}
|
||
|
|
||
|
if (appAnalysisVersion > analysisVersionInt){
|
||
|
// This analysis is not of the newest analysis version
|
||
|
// The user should run a new analysis on the same genomes to get the newest, most up to date and informative results
|
||
|
newerAnalysisVersionAvailable = true
|
||
|
} else {
|
||
|
newerAnalysisVersionAvailable = false
|
||
|
}
|
||
|
|
||
|
newestAnalysisCreatedTime = timeCreatedInt64
|
||
|
newestAnalysisIdentifier = analysisIdentifier
|
||
|
newestAnalysisPersonAAnalyzedGenomesList = personAAnalyzedGenomesList
|
||
|
newestAnalysisPersonBAnalyzedGenomesList = personBAnalyzedGenomesList
|
||
|
}
|
||
|
|
||
|
if (newestAnalysisFound == false){
|
||
|
return false, "", 0, nil, nil, false, errors.New("GetMapListItems not returning any items when anyItemsFound == true.")
|
||
|
}
|
||
|
|
||
|
// We have to return the personA/PersonB analyzed genomes list in the same order that they came in
|
||
|
|
||
|
if (inputPersonAIdentifier == personAIdentifier){
|
||
|
// No swapping happened.
|
||
|
return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisPersonAAnalyzedGenomesList, newestAnalysisPersonBAnalyzedGenomesList, newerAnalysisVersionAvailable, nil
|
||
|
}
|
||
|
|
||
|
// We swap the personA/PersonB analyzed genomes lists
|
||
|
|
||
|
return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisPersonBAnalyzedGenomesList, newestAnalysisPersonAAnalyzedGenomesList, newerAnalysisVersionAvailable, nil
|
||
|
}
|
||
|
|
||
|
|
||
|
// This function should only be used to retrieve an existing analysis
|
||
|
// It can be used for person and couple genetic analyses
|
||
|
//Outputs:
|
||
|
// -bool: Analysis found
|
||
|
// -[]map[string]string: Analysis map list
|
||
|
// -error
|
||
|
func GetGeneticAnalysis(analysisIdentifier string)(bool, []map[string]string, error){
|
||
|
|
||
|
userDirectory, err := localFilesystem.GetAppUserFolderPath()
|
||
|
if (err != nil) { return false, nil, err }
|
||
|
|
||
|
analysisFileName := analysisIdentifier + ".json"
|
||
|
|
||
|
analysisFilePath := filepath.Join(userDirectory, "MyAnalyses", analysisFileName)
|
||
|
|
||
|
fileExists, fileBytes, err := localFilesystem.GetFileContents(analysisFilePath)
|
||
|
if (err != nil) { return false, nil, err }
|
||
|
if (fileExists == false){
|
||
|
return false, nil, nil
|
||
|
}
|
||
|
|
||
|
fileString := string(fileBytes)
|
||
|
|
||
|
analysisMapList, err := readGeneticAnalysis.ReadGeneticAnalysisString(fileString)
|
||
|
if (err != nil) { return false, nil, err }
|
||
|
|
||
|
return true, analysisMapList, nil
|
||
|
}
|
||
|
|
||
|
// This map keeps track of current person analyses being generated
|
||
|
// Map structure: Person identifier -> Process identifier
|
||
|
var personAnalysisProcessesMap map[string]string = make(map[string]string)
|
||
|
|
||
|
var personAnalysisProcessesMapMutex sync.RWMutex
|
||
|
|
||
|
// This map keeps track of current couple analyses being generated
|
||
|
// Map structure: PersonAIdentifier + "+" + PersonBIdentifier -> Process identifier
|
||
|
var coupleAnalysisProcessesMap map[string]string = make(map[string]string)
|
||
|
|
||
|
var coupleAnalysisProcessesMapMutex sync.RWMutex
|
||
|
|
||
|
|
||
|
// Returns the process identifier for the current running genetic analyses for the person
|
||
|
// Outputs:
|
||
|
// -bool: Any process found (The process may be complete)
|
||
|
// -string: Process identifier
|
||
|
// -error
|
||
|
func GetPersonGeneticAnalysisProcessIdentifier(personIdentifier string)(bool, string, error){
|
||
|
|
||
|
personAnalysisProcessesMapMutex.RLock()
|
||
|
processIdentifier, exists := personAnalysisProcessesMap[personIdentifier]
|
||
|
personAnalysisProcessesMapMutex.RUnlock()
|
||
|
if (exists == false){
|
||
|
return false, "", nil
|
||
|
}
|
||
|
|
||
|
return true, processIdentifier, nil
|
||
|
}
|
||
|
|
||
|
// Returns the process identifier for the current running genetic analyses for the couple
|
||
|
// Outputs:
|
||
|
// -bool: Any process found (The process may be complete)
|
||
|
// -string: Process identifier
|
||
|
// -error
|
||
|
func GetCoupleGeneticAnalysisProcessIdentifier(inputPersonAIdentifier string, inputPersonBIdentifier string)(bool, string, error){
|
||
|
|
||
|
personAIdentifier, personBIdentifier, err := GetPeopleIdentifiersSortedForCouple(inputPersonAIdentifier, inputPersonBIdentifier)
|
||
|
if (err != nil) { return false, "", err }
|
||
|
|
||
|
coupleIdentifier := personAIdentifier + "+" + personBIdentifier
|
||
|
|
||
|
coupleAnalysisProcessesMapMutex.RLock()
|
||
|
processIdentifier, exists := coupleAnalysisProcessesMap[coupleIdentifier]
|
||
|
coupleAnalysisProcessesMapMutex.RUnlock()
|
||
|
if (exists == false){
|
||
|
return false, "", nil
|
||
|
}
|
||
|
|
||
|
return true, processIdentifier, nil
|
||
|
}
|
||
|
|
||
|
//Outputs:
|
||
|
// -bool: Process found
|
||
|
// -bool: Process is complete
|
||
|
// -bool: Process encountered error
|
||
|
// -error: Error encountered by process
|
||
|
// -int: Process percentage complete
|
||
|
// -error
|
||
|
func GetAnalysisProcessInfo(processIdentifier string)(bool, bool, bool, error, int, error){
|
||
|
|
||
|
processExists, processPercentageComplete := appMemory.GetMemoryEntry(processIdentifier + "_ProgressPercentageComplete")
|
||
|
if (processExists == false){
|
||
|
// This should not happen...
|
||
|
return false, false, false, nil, 0, nil
|
||
|
}
|
||
|
|
||
|
exists, errorEncountered := appMemory.GetMemoryEntry(processIdentifier + "_ErrorEncountered")
|
||
|
if (exists == true){
|
||
|
|
||
|
errorEncounteredError := errors.New(errorEncountered)
|
||
|
|
||
|
return true, true, true, errorEncounteredError, 100, nil
|
||
|
}
|
||
|
|
||
|
if (processPercentageComplete == "100"){
|
||
|
return true, true, false, nil, 100, nil
|
||
|
}
|
||
|
|
||
|
processPercentageCompleteInt, err := helpers.ConvertStringToInt(processPercentageComplete)
|
||
|
if (err != nil) {
|
||
|
return false, false, false, nil, 0, errors.New("Invalid processPercentageComplete for genetic analysis process: " + processPercentageComplete)
|
||
|
}
|
||
|
if (processPercentageCompleteInt < 0 || processPercentageCompleteInt > 100){
|
||
|
return false, false, false, nil, 0, errors.New("Invalid processPercentageComplete for genetic analysis process: " + processPercentageComplete)
|
||
|
}
|
||
|
|
||
|
return true, false, false, nil, processPercentageCompleteInt, nil
|
||
|
}
|
||
|
|
||
|
|
||
|
func StopProcess(processIdentifier string){
|
||
|
|
||
|
appMemory.SetMemoryEntry(processIdentifier + "_StopProcess", "Yes")
|
||
|
appMemory.SetMemoryEntry(processIdentifier + "_ProgressPercentageComplete", "100")
|
||
|
}
|
||
|
|
||
|
// This funciton will stop any currently running processes for this person and start generating a new genetic analysis
|
||
|
//Outputs:
|
||
|
// -string: New process identifier
|
||
|
// -error
|
||
|
func StartCreateNewPersonGeneticAnalysis(personIdentifier string)(string, error){
|
||
|
|
||
|
personAnalysisProcessesMapMutex.Lock()
|
||
|
existingProcessIdentifier, exists := personAnalysisProcessesMap[personIdentifier]
|
||
|
if (exists == true){
|
||
|
progressExists, processPercentageComplete := appMemory.GetMemoryEntry(existingProcessIdentifier + "_ProgressPercentageComplete")
|
||
|
if (progressExists == false){
|
||
|
personAnalysisProcessesMapMutex.Unlock()
|
||
|
return "", errors.New("personAnalysisProcessesMap process percentageComplete value not found.")
|
||
|
}
|
||
|
if (processPercentageComplete != "100"){
|
||
|
// There is another process running.
|
||
|
// We will stop the current process for this person
|
||
|
StopProcess(existingProcessIdentifier)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
newProcessIdentifier, err := helpers.GetNewRandomHexString(18)
|
||
|
if (err != nil) { return "", err }
|
||
|
|
||
|
personAnalysisProcessesMap[personIdentifier] = newProcessIdentifier
|
||
|
personAnalysisProcessesMapMutex.Unlock()
|
||
|
|
||
|
updatePercentageCompleteFunction := func(newPercentage int)error{
|
||
|
|
||
|
if (newPercentage < 0 || newPercentage > 100){
|
||
|
return errors.New("Invalid person analysis generation progress percentage.")
|
||
|
}
|
||
|
|
||
|
newPercentageString := helpers.ConvertIntToString(newPercentage)
|
||
|
|
||
|
appMemory.SetMemoryEntry(newProcessIdentifier + "_ProgressPercentageComplete", newPercentageString)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
err = updatePercentageCompleteFunction(0)
|
||
|
if (err != nil) { return "", err }
|
||
|
|
||
|
createNewPersonGeneticAnalysis := func()error{
|
||
|
|
||
|
checkIfProcessIsStopped := func()bool{
|
||
|
|
||
|
exists, _ := appMemory.GetMemoryEntry(newProcessIdentifier + "_StopProcess")
|
||
|
if (exists == true){
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
personGenomesMapList, err := myGenomes.GetAllPersonGenomesMapList(personIdentifier)
|
||
|
if (err != nil){ return err }
|
||
|
|
||
|
if (len(personGenomesMapList) == 0){
|
||
|
return errors.New("Cannot create person genetic analysis: No genomes found for Person.")
|
||
|
}
|
||
|
|
||
|
genomeIdentifiersList := make([]string, 0, len(personGenomesMapList))
|
||
|
|
||
|
genomesList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0, len(personGenomesMapList))
|
||
|
|
||
|
finalIndex := len(personGenomesMapList) - 1
|
||
|
|
||
|
for index, genomeMap := range personGenomesMapList{
|
||
|
|
||
|
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, index, 0, finalIndex, 0, 10)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
err = updatePercentageCompleteFunction(newPercentageProgress)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
currentPersonIdentifier, exists := genomeMap["PersonIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return errors.New("PersonGenomesMapList malformed: Item missing PersonIdentifier")
|
||
|
}
|
||
|
|
||
|
if (personIdentifier != currentPersonIdentifier){
|
||
|
return errors.New("GetAllPersonGenomesMapList returning genome for different person.")
|
||
|
}
|
||
|
|
||
|
genomeIdentifier, exists := genomeMap["GenomeIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return errors.New("PersonGenomesMapList malformed: Item missing GenomeIdentifier")
|
||
|
}
|
||
|
|
||
|
genomeRawDataString, err := myGenomes.GetGenomeRawDataString(genomeIdentifier)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
rawGenomeIsValid, rawGenomeWithMetadata, err := prepareRawGenomes.CreateRawGenomeWithMetadataObject(genomeIdentifier, genomeRawDataString)
|
||
|
if (err != nil) { return err }
|
||
|
if (rawGenomeIsValid == false){
|
||
|
return errors.New("myGenomes contains invalid rawGenomeDataString: " + genomeIdentifier)
|
||
|
}
|
||
|
|
||
|
genomesList = append(genomesList, rawGenomeWithMetadata)
|
||
|
genomeIdentifiersList = append(genomeIdentifiersList, genomeIdentifier)
|
||
|
}
|
||
|
|
||
|
analysisUpdatePercentageCompleteFunction := func(inputProgress int)error{
|
||
|
|
||
|
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, inputProgress, 0, 100, 10, 10)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
err = updatePercentageCompleteFunction(newPercentageProgress)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
processCompleted, newGeneticAnalysisString, err := createGeneticAnalysis.CreatePersonGeneticAnalysis(genomesList, analysisUpdatePercentageCompleteFunction, checkIfProcessIsStopped)
|
||
|
if (err != nil) { return err }
|
||
|
if (processCompleted == false){
|
||
|
// User stopped the analysis mid-way
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
analyzedGenomesListString := strings.Join(genomeIdentifiersList, "+")
|
||
|
|
||
|
analysisIdentifier, err := helpers.GetNewRandomHexString(17)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
currentTime := time.Now().Unix()
|
||
|
currentTimeString := helpers.ConvertInt64ToString(currentTime)
|
||
|
|
||
|
currentAnalysisVersion := appValues.GetGeneticAnalysisVersion()
|
||
|
currentAnalysisVersionString := helpers.ConvertIntToString(currentAnalysisVersion)
|
||
|
|
||
|
newAnalysisMap := map[string]string{
|
||
|
"PersonIdentifier": personIdentifier,
|
||
|
"AnalysisIdentifier": analysisIdentifier,
|
||
|
"TimeCreated": currentTimeString,
|
||
|
"AnalyzedGenomes": analyzedGenomesListString,
|
||
|
"AnalysisVersion": currentAnalysisVersionString,
|
||
|
}
|
||
|
|
||
|
updatingMyAnalysesMutex.Lock()
|
||
|
defer updatingMyAnalysesMutex.Unlock()
|
||
|
|
||
|
err = myPersonAnalysesMapListDatastore.AddMapListItem(newAnalysisMap)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
userDirectory, err := localFilesystem.GetAppUserFolderPath()
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
|
||
|
|
||
|
analysisFileName := analysisIdentifier + ".json"
|
||
|
|
||
|
err = localFilesystem.CreateOrOverwriteFile([]byte(newGeneticAnalysisString), myAnalysesFolderPath, analysisFileName)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
err = updatePercentageCompleteFunction(100)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
personAnalysisProcessesMapMutex.Lock()
|
||
|
delete(personAnalysisProcessesMap, personIdentifier)
|
||
|
personAnalysisProcessesMapMutex.Unlock()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
runAnalysis := func(){
|
||
|
|
||
|
err := createNewPersonGeneticAnalysis()
|
||
|
if (err != nil){
|
||
|
appMemory.SetMemoryEntry(newProcessIdentifier + "_ProgressPercentageComplete", "100")
|
||
|
appMemory.SetMemoryEntry(newProcessIdentifier + "_ErrorEncountered", err.Error())
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
go runAnalysis()
|
||
|
|
||
|
return newProcessIdentifier, nil
|
||
|
}
|
||
|
|
||
|
|
||
|
// This will stop any currently running processes for this couple and start generating a new genetic analysis
|
||
|
//Outputs:
|
||
|
// -string: New process identifier
|
||
|
// -error
|
||
|
func StartCreateNewCoupleGeneticAnalysis(inputPersonAIdentifier string, inputPersonBIdentifier string)(string, error){
|
||
|
|
||
|
personAIdentifier, personBIdentifier, err := GetPeopleIdentifiersSortedForCouple(inputPersonAIdentifier, inputPersonBIdentifier)
|
||
|
if (err != nil) { return "", err }
|
||
|
|
||
|
coupleIdentifier := personAIdentifier + "+" + personBIdentifier
|
||
|
|
||
|
coupleAnalysisProcessesMapMutex.Lock()
|
||
|
existingProcessIdentifier, exists := coupleAnalysisProcessesMap[coupleIdentifier]
|
||
|
if (exists == true){
|
||
|
progressExists, processPercentageComplete := appMemory.GetMemoryEntry(existingProcessIdentifier + "_ProgressPercentageComplete")
|
||
|
if (progressExists == false){
|
||
|
coupleAnalysisProcessesMapMutex.Unlock()
|
||
|
return "", errors.New("coupleAnalysisProcessesMap process percentageComplete value not found.")
|
||
|
}
|
||
|
if (processPercentageComplete != "100"){
|
||
|
// There is another process running.
|
||
|
// We will stop the current process for this person
|
||
|
StopProcess(existingProcessIdentifier)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
newCoupleProcessIdentifier, err := helpers.GetNewRandomHexString(18)
|
||
|
if (err != nil) { return "", err }
|
||
|
|
||
|
coupleAnalysisProcessesMap[coupleIdentifier] = newCoupleProcessIdentifier
|
||
|
coupleAnalysisProcessesMapMutex.Unlock()
|
||
|
|
||
|
updatePercentageCompleteFunction := func(newPercentage int)error{
|
||
|
|
||
|
if (newPercentage < 0 || newPercentage > 100){
|
||
|
return errors.New("Invalid couple analysis generation progress percentage.")
|
||
|
}
|
||
|
|
||
|
newPercentageString := helpers.ConvertIntToString(newPercentage)
|
||
|
|
||
|
appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ProgressPercentageComplete", newPercentageString)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
err = updatePercentageCompleteFunction(0)
|
||
|
if (err != nil) { return "", err }
|
||
|
|
||
|
createNewCoupleGeneticAnalysis := func()error{
|
||
|
|
||
|
checkIfProcessIsStopped := func()bool{
|
||
|
|
||
|
exists, _ := appMemory.GetMemoryEntry(newCoupleProcessIdentifier + "_StopProcess")
|
||
|
if (exists == true){
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// We need both personA and personB to have an analysis before performing the couple analysis
|
||
|
// We will see if an analysis is already running for each person
|
||
|
// We will either start a new analysis for each person or monitor the existing one until it is done
|
||
|
|
||
|
personIdentifiersList := []string{personAIdentifier, personBIdentifier}
|
||
|
|
||
|
for index, personIdentifier := range personIdentifiersList{
|
||
|
|
||
|
// This function will return the percentage progress that this person's analysis will use of the entire couple analysis
|
||
|
getPercentageRangeForPersonAnalysis := func()(int, int){
|
||
|
if (index == 0){
|
||
|
return 0, 33
|
||
|
}
|
||
|
return 33, 66
|
||
|
}
|
||
|
|
||
|
personPercentageRangeStart, personPercentageRangeEnd := getPercentageRangeForPersonAnalysis()
|
||
|
|
||
|
// Outputs:
|
||
|
// -bool: Person needs update
|
||
|
// -string: Person process identifier
|
||
|
// -error
|
||
|
getPersonProcessIdentifier := func()(bool, string, error){
|
||
|
|
||
|
runningProcessFound, runningProcessIdentifier, err := GetPersonGeneticAnalysisProcessIdentifier(personIdentifier)
|
||
|
if (err != nil) { return false, "", err }
|
||
|
if (runningProcessFound == true){
|
||
|
// This person has a running analysis. We will show the progress in our current process
|
||
|
return true, runningProcessIdentifier, nil
|
||
|
}
|
||
|
|
||
|
// We check if the person needs a new analysis.
|
||
|
getPersonNewAnalysisNeededBool := func()(bool, error){
|
||
|
|
||
|
anyPersonAnalysisFound, _, _, newestPersonAnalysisListOfGenomesAnalyzed, newerAnalysisVersionAvailable, err := GetPersonNewestGeneticAnalysisInfo(personIdentifier)
|
||
|
if (err != nil) { return false, err }
|
||
|
if (anyPersonAnalysisFound == false){
|
||
|
// No analysis exists. We need to create a new analysis
|
||
|
return true, nil
|
||
|
}
|
||
|
if (newerAnalysisVersionAvailable == true){
|
||
|
// New analysis version is available.
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
allPersonRawGenomeIdentifiersList, err := myGenomes.GetAllPersonRawGenomeIdentifiersList(personAIdentifier)
|
||
|
if (err != nil) { return false, err }
|
||
|
|
||
|
genomesAreIdentical := helpers.CheckIfTwoListsContainIdenticalItems(allPersonRawGenomeIdentifiersList, newestPersonAnalysisListOfGenomesAnalyzed)
|
||
|
if (genomesAreIdentical == false){
|
||
|
// New genome exists/a genome was deleted. A new analysis is needed
|
||
|
return true, nil
|
||
|
}
|
||
|
// Analysis is up to date. Nothing to do.
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
personNewAnalysisIsNeeded, err := getPersonNewAnalysisNeededBool()
|
||
|
if (err != nil) { return false, "", err }
|
||
|
if (personNewAnalysisIsNeeded == false){
|
||
|
return false, "", nil
|
||
|
}
|
||
|
|
||
|
// We start a new analysis
|
||
|
|
||
|
newProcessIdentifier, err := StartCreateNewPersonGeneticAnalysis(personIdentifier)
|
||
|
if (err != nil) { return false, "", err }
|
||
|
|
||
|
return true, newProcessIdentifier, nil
|
||
|
}
|
||
|
|
||
|
personNeedsUpdate, personProcessIdentifier, err := getPersonProcessIdentifier()
|
||
|
if (err != nil) { return err }
|
||
|
if (personNeedsUpdate == false){
|
||
|
// No analysis needed
|
||
|
err := updatePercentageCompleteFunction(personPercentageRangeEnd)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
for{
|
||
|
|
||
|
processFound, processIsComplete, processEncounteredError, errorEncounteredByProcess, processPercentageComplete, err := GetAnalysisProcessInfo(personProcessIdentifier)
|
||
|
if (err != nil){ return err }
|
||
|
if (processFound == false){
|
||
|
return errors.New("Person process not found after being found already.")
|
||
|
}
|
||
|
if (processIsComplete == true){
|
||
|
if (processEncounteredError == true){
|
||
|
return errorEncounteredByProcess
|
||
|
}
|
||
|
err := updatePercentageCompleteFunction(personPercentageRangeEnd)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
break
|
||
|
}
|
||
|
|
||
|
personPercentageComplete, err := helpers.ScaleNumberProportionally(true, processPercentageComplete, 0, 100, personPercentageRangeStart, personPercentageRangeEnd)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
err = updatePercentageCompleteFunction(personPercentageComplete)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
time.Sleep(100 * time.Millisecond)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Both people's analyses are complete.
|
||
|
// The percentage progress is at 66%
|
||
|
// Now we perform the couple analysis
|
||
|
|
||
|
//Outputs:
|
||
|
// -[]prepareRawGenomes.RawGenomeWithMetadata: Genome Identifier -> Genome raw data string
|
||
|
// -[]string: Person raw genome identifiers list
|
||
|
// -error
|
||
|
getPersonGenomesList := func(personIdentifier string)([]prepareRawGenomes.RawGenomeWithMetadata, []string, error){
|
||
|
|
||
|
personGenomesMapList, err := myGenomes.GetAllPersonGenomesMapList(personIdentifier)
|
||
|
if (err != nil){ return nil, nil, err }
|
||
|
|
||
|
if (len(personGenomesMapList) == 0){
|
||
|
return nil, nil, errors.New("Cannot create person genetic analysis: No genomes found for Person.")
|
||
|
}
|
||
|
|
||
|
genomeIdentifiersList := make([]string, 0, len(personGenomesMapList))
|
||
|
|
||
|
// Map structure: Genome identifier -> Genome raw data string
|
||
|
genomesList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0, len(personGenomesMapList))
|
||
|
|
||
|
for _, genomeMap := range personGenomesMapList{
|
||
|
|
||
|
currentPersonIdentifier, exists := genomeMap["PersonIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return nil, nil, errors.New("PersonGenomesMapList malformed: Item missing PersonIdentifier")
|
||
|
}
|
||
|
|
||
|
if (personIdentifier != currentPersonIdentifier){
|
||
|
return nil, nil, errors.New("GetAllPersonGenomesMapList returning genome for different person.")
|
||
|
}
|
||
|
|
||
|
genomeIdentifier, exists := genomeMap["GenomeIdentifier"]
|
||
|
if (exists == false) {
|
||
|
return nil, nil, errors.New("PersonGenomesMapList malformed: Item missing GenomeIdentifier")
|
||
|
}
|
||
|
|
||
|
genomeRawDataString, err := myGenomes.GetGenomeRawDataString(genomeIdentifier)
|
||
|
if (err != nil) { return nil, nil, err }
|
||
|
|
||
|
rawGenomeIsValid, rawGenomeWithMetadata, err := prepareRawGenomes.CreateRawGenomeWithMetadataObject(genomeIdentifier, genomeRawDataString)
|
||
|
if (err != nil) { return nil, nil, err }
|
||
|
if (rawGenomeIsValid == false){
|
||
|
return nil, nil, errors.New("myGenomes contains invalid rawGenomeDataString: " + genomeIdentifier)
|
||
|
}
|
||
|
|
||
|
genomesList = append(genomesList, rawGenomeWithMetadata)
|
||
|
genomeIdentifiersList = append(genomeIdentifiersList, genomeIdentifier)
|
||
|
}
|
||
|
|
||
|
return genomesList, genomeIdentifiersList, nil
|
||
|
}
|
||
|
|
||
|
personAGenomesList, personARawGenomeIdentifiersList, err := getPersonGenomesList(personAIdentifier)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
updatePercentageCompleteFunction(70)
|
||
|
|
||
|
personBGenomesList, personBRawGenomeIdentifiersList, err := getPersonGenomesList(personBIdentifier)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
updatePercentageCompleteFunction(74)
|
||
|
|
||
|
updateCoupleAnalysisPercentageCompleteFunction := func(newPercentage int)error{
|
||
|
|
||
|
personPercentageComplete, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 74, 100)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
err = updatePercentageCompleteFunction(personPercentageComplete)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
processCompleted, newGeneticAnalysisString, err := createGeneticAnalysis.CreateCoupleGeneticAnalysis(personAGenomesList, personBGenomesList, updateCoupleAnalysisPercentageCompleteFunction, checkIfProcessIsStopped)
|
||
|
if (err != nil) { return err }
|
||
|
if (processCompleted == false){
|
||
|
// User stopped the analysis mid-way
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
personAAnalyzedGenomesListString := strings.Join(personARawGenomeIdentifiersList, "+")
|
||
|
personBAnalyzedGenomesListString := strings.Join(personBRawGenomeIdentifiersList, "+")
|
||
|
|
||
|
analysisIdentifier, err := helpers.GetNewRandomHexString(17)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
currentTime := time.Now().Unix()
|
||
|
currentTimeString := helpers.ConvertInt64ToString(currentTime)
|
||
|
|
||
|
currentAnalysisVersion := appValues.GetGeneticAnalysisVersion()
|
||
|
currentAnalysisVersionString := helpers.ConvertIntToString(currentAnalysisVersion)
|
||
|
|
||
|
newAnalysisMap := map[string]string{
|
||
|
"PersonAIdentifier": personAIdentifier,
|
||
|
"PersonBIdentifier": personBIdentifier,
|
||
|
"AnalysisIdentifier": analysisIdentifier,
|
||
|
"TimeCreated": currentTimeString,
|
||
|
"PersonAAnalyzedGenomes": personAAnalyzedGenomesListString,
|
||
|
"PersonBAnalyzedGenomes": personBAnalyzedGenomesListString,
|
||
|
"AnalysisVersion": currentAnalysisVersionString,
|
||
|
}
|
||
|
|
||
|
updatingMyAnalysesMutex.Lock()
|
||
|
defer updatingMyAnalysesMutex.Unlock()
|
||
|
|
||
|
err = myCoupleAnalysesMapListDatastore.AddMapListItem(newAnalysisMap)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
userDirectory, err := localFilesystem.GetAppUserFolderPath()
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
|
||
|
|
||
|
analysisFileName := analysisIdentifier + ".json"
|
||
|
|
||
|
err = localFilesystem.CreateOrOverwriteFile([]byte(newGeneticAnalysisString), myAnalysesFolderPath, analysisFileName)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
err = updatePercentageCompleteFunction(100)
|
||
|
if (err != nil) { return err }
|
||
|
|
||
|
coupleAnalysisProcessesMapMutex.Lock()
|
||
|
delete(coupleAnalysisProcessesMap, coupleIdentifier)
|
||
|
coupleAnalysisProcessesMapMutex.Unlock()
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
runAnalysis := func(){
|
||
|
|
||
|
err := createNewCoupleGeneticAnalysis()
|
||
|
if (err != nil){
|
||
|
appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ProgressPercentageComplete", "100")
|
||
|
appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ErrorEncountered", err.Error())
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
go runAnalysis()
|
||
|
|
||
|
return newCoupleProcessIdentifier, nil
|
||
|
}
|
||
|
|
||
|
|
||
|
|