summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-04 00:09:45 +0200
committerPaul Buetow <paul@buetow.org>2026-03-04 00:09:45 +0200
commit79b21d5b7d82ceed208fbf488cbac2f01f6ba635 (patch)
treee0d074ea0998a203fd2920452c4a6d13f632e070 /internal
parentcc070c69a5782d6640c81a88d1c1f9c5a984ca6f (diff)
worktime: extract BuildReport entry processor
Diffstat (limited to 'internal')
-rw-r--r--internal/worktime/report.go146
1 files changed, 84 insertions, 62 deletions
diff --git a/internal/worktime/report.go b/internal/worktime/report.go
index e46cd4d..246a602 100644
--- a/internal/worktime/report.go
+++ b/internal/worktime/report.go
@@ -50,6 +50,17 @@ type weekAccumulator struct {
values map[string]int64
}
+type reportBuildContext struct {
+ login map[string]Entry
+ totalBuffer int64
+ cumulativeBalance int64
+ reports []WeekReport
+ currentDay dayAccumulator
+ currentWeek weekAccumulator
+ prevDayKey string
+ prevWeekKey string
+}
+
// BuildReport generates weekly reports from merged worktime entries.
func BuildReport(entries []Entry, cfg config.Config) ([]WeekReport, error) {
if len(entries) == 0 {
@@ -66,80 +77,91 @@ func BuildReport(entries []Entry, cfg config.Config) ([]WeekReport, error) {
weekendDays := stringSet(cfg.WeekendDays)
plusFor := stringSet(cfg.PlusFor)
- login := map[string]Entry{}
- var totalBuffer int64
- var cumulativeBalance int64
- var reports []WeekReport
+ ctx := reportBuildContext{
+ login: map[string]Entry{},
+ currentDay: newDayAccumulator(),
+ currentWeek: newWeekAccumulator(),
+ }
- currentDay := newDayAccumulator()
- currentWeek := newWeekAccumulator()
+ for _, entry := range sorted {
+ if err := processEntry(&ctx, entry, cfg, plusFor, minusFor, weekendDays, bufferFor); err != nil {
+ return nil, err
+ }
+ }
- prevDayKey := ""
- prevWeekKey := ""
+ finalizeDayIntoWeek(&ctx.currentWeek, ctx.currentDay, minusFor, weekendDays)
+ weekReport := finalizeWeek(ctx.currentWeek, cfg, plusFor, minusFor, ctx.totalBuffer, &ctx.cumulativeBalance)
+ ctx.reports = append(ctx.reports, weekReport)
- for _, entry := range sorted {
- entryDayKey := dayKey(entry.Epoch)
- entryWeekKey := isoWeekKey(entry.Epoch)
+ return ctx.reports, nil
+}
- if prevDayKey == "" {
- prevDayKey = entryDayKey
- }
- if prevWeekKey == "" {
- prevWeekKey = entryWeekKey
- currentWeek.weekLabel = weekLabel(entry.Epoch)
- }
+func processEntry(
+ ctx *reportBuildContext,
+ entry Entry,
+ cfg config.Config,
+ plusFor map[string]struct{},
+ minusFor map[string]struct{},
+ weekendDays map[string]struct{},
+ bufferFor map[string]struct{},
+) error {
+ entryDayKey := dayKey(entry.Epoch)
+ entryWeekKey := isoWeekKey(entry.Epoch)
+
+ if ctx.prevDayKey == "" {
+ ctx.prevDayKey = entryDayKey
+ }
+ if ctx.prevWeekKey == "" {
+ ctx.prevWeekKey = entryWeekKey
+ ctx.currentWeek.weekLabel = weekLabel(entry.Epoch)
+ }
- if entryDayKey != prevDayKey {
- finalizeDayIntoWeek(&currentWeek, currentDay, minusFor, weekendDays)
- currentDay = newDayAccumulator()
- prevDayKey = entryDayKey
- }
+ if entryDayKey != ctx.prevDayKey {
+ finalizeDayIntoWeek(&ctx.currentWeek, ctx.currentDay, minusFor, weekendDays)
+ ctx.currentDay = newDayAccumulator()
+ ctx.prevDayKey = entryDayKey
+ }
- if entryWeekKey != prevWeekKey {
- weekReport := finalizeWeek(currentWeek, cfg, plusFor, minusFor, totalBuffer, &cumulativeBalance)
- reports = append(reports, weekReport)
- currentWeek = newWeekAccumulator()
- currentWeek.weekLabel = weekLabel(entry.Epoch)
- prevWeekKey = entryWeekKey
- }
+ if entryWeekKey != ctx.prevWeekKey {
+ weekReport := finalizeWeek(ctx.currentWeek, cfg, plusFor, minusFor, ctx.totalBuffer, &ctx.cumulativeBalance)
+ ctx.reports = append(ctx.reports, weekReport)
+ ctx.currentWeek = newWeekAccumulator()
+ ctx.currentWeek.weekLabel = weekLabel(entry.Epoch)
+ ctx.prevWeekKey = entryWeekKey
+ }
- category := normalizeCategory(entry.What)
- if currentDay.epoch == 0 {
- currentDay.epoch = entry.Epoch
+ category := normalizeCategory(entry.What)
+ if ctx.currentDay.epoch == 0 {
+ ctx.currentDay.epoch = entry.Epoch
+ }
+ if _, ok := ctx.currentDay.values[category]; !ok {
+ ctx.currentDay.values[category] = 0
+ }
+
+ action := strings.ToLower(strings.TrimSpace(entry.Action))
+ switch action {
+ case actionAdd:
+ ctx.currentDay.values[category] += entry.Value
+ if _, ok := bufferFor[category]; ok {
+ ctx.totalBuffer += entry.Value
}
- if _, ok := currentDay.values[category]; !ok {
- currentDay.values[category] = 0
+ case actionLogin:
+ if _, ok := ctx.login[category]; ok {
+ return fmt.Errorf("already logged in for %q at epoch %d", category, entry.Epoch)
}
-
- action := strings.ToLower(strings.TrimSpace(entry.Action))
- switch action {
- case actionAdd:
- currentDay.values[category] += entry.Value
- if _, ok := bufferFor[category]; ok {
- totalBuffer += entry.Value
- }
- case actionLogin:
- if _, ok := login[category]; ok {
- return nil, fmt.Errorf("already logged in for %q at epoch %d", category, entry.Epoch)
- }
- login[category] = entry
- case actionLogout:
- startEntry, ok := login[category]
- if !ok {
- return nil, fmt.Errorf("logout without login for %q at epoch %d", category, entry.Epoch)
- }
- currentDay.values[category] += entry.Epoch - startEntry.Epoch
- delete(login, category)
- default:
- return nil, fmt.Errorf("unknown action %q at epoch %d", entry.Action, entry.Epoch)
+ ctx.login[category] = entry
+ case actionLogout:
+ startEntry, ok := ctx.login[category]
+ if !ok {
+ return fmt.Errorf("logout without login for %q at epoch %d", category, entry.Epoch)
}
+ ctx.currentDay.values[category] += entry.Epoch - startEntry.Epoch
+ delete(ctx.login, category)
+ default:
+ return fmt.Errorf("unknown action %q at epoch %d", entry.Action, entry.Epoch)
}
- finalizeDayIntoWeek(&currentWeek, currentDay, minusFor, weekendDays)
- weekReport := finalizeWeek(currentWeek, cfg, plusFor, minusFor, totalBuffer, &cumulativeBalance)
- reports = append(reports, weekReport)
-
- return reports, nil
+ return nil
}
// FormatReport renders week/day reports as text. Colors can be toggled.