From e41066140510a4e16a49eb162b69f5eecb744ec3 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 10 Jan 2026 11:27:33 +0100 Subject: [PATCH] Optimizer: decouple from solar forecast (#26584) --- core/site_optimizer.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/core/site_optimizer.go b/core/site_optimizer.go index 2da2750f9..e78e0c7fe 100644 --- a/core/site_optimizer.go +++ b/core/site_optimizer.go @@ -86,11 +86,17 @@ func (site *Site) optimizerUpdate(battery []measurement) error { return nil } - solar := currentRates(site.GetTariff(api.TariffUsageSolar)) + solarTariff := site.GetTariff(api.TariffUsageSolar) + solar := currentRates(solarTariff) + grid := currentRates(site.GetTariff(api.TariffUsageGrid)) feedIn := currentRates(site.GetTariff(api.TariffUsageFeedIn)) - minLen := lo.Min([]int{len(grid), len(feedIn), len(solar)}) + minLen := lo.Min([]int{len(grid), len(feedIn)}) + if solarTariff != nil { + // allow empty solar forecast + minLen = min(minLen, len(solar)) + } if minLen < 8 { return fmt.Errorf("not enough slots for optimization: %d (grid=%d, feedIn=%d, solar=%d)", minLen, len(grid), len(feedIn), len(solar)) } @@ -110,9 +116,15 @@ func (site *Site) optimizerUpdate(battery []measurement) error { return err } - solarEnergy, err := solarRatesToEnergy(solar) - if err != nil { - return err + // allow empty solar forecast + ft := lo.RepeatBy(minLen, func(i int) float32 { return float32(0) }) + if solarTariff != nil { + solarEnergy, err := solarRatesToEnergy(solar) + if err != nil { + return err + } + + ft = prorate(scaleAndPrune(solarEnergy, 1, minLen), firstSlotDuration) } req := evopt.OptimizationInput{ @@ -125,7 +137,7 @@ func (site *Site) optimizerUpdate(battery []measurement) error { TimeSeries: evopt.TimeSeries{ Dt: dt, Gt: prorate(gt, firstSlotDuration), - Ft: prorate(scaleAndPrune(solarEnergy, 1, minLen), firstSlotDuration), + Ft: ft, PN: scaleAndPrune(grid, 1e3, minLen), PE: scaleAndPrune(feedIn, 1e3, minLen), },