diff --git a/tariff/amber.go b/tariff/amber.go index be64f6556..65b5a19b9 100644 --- a/tariff/amber.go +++ b/tariff/amber.go @@ -95,29 +95,76 @@ func (t *Amber) run(done chan error) { continue } - data := make(api.Rates, 0, len(res)) + // Group by hour and average intervals within each hour + hourlyData := make(map[time.Time]*struct { + totalValue float64 + totalDuration time.Duration + start time.Time + currentValue *float64 // Override with current interval if present (for accurate charging session costs) + }) for _, r := range res { if t.channel == strings.ToLower(r.ChannelType) { startTime, _ := time.Parse("2006-01-02T15:04:05Z", r.StartTime) endTime, _ := time.Parse("2006-01-02T15:04:05Z", r.EndTime) - ar := api.Rate{ - Start: startTime.Local(), - End: endTime.Local(), - Value: r.PerKwh / 1e2, - } + + value := r.PerKwh / 1e2 if r.AdvancedPrice != nil { - ar.Value = r.AdvancedPrice.Predicted / 1e2 + value = r.AdvancedPrice.Predicted / 1e2 } // Invert feed-in prices to match evcc expectations (positive = paid for exports) if t.channel == "feedin" { - ar.Value = -ar.Value + value = -value + } + + localStart := startTime.Local() + localEnd := endTime.Local() + hourStart := localStart.Truncate(time.Hour) // Preserve date+hour + duration := localEnd.Sub(localStart) + + // Initialize hour entry if needed + if hourlyData[hourStart] == nil { + hourlyData[hourStart] = &struct { + totalValue float64 + totalDuration time.Duration + start time.Time + currentValue *float64 + }{start: hourStart} + } + + hr := hourlyData[hourStart] + + // If this is the current interval, use its value directly for this hour + if r.Type == "CurrentInterval" { + hr.currentValue = &value + } else { + // Add to weighted average for forecast intervals + hr.totalValue += value * duration.Seconds() + hr.totalDuration += duration } - data = append(data, ar) } } + // Convert to final hourly rates + data := make(api.Rates, 0, len(hourlyData)) + for _, hr := range hourlyData { + var finalValue float64 + if hr.currentValue != nil { + // Use current interval value if available + finalValue = *hr.currentValue + } else if hr.totalDuration > 0 { + // Otherwise use weighted average of forecast intervals + finalValue = hr.totalValue / hr.totalDuration.Seconds() + } + + data = append(data, api.Rate{ + Start: hr.start, + End: hr.start.Add(time.Hour), + Value: finalValue, + }) + } + mergeRates(t.data, data) once.Do(func() { close(done) }) }