Playtime: average over the days recorded, full bars for leaders under an hour

Busiest hours and the heatmap averaged over 30 days (4 weeks) even when
recording began yesterday; they now use only the days since it began, and
say so. Bars capped their scale at 1, so a leader with 0.5 h got half a
bar. The hours tile keeps a decimal under 10, and K/D shows - with no
kills or deaths yet.
This commit is contained in:
Astra 2026-10-04 22:24:37 +01:00
parent f659859d66
commit c7fbff0e43
4 changed files with 57 additions and 14 deletions

View file

@ -233,8 +233,12 @@ type Overview struct {
Summary Summary `json:"summary"`
ByHour [24]float64 `json:"byHour"` // average players on T/CT in each local hour of the day
Heat [7][24]float64 `json:"heat"` // the same per weekday, Monday first
Days []Day `json:"days"`
Maps []MapTime `json:"maps"`
// The days ByHour and Heat average over: the last 30 (28 for Heat), or fewer while the
// recording is younger than that, so a new server doesn't look emptier than it is.
ByHourDays int `json:"byHourDays"`
HeatDays int `json:"heatDays"`
Days []Day `json:"days"`
Maps []MapTime `json:"maps"`
}
func (s *Store) Overview(ctx context.Context, now time.Time, loc *time.Location) (Overview, error) {
@ -246,12 +250,38 @@ func (s *Store) Overview(ctx context.Context, now time.Time, loc *time.Location)
}
week, fortnight := now.Add(-7*24*time.Hour), now.Add(-14*24*time.Hour)
heatFrom := dayStart(now, loc).AddDate(0, 0, -27)
// Average over the days there's been recording for, not days from before it began.
where, args := s.and("server_id", nil)
var first sql.NullTime
if err := s.db.QueryRowContext(ctx, `SELECT MIN(started_at) FROM pt_sessions WHERE 1=1`+where, args...).Scan(&first); err != nil {
return o, err
}
byHourFrom, heatStart := start, heatFrom
if first.Valid {
if f := dayStart(first.Time, loc); f.After(byHourFrom) {
byHourFrom = f
}
if f := dayStart(first.Time, loc); f.After(heatStart) {
heatStart = f
}
}
today := dayStart(now, loc)
var weekdays [7]int // how many of each weekday the heatmap covers
for d := heatStart; !d.After(today); d = d.AddDate(0, 0, 1) {
weekdays[(int(d.Weekday())+6)%7]++
o.HeatDays++
}
for d := byHourFrom; !d.After(today); d = d.AddDate(0, 0, 1) {
o.ByHourDays++
}
for _, b := range buckets {
h := float64(b.Seconds) / 3600
local := b.Start.In(loc)
o.ByHour[local.Hour()] += h / 30
if !local.Before(heatFrom) {
o.Heat[(int(local.Weekday())+6)%7][local.Hour()] += h / 4
o.ByHour[local.Hour()] += h / float64(max(o.ByHourDays, 1))
if wd := (int(local.Weekday()) + 6) % 7; !local.Before(heatFrom) && weekdays[wd] > 0 {
o.Heat[wd][local.Hour()] += h / float64(weekdays[wd])
}
switch {
case !b.Start.Before(week):
@ -271,7 +301,7 @@ func (s *Store) Overview(ctx context.Context, now time.Time, loc *time.Location)
}
o.Summary.Hours, o.Summary.HoursPrev = round1(o.Summary.Hours), round1(o.Summary.HoursPrev)
where, args := s.and("server_id", []any{week, fortnight, week, fortnight})
where, args = s.and("server_id", []any{week, fortnight, week, fortnight})
if err := s.db.QueryRowContext(ctx, `SELECT
COUNT(DISTINCT IF(started_at >= ?, steamid64, NULL)),
COUNT(DISTINCT IF(started_at >= ? AND started_at < ?, steamid64, NULL))