Optimizer: extend horizon to 48h plus end of day (#32893)

This commit is contained in:
andig 2026-08-17 14:50:55 +02:00 • committed by GitHub
parent 64d48e5800
commit 4cc75b1a45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 47 additions and 2 deletions

View file

@ -413,8 +413,7 @@ func (site *Site) optimizerRequest(battery []types.Measurement) (optimizer.Optim
}
if optimizerURI() == OPTIMIZER_URI {
// limit to 2 days for sake of performance
minLen = min(2*96, minLen)
minLen = slotsUntil(grid, optimizerHorizon(time.Now()), minLen)
}
if expectedSlots := 8; minLen < expectedSlots {
@ -1127,6 +1126,27 @@ func currentRates(tariff api.Tariff) api.Rates {
})
}
// optimizerHorizon is the timeframe the hosted optimizer is limited to for sake
// of performance: 48 hours, extended to the end of that day. In the early hours
// the extension would add almost a full day, hence it only applies past 6:00.
func optimizerHorizon(t time.Time) time.Time {
horizon := t.Add(48 * time.Hour)
if t.Hour() < 6 {
return horizon
}
return now.With(horizon).EndOfDay()
}
// slotsUntil limits maxLen to the slots starting before the given horizon
func slotsUntil(rates api.Rates, horizon time.Time, maxLen int) int {
if i := slices.IndexFunc(rates[:min(maxLen, len(rates))], func(slot api.Rate) bool {
return slot.Start.After(horizon)
}); i >= 0 {
return i
}
return maxLen
}
func timeSteps(minLen int, now time.Time) []int {
res := make([]int, 0, minLen)

View file

@ -7,6 +7,7 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/core/types"
"github.com/evcc-io/evcc/tariff"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
optimizer "github.com/evcc-io/optimizer/client"
@ -29,6 +30,30 @@ func TestLoadpointProfile(t *testing.T) {
require.Equal(t, []float64{250, 250, 250, 250, 250, 250, 250, 50}, loadpointProfile(lp, 8))
}
func TestOptimizerHorizon(t *testing.T) {
ts := time.Date(2025, 1, 1, 10, 30, 0, 0, time.Local)
horizon := optimizerHorizon(ts)
// 48h plus end of day
assert.Equal(t, time.Date(2025, 1, 3, 23, 59, 59, int(time.Second-time.Nanosecond), time.Local), horizon)
// before 6:00 the day is not extended
assert.Equal(t, time.Date(2025, 1, 3, 5, 30, 0, 0, time.Local),
optimizerHorizon(time.Date(2025, 1, 1, 5, 30, 0, 0, time.Local)))
rates := make(api.Rates, 0, 4*96)
for slot := ts.Truncate(tariff.SlotDuration); len(rates) < cap(rates); slot = slot.Add(tariff.SlotDuration) {
rates = append(rates, api.Rate{Start: slot, End: slot.Add(tariff.SlotDuration)})
}
// 4 days of slots from 10:30, capped at the last slot of Jan 3rd
assert.Equal(t, 246, slotsUntil(rates, horizon, len(rates)))
assert.Equal(t, time.Date(2025, 1, 3, 23, 45, 0, 0, time.Local), rates[245].Start)
// shorter forecast is not extended
assert.Equal(t, 8, slotsUntil(rates, horizon, 8))
}
func TestApplyPrecondition(t *testing.T) {
ctrl := gomock.NewController(t)