122 lines
3.8 KiB
Go
122 lines
3.8 KiB
Go
package main
|
|
|
|
// Ordering, filtering and the weekly measure the verbs entries, groups and status serve (novox/hq ADR 0459
|
|
// §2, §10; issue 1521): pure functions, no store — tools.go's entriesTool, groupsTool and statusTool call
|
|
// them over what Store.Entries, Store.Groups and Store.Lessons already answered.
|
|
|
|
import (
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
// SortEntries orders mail entries as entries answers them (1521/AC1): those marked matters first, nearest
|
|
// due date first among them (none last, within that group); then the rest, newest first.
|
|
func SortEntries(entries []Entry) []Entry {
|
|
sorted := append([]Entry(nil), entries...)
|
|
sort.SliceStable(sorted, func(i, j int) bool {
|
|
a, b := sorted[i], sorted[j]
|
|
aMatters, bMatters := a.Mark == MarkMatters, b.Mark == MarkMatters
|
|
if aMatters != bMatters {
|
|
return aMatters
|
|
}
|
|
if aMatters {
|
|
aNone, bNone := a.DueDate.IsZero(), b.DueDate.IsZero()
|
|
if aNone != bNone {
|
|
return bNone // a has a due date, b does not: a first (none last)
|
|
}
|
|
if !aNone {
|
|
return a.DueDate.Before(b.DueDate) // nearest due date first
|
|
}
|
|
}
|
|
return a.Date.After(b.Date) // newest first, otherwise
|
|
})
|
|
return sorted
|
|
}
|
|
|
|
// FilterEntries keeps only entries of the given mail group (when group is not 0) and the given mark (when
|
|
// mark is not "") (1521/AC1, AC4: "filtered by mail group or by mark when asked").
|
|
func FilterEntries(entries []Entry, group int64, mark Mark) []Entry {
|
|
var out []Entry
|
|
for _, e := range entries {
|
|
if group != 0 && e.Group != group {
|
|
continue
|
|
}
|
|
if mark != "" && e.Mark != mark {
|
|
continue
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// SortGroups orders mail groups largest first (1521/AC2), by label ascending to break a tie.
|
|
func SortGroups(groups []Group) []Group {
|
|
sorted := append([]Group(nil), groups...)
|
|
sort.SliceStable(sorted, func(i, j int) bool {
|
|
if sorted[i].Count != sorted[j].Count {
|
|
return sorted[i].Count > sorted[j].Count
|
|
}
|
|
return sorted[i].Label < sorted[j].Label
|
|
})
|
|
return sorted
|
|
}
|
|
|
|
// WeeksCovered is how many trailing weeks the status verb's own measure covers (ADR 0459 §10: "for each of
|
|
// the last 8 weeks").
|
|
const WeeksCovered = 8
|
|
|
|
// WeekMeasure is one week's reading-lessons-per-100-read-mails measure (ADR 0459 §10, 1521/AC3).
|
|
type WeekMeasure struct {
|
|
WeekStart time.Time
|
|
MailsRead int
|
|
Lessons int
|
|
// Rate is lessons given that week, per 100 mails read that week; nil when MailsRead is 0 ("a week with no
|
|
// mail read says so" — this nil is that saying).
|
|
Rate *float64
|
|
}
|
|
|
|
// weekStart is the Monday, UTC midnight, of the week t falls in.
|
|
func weekStart(t time.Time) time.Time {
|
|
t = t.UTC()
|
|
offset := (int(t.Weekday()) + 6) % 7 // Monday=0 ... Sunday=6
|
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, -offset)
|
|
}
|
|
|
|
// WeeklyMeasure computes the reading-lessons-per-100-read-mails measure for each of the last WeeksCovered
|
|
// weeks, the oldest first, ending with the week now falls in (1521/AC3): lessons given that week, divided by
|
|
// mails read (judged) that week, times 100; a week with no mail read carries no rate.
|
|
func WeeklyMeasure(entries []Entry, lessons []Lesson, now time.Time) []WeekMeasure {
|
|
start := weekStart(now).AddDate(0, 0, -7*(WeeksCovered-1))
|
|
out := make([]WeekMeasure, WeeksCovered)
|
|
for i := range out {
|
|
out[i].WeekStart = start.AddDate(0, 0, 7*i)
|
|
}
|
|
indexOf := func(t time.Time) int {
|
|
if t.IsZero() || t.Before(start) {
|
|
return -1
|
|
}
|
|
idx := int(t.UTC().Sub(start).Hours() / (24 * 7))
|
|
if idx < 0 || idx >= WeeksCovered {
|
|
return -1
|
|
}
|
|
return idx
|
|
}
|
|
for _, e := range entries {
|
|
if i := indexOf(e.Judged); i >= 0 {
|
|
out[i].MailsRead++
|
|
}
|
|
}
|
|
for _, l := range lessons {
|
|
if i := indexOf(l.Made); i >= 0 {
|
|
out[i].Lessons++
|
|
}
|
|
}
|
|
for i := range out {
|
|
if out[i].MailsRead > 0 {
|
|
rate := float64(out[i].Lessons) / float64(out[i].MailsRead) * 100
|
|
out[i].Rate = &rate
|
|
}
|
|
}
|
|
return out
|
|
}
|