Last active
November 21, 2019 00:03
-
-
Save Galadros/be1e19cddb85808298960b6e10705729 to your computer and use it in GitHub Desktop.
Luma Health
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// INSTRUCTIONS FOR USAGE: | |
// Enter the latitude and longitude separated by spaces after calling the executable. | |
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"math/rand" | |
"os" | |
"sort" | |
"time" | |
) | |
//var inputDatabase string = "/home/patrick/Coding_Challenges/go_code/src/lumaHealth/patientWaitlist/full-stack-interview/sample-data/patients.json" | |
var inputDatabase string = "full-stack-interview/sample-data/patients.json" | |
var clinicLocation Location | |
func main() { | |
if len(os.Args) != 3 { | |
fmt.Println("Wrong number of arguments provided.") | |
os.Exit(1) | |
} | |
inputLatitude := string(os.Args[1]) | |
inputLongitude := string(os.Args[2]) | |
rand.Seed(time.Now().UTC().UnixNano()) | |
bestPatients := getBestPatients(Location{inputLatitude, inputLongitude}) | |
fmt.Printf("\n") | |
fmt.Printf("%+v\n", bestPatients) | |
fmt.Printf("\n") | |
fmt.Printf("\n") | |
fmt.Printf("\n") | |
for _, currPatient := range bestPatients { | |
fmt.Printf("Name: %s, Score: %f, ID: %s\n", currPatient.Name, currPatient.Scores.FinalScore, currPatient.ID) | |
} | |
} | |
func getBestPatients(inputLoc Location) []Patient { | |
patientList := make([]Patient, 0) | |
clinicLocation = inputLoc | |
// Getting a sense for the data distribution | |
patientData, err := os.Open(inputDatabase) | |
if err != nil { | |
fmt.Println("Couldn't parse the database file provided.") | |
os.Exit(1) | |
} | |
patientDecoder := json.NewDecoder(patientData) | |
if decodeErr := patientDecoder.Decode(&patientList); decodeErr != nil { | |
fmt.Println(decodeErr) | |
os.Exit(1) | |
} | |
lottery := CalculateScores(patientList) | |
sort.Sort(ByFinalScore(patientList)) | |
outputPatients := make([]Patient, 10) | |
currIndex := 0 | |
// Give a random bump to patients with low activity recorded- 15% of slots are randomly split among folks with low activity. | |
for i := 0; i < 10; i ++ { | |
if rand.Float64() < 0.15 { | |
randIndex := rand.Intn(len(lottery)) | |
outputPatients[i] = lottery[randIndex] | |
lottery[randIndex], lottery[len(lottery)-1] = lottery[len(lottery)-1], lottery[randIndex] | |
lottery = lottery[:len(lottery)-1] | |
} else { | |
outputPatients[i] = patientList[currIndex] | |
currIndex += 1 | |
} | |
} | |
return outputPatients | |
} | |
// In general, this calculates a score from 1-10 based off of the patient's ranking in the database. | |
// Returns a list of low-activity patients for later random insertion. | |
func CalculateScores (database []Patient) []Patient { | |
numPatients := float64(len(database)) | |
sort.Sort(ByDistance(database)) | |
for i, _ := range database { | |
database[i].Scores.Distance = 1 + 9*float64(i)/numPatients | |
} | |
sort.Sort(ByAge(database)) | |
for i, _ := range database { | |
database[i].Scores.Age = 1 + 9*float64(i)/numPatients | |
} | |
sort.Sort(ByAccepted(database)) | |
for i, _ := range database { | |
database[i].Scores.AcceptedOffers = 1 + 9*float64(i)/numPatients | |
} | |
// This gives a low value to patients with many cancelled offers and a high one to patients with few cancelled offers. | |
sort.Sort(ByCancelled(database)) | |
for i, _ := range database { | |
database[i].Scores.CancelledOffers = 1 + 9*float64(i)/numPatients | |
} | |
sort.Sort(ByReplyTime(database)) | |
for i, _ := range database { | |
database[i].Scores.AvgReplyTime = 1 + 9*float64(i)/numPatients | |
} | |
totalOffers := 0 | |
for _, currPatient := range database { | |
totalOffers += currPatient.AcceptedOffers | |
totalOffers += currPatient.CancelledOffers | |
} | |
avgOffers := float64(totalOffers)/numPatients | |
// Calculate final score. Distance and cancelled offers are weighted negatively (i.e. a higher distance or higher | |
// number of cancelled appointments leads to a lower score). | |
// Given the input parameters I have (specifying a weight of 10% to age) I'm choosing to prioritize older patients, | |
// as they are more likely to need imminent service. | |
lowActPatients := make([]Patient, 0) | |
for i, _ := range database { | |
database[i].Scores.FinalScore = 0.1*database[i].Scores.Age + 0.1*database[i].Scores.Distance + 0.3*database[i].Scores.AcceptedOffers+ 0.3*database[i].Scores.CancelledOffers+ 0.2*database[i].Scores.AvgReplyTime | |
// Construct list of low-activity patients for possible use later | |
if totalOffers := float64(database[i].AcceptedOffers + database[i].CancelledOffers); totalOffers < 0.3*avgOffers { | |
if rand.Float64() * 0.3 - (totalOffers/avgOffers) > 0 { | |
lowActPatients = append(lowActPatients, database[i]) | |
} | |
} | |
} | |
return lowActPatients | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"math" | |
"os" | |
"strconv" | |
) | |
// Given the input parameters I have (specifying a weight of 10% to age) I'm choosing to prioritize older patients, | |
// as they are more likely to need imminent service. | |
type ByAge []Patient | |
func (t ByAge) Len() int {return len(t)} | |
func (t ByAge) Less(i, j int) bool {return t[i].Age < t[j].Age} | |
func (t ByAge) Swap(i, j int) {t[i], t[j] = t[j], t[i]} | |
//Sorts by distance to a clinic. Note that smaller values are better in this case. | |
type ByDistance []Patient | |
func (t ByDistance) Len() int {return len(t)} | |
func (t ByDistance) Less(i, j int) bool { | |
lat1, _ := strconv.ParseFloat(t[i].PatLocation.Latitude, 64) | |
long1, _ := strconv.ParseFloat(t[i].PatLocation.Longitude, 64) | |
lat2, _ := strconv.ParseFloat(t[j].PatLocation.Latitude, 64) | |
long2, _ := strconv.ParseFloat(t[j].PatLocation.Longitude, 64) | |
clinicLat, err1 := strconv.ParseFloat(clinicLocation.Latitude, 64) | |
clinicLong, err2 := strconv.ParseFloat(clinicLocation.Longitude, 64) | |
if !(err1 == nil && err2 == nil) { | |
fmt.Println("Input location invalid. Please try again.") | |
os.Exit(1) | |
} | |
return math.Sqrt(math.Pow(clinicLat-lat1, 2) + math.Pow(clinicLong-long1, 2)) > math.Sqrt(math.Pow(clinicLat-lat2, 2) + math.Pow(clinicLong-long2, 2)) | |
} | |
func (t ByDistance) Swap(i, j int) {t[i], t[j] = t[j], t[i]} | |
type ByAccepted []Patient | |
func (t ByAccepted) Len() int {return len(t)} | |
func (t ByAccepted) Less(i, j int) bool {return t[i].AcceptedOffers < t[j].AcceptedOffers} | |
func (t ByAccepted) Swap(i, j int) {t[i], t[j] = t[j], t[i]} | |
// Note that the order of this sort is reversed from the previous. | |
type ByCancelled []Patient | |
func (t ByCancelled) Len() int {return len(t)} | |
func (t ByCancelled) Less(i, j int) bool {return t[i].CancelledOffers > t[j].CancelledOffers} | |
func (t ByCancelled) Swap(i, j int) {t[i], t[j] = t[j], t[i]} | |
type ByReplyTime []Patient | |
func (t ByReplyTime) Len() int {return len(t)} | |
func (t ByReplyTime) Less(i, j int) bool {return t[i].AvgReplyTime < t[j].AvgReplyTime} | |
func (t ByReplyTime) Swap(i, j int) {t[i], t[j] = t[j], t[i]} | |
type ByFinalScore []Patient | |
func (t ByFinalScore) Len() int {return len(t)} | |
func (t ByFinalScore) Less(i, j int) bool {return t[i].Scores.FinalScore > t[j].Scores.FinalScore} | |
func (t ByFinalScore) Swap(i, j int) {t[i], t[j] = t[j], t[i]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
type Patient struct { | |
ID string `json:"id"` | |
Name string `json:"name"` | |
PatLocation Location `json:"location"` | |
Age int `json:"age"` | |
AcceptedOffers int `json:"acceptedOffers"` | |
CancelledOffers int `json:"canceledOffers"` | |
AvgReplyTime int `json:"averageReplyTime"` | |
Scores PatientScore //This one is just for internal sorting | |
} | |
type Location struct { | |
Latitude string `json:"latitude"` | |
Longitude string `json:"longitude"` | |
} | |
// Tracks patient rankings in order to determine which scores to assign. | |
type PatientScore struct { | |
//ID string | |
//Name string | |
Distance float64 | |
Age float64 | |
AcceptedOffers float64 | |
CancelledOffers float64 //Will be weighted negatively | |
AvgReplyTime float64 | |
FinalScore float64 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment