package main import "sort" // aggregateDays folds chunks into days, newest day first, each with its running lifetime // total. Pure, so the arithmetic is tested without a database. func aggregateDays(chunks []ConsumptionChunk) []DailyConsumption { type day struct { DailyConsumption sohAt int64 hasSoh bool } byDate := map[string]*day{} for _, chunk := range chunks { d, ok := byDate[chunk.Date] if !ok { d = &day{DailyConsumption: DailyConsumption{ InstallationID: chunk.InstallationID, Date: chunk.Date, RecordedAt: chunk.RecordedAt, }} byDate[chunk.Date] = d } d.Day.add(chunk.Day) if chunk.RecordedAt.After(d.RecordedAt) { d.RecordedAt = chunk.RecordedAt } // The newest manual SoH of the day wins. at := chunk.RecordedAt.UnixMilli() if chunk.SohPercent != nil && (!d.hasSoh || at > d.sohAt) { soh := *chunk.SohPercent d.SohPercent = &soh d.sohAt = at d.hasSoh = true } } days := make([]DailyConsumption, 0, len(byDate)) for _, d := range byDate { days = append(days, d.DailyConsumption) } sort.Slice(days, func(i, j int) bool { return days[i].Date < days[j].Date }) var running Totals for i := range days { running.add(days[i].Day) days[i].Lifetime = running } for i, j := 0, len(days)-1; i < j; i, j = i+1, j-1 { days[i], days[j] = days[j], days[i] } return days }