diff --git a/server.go b/server.go index 59c2e16..a482e0c 100644 --- a/server.go +++ b/server.go @@ -47,8 +47,7 @@ func getCategories(rw http.ResponseWriter, req *http.Request) { rows, err := db.Query("SELECT * FROM categories") if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } defer rows.Close() @@ -60,22 +59,19 @@ func getCategories(rw http.ResponseWriter, req *http.Request) { ) if err := rows.Scan(&description, &id); err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } categories = append(categories, Category{description, id}) } if err := rows.Err(); err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } js, err := json.Marshal(categories) if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } rw.Header().Set("Content-Type", "application/json") @@ -104,20 +100,17 @@ func addCategory(rw http.ResponseWriter, req *http.Request) { if len(request.Description) > 0 { result, err := db.Exec("INSERT INTO categories(description) VALUES(?)", request.Description) if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } insertId, err := result.LastInsertId() if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } affectedRows, err := result.RowsAffected() if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } response.Success = affectedRows > 0 @@ -126,8 +119,7 @@ func addCategory(rw http.ResponseWriter, req *http.Request) { js, err := json.Marshal(response) if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } rw.Header().Set("Content-Type", "application/json") @@ -151,20 +143,17 @@ func removeCategory(rw http.ResponseWriter, req *http.Request) { result, err := db.Exec("DELETE FROM categories WHERE id = (?)", request.Id) if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } affectedRows, err := result.RowsAffected() if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } js, err := json.Marshal(Response{affectedRows > 0}) if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Fatal(err) } rw.Header().Set("Content-Type", "application/json") diff --git a/types.go b/types.go index 2ac699e..23f4de3 100644 --- a/types.go +++ b/types.go @@ -22,32 +22,42 @@ package main +type Context struct { + hasPosition bool + latitude float64 + longitude float64 + profile Features + walkingDist float64 +} + type Projection struct { - sample float32 + sample float64 stats RecordStats } -type Features map[string]float32 +type Features map[interface{}]float64 type RecordStats struct { - compatibility float32 + compatibility float64 count int } type Range struct { - max float32 - min float32 + max float64 + min float64 } type Record struct { accessCount int - compatibility float32 - distanceToStn float32 - distanceToUser float32 + compatibility float64 + distanceToStn float64 + distanceToUser float64 features Features id int + latitude float64 + longitude float64 name string - score float32 + score float64 } type Records []Record diff --git a/util.go b/util.go index 9a09c8d..bf4c1dc 100644 --- a/util.go +++ b/util.go @@ -22,10 +22,15 @@ package main -import "sort" +import ( + "github.com/kellydunn/golang-geo" + "log" + "math" + "sort" +) -func innerProduct(features1 Features, features2 Features) float32 { - var result float32 +func innerProduct(features1 Features, features2 Features) float64 { + var result float64 for key, value1 := range features1 { value2, _ := features2[key] result += value1 * value2 @@ -34,7 +39,7 @@ func innerProduct(features1 Features, features2 Features) float32 { return result } -func walkMatches(records Records, features Features, minScore float32, callback func(Record, float32)) { +func walkMatches(records Records, features Features, minScore float64, callback func(Record, float64)) { for _, record := range records { if score := innerProduct(features, record.features); score >= minScore { callback(record, score) @@ -42,9 +47,9 @@ func walkMatches(records Records, features Features, minScore float32, callback } } -func statRecords(records Records, features Features, minScore float32) RecordStats { +func statRecords(records Records, features Features, minScore float64) RecordStats { var stats RecordStats - walkMatches(records, features, minScore, func(record Record, score float32) { + walkMatches(records, features, minScore, func(record Record, score float64) { stats.compatibility += record.compatibility stats.count++ }) @@ -52,11 +57,11 @@ func statRecords(records Records, features Features, minScore float32) RecordSta return stats } -func stepRange(rng Range, steps int, callback func(float32)) { - stepSize := (rng.max - rng.min) / float32(steps) +func stepRange(rng Range, steps int, callback func(float64)) { + stepSize := (rng.max - rng.min) / float64(steps) for i := 0; i < steps; i++ { - stepMax := rng.max - stepSize*float32(i) + stepMax := rng.max - stepSize*float64(i) stepMin := stepMax - stepSize stepMid := (stepMin + stepMax) / 2 @@ -64,24 +69,24 @@ func stepRange(rng Range, steps int, callback func(float32)) { } } -func findRecords(records Records, features Features, minScore float32) { +func findRecords(records Records, features Features, minScore float64) { var foundRecords Records - walkMatches(records, features, minScore, func(record Record, score float32) { + walkMatches(records, features, minScore, func(record Record, score float64) { foundRecords = append(foundRecords, record) }) sort.Sort(foundRecords) } -func project(records Records, features Features, featureName string, minScore float32, rng Range, steps int) []Projection { +func project(records Records, features Features, featureName string, minScore float64, rng Range, steps int) []Projection { sampleFeatures := make(Features) for key, value := range features { sampleFeatures[key] = value } var projection []Projection - stepRange(rng, steps, func(sample float32) { + stepRange(rng, steps, func(sample float64) { sampleFeatures[featureName] = sample stats := statRecords(records, sampleFeatures, minScore) projection = append(projection, Projection{sample: sample, stats: stats}) @@ -89,3 +94,91 @@ func project(records Records, features Features, featureName string, minScore fl return projection } + +func computeRecordGeo(records Records, context Context) { + distUserMin := math.MaxFloat64 + distUserMax := 0.0 + + for _, record := range records { + if context.hasPosition { + userPoint := geo.NewPoint(context.latitude, context.longitude) + recordPoint := geo.NewPoint(record.latitude, context.longitude) + record.distanceToUser = userPoint.GreatCircleDistance(recordPoint) + } + + if record.distanceToUser < distUserMin { + distUserMin = record.distanceToUser + } + if record.distanceToUser > distUserMax { + distUserMax = record.distanceToUser + } + } + + distUserRange := distUserMax - distUserMin + + for _, record := range records { + nearby := -((record.distanceToUser-distUserMin)/distUserRange - 0.5) * 2.0 + + accessible := 1.0 - (record.distanceToStn / context.walkingDist) + if accessible < -1.0 { + accessible = 1.0 + } else if accessible > 1.0 { + accessible = 1.0 + } + + record.features["nearby"] = nearby + record.features["accessible"] = accessible + } +} + +func computeRecordPopularity(records Records, context Context) { + for _, record := range records { + historyRows, err := db.Query("SELECT id FROM history WHERE reviewId = (?)", record.id) + if err != nil { + log.Fatal(err) + } + + var groupSum float64 + var groupCount int + + for historyRows.Next() { + var historyId int + if err := historyRows.Scan(&historyId); err != nil { + log.Fatal(err) + } + + groupRows, err := db.Query("SELECT categoryId, categoryValue FROM historyGroups WHERE historyId = (?)", historyId) + if err != nil { + log.Fatal(err) + } + + recordProfile := make(Features) + for groupRows.Next() { + var categoryId int + var categoryValue float64 + + if err := groupRows.Scan(&categoryId, &categoryValue); err != nil { + log.Fatal(err) + } + + recordProfile[categoryId] = categoryValue + } + if err := groupRows.Err(); err != nil { + log.Fatal(err) + } + + groupSum += innerProduct(recordProfile, context.profile) + groupCount++ + } + if err := historyRows.Err(); err != nil { + log.Fatal(err) + } + + var compatibility float64 + if groupCount > 0 { + compatibility = groupSum / float64(groupCount) + } + + record.features["compatibility"] = compatibility + } +}