Tariff (solar): resample sub-slots along the source curve (#33105)

This commit is contained in:
andig 2026-08-23 12:05:20 +02:00 • committed by GitHub
parent f43499ea97
commit defe9403de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 94 additions and 57 deletions

View file

@ -6,8 +6,10 @@ import (
"github.com/benbjohnson/clock"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/tariff"
"github.com/jinzhu/now"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
@ -112,3 +114,39 @@ func TestSolarEnergyNoRates(t *testing.T) {
assert.Equal(t, 0.0, solarEnergy(api.Rates{}, now, now.Add(time.Hour)))
assert.Equal(t, 0.0, solarEnergy(nil, now, now.Add(time.Hour)))
}
type solarTariff struct {
rates api.Rates
}
func (t *solarTariff) Rates() (api.Rates, error) { return t.rates, nil }
func (t *solarTariff) Type() api.TariffType { return api.TariffTypeSolar }
// TestSolarSlotSplitPreservesEnergy asserts that splitting hourly solar rates into
// 15min slots leaves the integrated energy untouched- the wrapper resamples the
// curve, it does not change it
func TestSolarSlotSplitPreservesEnergy(t *testing.T) {
start := time.Now().Truncate(tariff.SlotDuration)
var rr api.Rates
for i, v := range []float64{0, 1000, 2500, 3000, 1500, 0} {
s := start.Add(time.Duration(i) * time.Hour)
rr = append(rr, api.Rate{Start: s, End: s.Add(time.Hour), Value: v})
}
w := &tariff.SlotWrapper{Tariff: &solarTariff{rates: rr}}
res, err := w.Rates()
require.NoError(t, err)
require.Len(t, res, 6*4)
to := rr[len(rr)-1].Start
assert.InDelta(t, solarEnergy(rr, start, to), solarEnergy(res, start, to), 1e-9)
// every sub-interval integrates identically, not just the total
for i := range res {
assert.InDelta(t,
solarEnergy(rr, start, res[i].Start),
solarEnergy(res, start, res[i].Start),
1e-9, "slot %d", i)
}
}