Skip to content

Commit

Permalink
Merge pull request #81 from jovandeginste/convert-user-statistics
Browse files Browse the repository at this point in the history
Convert user statistics to use the new function
  • Loading branch information
jovandeginste authored Apr 11, 2024
2 parents d9d1832 + d81f356 commit 2c63542
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 264 deletions.
15 changes: 0 additions & 15 deletions pkg/app/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,6 @@ func (a *App) addUsers(data map[string]interface{}) error {
return nil
}

func (a *App) addUserStatistics(u *database.User, data map[string]interface{}) error {
if u == nil {
return nil
}

us, err := u.Statistics(a.db)
if err != nil {
return err
}

data["UserStatistics"] = us

return nil
}

func (a *App) getWorkout(c echo.Context) (*database.Workout, error) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
Expand Down
8 changes: 0 additions & 8 deletions pkg/app/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ func (a *App) statisticsHandler(c echo.Context) error {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}

if err := a.addUserStatistics(u, data); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}

data["user"] = u

return c.Render(http.StatusOK, "user_statistics.html", data)
Expand All @@ -46,10 +42,6 @@ func (a *App) dashboardHandler(c echo.Context) error {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}

if err := a.addUserStatistics(u, data); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}

if err := a.addUsers(data); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}
Expand Down
4 changes: 0 additions & 4 deletions pkg/app/users_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,5 @@ func (a *App) userShowHandler(c echo.Context) error {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}

if err := a.addUserStatistics(u, data); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-signout"), err)
}

return c.Render(http.StatusOK, "user_show.html", data)
}
34 changes: 10 additions & 24 deletions pkg/database/statistics.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package database

import (
"time"
)

type StatConfig struct {
Since string `query:"since"`
Per string `query:"per"`
Expand All @@ -26,27 +22,17 @@ func (sc *StatConfig) GetSince() string {
return sc.Since
}

type Statistics struct {
UserID uint
Buckets map[string]map[WorkoutType]Bucket
}

type Bucket struct {
Bucket string `json:",omitempty"`
WorkoutType WorkoutType
Workouts int
Distance float64 `json:",omitempty"`
Up float64 `json:",omitempty"`
Duration time.Duration `json:",omitempty"`
AverageSpeed float64 `json:",omitempty"`
AverageSpeedNoPause float64 `json:",omitempty"`
MaxSpeed float64 `json:",omitempty"`
func (u *User) GetDefaultStatistics() (*Statistics, error) {
return u.GetStatistics(StatConfig{
Since: "-1 year",
Per: "month",
})
}

func (u *User) GetStatistics(statConfig StatConfig) (*Statistics, error) {
r := &Statistics{
UserID: u.ID,
Buckets: map[string]map[WorkoutType]Bucket{},
Buckets: map[WorkoutType]map[string]Bucket{},
}

rows, err := u.db.
Expand Down Expand Up @@ -79,11 +65,11 @@ func (u *User) GetStatistics(statConfig StatConfig) (*Statistics, error) {
return nil, err
}

if r.Buckets[result.Bucket] == nil {
r.Buckets[result.Bucket] = map[WorkoutType]Bucket{}
if r.Buckets[result.WorkoutType] == nil {
r.Buckets[result.WorkoutType] = map[string]Bucket{}
}

r.Buckets[result.Bucket][result.WorkoutType] = result
r.Buckets[result.WorkoutType][result.Bucket] = result
}

return r, nil
Expand Down Expand Up @@ -143,7 +129,7 @@ func (u *User) GetRecords(t WorkoutType) (*WorkoutRecord, error) {

r := &WorkoutRecord{WorkoutType: t}

mapping := map[*record]string{
mapping := map[*float64Record]string{
&r.Distance: "max(total_distance)",
&r.MaxSpeed: "max(max_speed)",
&r.TotalUp: "max(total_up)",
Expand Down
207 changes: 36 additions & 171 deletions pkg/database/user_statistics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,197 +2,62 @@ package database

import (
"time"

"gorm.io/gorm"
)

type (
record struct {
Value float64
Date time.Time
ID uint
Statistics struct {
UserID uint
Buckets map[WorkoutType]map[string]Bucket
}

WorkoutStatistics map[string]*WorkoutStatistic
Bucket struct {
Bucket string `json:",omitempty"`
WorkoutType WorkoutType
Workouts int
Distance float64 `json:",omitempty"`
Up float64 `json:",omitempty"`
Duration time.Duration `json:",omitempty"`
AverageSpeed float64 `json:",omitempty"`
AverageSpeedNoPause float64 `json:",omitempty"`
MaxSpeed float64 `json:",omitempty"`
}

WorkoutStatistic struct {
WorkoutType WorkoutType
Total Totals
PerYear map[int]*Totals
PerMonth map[int]map[int]*Totals
Records WorkoutRecord
float64Record struct {
Value float64
Date time.Time
ID uint
}

Totals struct {
WorkoutType WorkoutType
Workouts int
Distance float64
Up float64
Duration time.Duration
AverageSpeed float64
AverageSpeedNoPause float64
MaxSpeed float64
durationRecord struct {
Value time.Duration
Date time.Time
ID uint
}

WorkoutRecord struct {
WorkoutType WorkoutType
Active bool
AverageSpeed record
AverageSpeedNoPause record
MaxSpeed record
Distance record
TotalUp record
Duration struct {
Value time.Duration
Date time.Time
ID uint
}
AverageSpeed float64Record
AverageSpeedNoPause float64Record
MaxSpeed float64Record
Distance float64Record
TotalUp float64Record
Duration durationRecord
}
)

func (t *Totals) AverageSpeedKPH() float64 {
return 3.6 * t.AverageSpeed
}

func (t *Totals) AverageSpeedNoPauseKPH() float64 {
return 3.6 * t.AverageSpeedNoPause
}

func (t *Totals) MaxSpeedKPH() float64 {
return 3.6 * t.MaxSpeed
}

func NewWorkoutStatistic(t WorkoutType) *WorkoutStatistic {
return &WorkoutStatistic{
WorkoutType: t,
Records: WorkoutRecord{WorkoutType: t},
Total: Totals{WorkoutType: t},
PerYear: map[int]*Totals{},
PerMonth: map[int]map[int]*Totals{},
}
}

func (r *record) CheckAndSwap(value float64, id uint, date *time.Time) {
if r.Value < value {
r.Value = value
r.Date = *date
r.ID = id
}
}

func (us *WorkoutStatistic) Add(w *Workout) {
us.Total.Workouts++
us.Total.Distance += w.Data.TotalDistance
us.Total.Duration += w.Data.TotalDuration
us.Total.Up += w.Data.TotalUp
us.Total.AverageSpeed += w.Data.AverageSpeed()
us.Total.AverageSpeedNoPause += w.Data.AverageSpeedNoPause()
us.Total.MaxSpeed += w.Data.MaxSpeed

d := w.Date
year := d.Year()
month := int(d.Month())

us.AddYear(us.WorkoutType, year, w)
us.AddMonth(us.WorkoutType, year, month, w)
}

func NewTotal(t WorkoutType, d *MapData) *Totals {
return &Totals{
Workouts: 1,
WorkoutType: t,
Distance: d.TotalDistance,
Up: d.TotalUp,
Duration: d.TotalDuration,
AverageSpeed: d.AverageSpeed(),
AverageSpeedNoPause: d.AverageSpeedNoPause(),
MaxSpeed: d.MaxSpeed,
}
func (b Bucket) DistanceKM() float64 {
return b.Distance / 1000
}

func calcNewAvg(oldValue, newValue float64, newCounter int) float64 {
c := float64(newCounter)
return ((oldValue * (c - 1)) + newValue) / c
func (b Bucket) AverageSpeedNoPauseKPH() float64 {
return 3.6 * b.AverageSpeedNoPause
}

func (t *Totals) Add(d *MapData) {
t.Workouts++
t.Distance += d.TotalDistance
t.Duration += d.TotalDuration
t.Up += d.TotalUp

t.AverageSpeed = calcNewAvg(t.AverageSpeed, d.AverageSpeed(), t.Workouts)
t.AverageSpeedNoPause = calcNewAvg(t.AverageSpeedNoPause, d.AverageSpeedNoPause(), t.Workouts)

if d.MaxSpeed > t.MaxSpeed {
t.MaxSpeed = d.MaxSpeed
}
func (b Bucket) AverageSpeedKPH() float64 {
return 3.6 * b.AverageSpeed
}

func (us *WorkoutStatistic) AddMonth(t WorkoutType, year, month int, w *Workout) {
if _, ok := us.PerMonth[year]; !ok {
us.PerMonth[year] = map[int]*Totals{}
}

entry, ok := us.PerMonth[year][month]
if !ok {
us.PerMonth[year][month] = NewTotal(t, w.Data)

return
}

entry.Add(w.Data)
}

func (us *WorkoutStatistic) AddYear(t WorkoutType, year int, w *Workout) {
entry, ok := us.PerYear[year]
if !ok {
us.PerYear[year] = NewTotal(t, w.Data)

return
}

entry.Add(w.Data)
}

func (u *User) Statistics(db *gorm.DB) (WorkoutStatistics, error) {
us := WorkoutStatistics{}

workouts, err := u.GetWorkouts(db)
if err != nil {
return nil, err
}

for _, w := range workouts {
if !w.Type.IsDistance() {
continue
}

s, ok := us[w.Type.String()]
if !ok {
s = NewWorkoutStatistic(w.Type)
us[w.Type.String()] = s
}

s.Records.Active = true
s.Add(w)

s.Records.CheckAndSwap(w)
}

return us, nil
}

func (wr *WorkoutRecord) CheckAndSwap(w *Workout) {
wr.AverageSpeedNoPause.CheckAndSwap(w.Data.AverageSpeedNoPause(), w.ID, w.Date)
wr.AverageSpeed.CheckAndSwap(w.Data.AverageSpeed(), w.ID, w.Date)
wr.MaxSpeed.CheckAndSwap(w.Data.MaxSpeed, w.ID, w.Date)
wr.Distance.CheckAndSwap(w.Data.TotalDistance, w.ID, w.Date)
wr.TotalUp.CheckAndSwap(w.Data.TotalUp, w.ID, w.Date)

if w.Data.TotalDuration > wr.Duration.Value {
wr.Duration.Value = w.Data.TotalDuration
wr.Duration.ID = w.ID
wr.Duration.Date = *w.Date
}
func (b Bucket) MaxSpeedKPH() float64 {
return 3.6 * b.MaxSpeed
}
Loading

0 comments on commit 2c63542

Please sign in to comment.