Amber: normalise rates to 1-hour slots (#22870)

This commit is contained in:
ross-w 2025-08-10 18:34:20 +10:00 • committed by GitHub
parent fb09b370d0
commit 11e7c13f01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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) })
}