81 lines
1.7 KiB
Go
81 lines
1.7 KiB
Go
package bot
|
||
|
||
import (
|
||
"sort"
|
||
|
||
"worktimeBot/internal/db"
|
||
)
|
||
|
||
type State int
|
||
|
||
const (
|
||
StateIdle State = iota
|
||
StateWorking
|
||
StateOnBreak
|
||
)
|
||
|
||
// DailyTotals holds aggregated times for a single day.
|
||
type DailyTotals struct {
|
||
TotalSeconds int64
|
||
BreakSeconds int64
|
||
PairCount int
|
||
}
|
||
|
||
// ComputeDailyTotals pairs events with break inference.
|
||
// Multi‑out sequences: first out ends work, second out starts break,
|
||
// next in ends break, next out ends work, etc.
|
||
func ComputeDailyTotals(events []db.Event) DailyTotals {
|
||
if len(events) == 0 {
|
||
return DailyTotals{}
|
||
}
|
||
sorted := make([]db.Event, len(events))
|
||
copy(sorted, events)
|
||
sort.Slice(sorted, func(i, j int) bool {
|
||
return sorted[i].OccurredAt < sorted[j].OccurredAt
|
||
})
|
||
|
||
var workTotal, breakTotal int64
|
||
var workStart, breakStart int64
|
||
pairs := 0
|
||
state := StateIdle
|
||
|
||
for _, e := range sorted {
|
||
switch {
|
||
case e.EventType == "in":
|
||
switch state {
|
||
case StateIdle, StateOnBreak:
|
||
if state == StateOnBreak {
|
||
breakTotal += e.OccurredAt - breakStart
|
||
breakStart = 0
|
||
}
|
||
workStart = e.OccurredAt
|
||
state = StateWorking
|
||
// Working → ignore duplicate in (shouldn't happen)
|
||
case StateWorking:
|
||
// already working, ignore
|
||
}
|
||
case e.EventType == "out":
|
||
switch state {
|
||
case StateWorking:
|
||
// End of work period
|
||
workTotal += e.OccurredAt - workStart
|
||
workStart = 0
|
||
pairs++
|
||
state = StateIdle
|
||
case StateIdle:
|
||
// Second consecutive out → break start
|
||
breakStart = e.OccurredAt
|
||
state = StateOnBreak
|
||
case StateOnBreak:
|
||
// Duplicate break start – ignore
|
||
}
|
||
}
|
||
}
|
||
|
||
return DailyTotals{
|
||
TotalSeconds: workTotal,
|
||
BreakSeconds: breakTotal,
|
||
PairCount: pairs,
|
||
}
|
||
}
|