91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
package tariff
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/util"
|
|
)
|
|
|
|
// Merged combines a primary tariff with a secondary (forecast) tariff.
|
|
// Primary rates are used where available, secondary fills gaps after primary ends.
|
|
type Merged struct {
|
|
log *util.Logger
|
|
primary api.Tariff
|
|
secondary api.Tariff
|
|
}
|
|
|
|
func init() {
|
|
registry.AddCtx("merged", NewMergedFromConfig)
|
|
}
|
|
|
|
func NewMergedFromConfig(ctx context.Context, other map[string]any) (api.Tariff, error) {
|
|
var cc struct {
|
|
Primary, Secondary Typed
|
|
}
|
|
|
|
if err := util.DecodeOther(other, &cc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
primary, err := NewFromConfig(ctx, cc.Primary.Type, cc.Primary.Other)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
secondary, err := NewFromConfig(ctx, cc.Secondary.Type, cc.Secondary.Other)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if pType, sType := primary.Type(), secondary.Type(); pType != sType {
|
|
return nil, fmt.Errorf("primary and secondary tariff types are not compatible: %v vs %v", pType, sType)
|
|
}
|
|
|
|
t := &Merged{
|
|
log: util.NewLogger("merged"),
|
|
primary: primary,
|
|
secondary: secondary,
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// Rates implements the api.Tariff interface
|
|
func (t *Merged) Rates() (api.Rates, error) {
|
|
result, err := t.primary.Rates()
|
|
if err != nil {
|
|
t.log.DEBUG.Printf("primary tariff failed, falling back to secondary: %v", err)
|
|
return t.secondary.Rates()
|
|
}
|
|
|
|
secondaryRates, err := t.secondary.Rates()
|
|
if err != nil {
|
|
t.log.DEBUG.Printf("secondary tariff failed, using primary only: %v", err)
|
|
return result, nil
|
|
}
|
|
|
|
// If primary is empty, use all secondary rates
|
|
if len(result) == 0 {
|
|
return secondaryRates, nil
|
|
}
|
|
|
|
// Find where primary data ends and append secondary rates starting there
|
|
primaryEnd := result[len(result)-1].End
|
|
if idx, found := slices.BinarySearchFunc(secondaryRates, primaryEnd, func(r api.Rate, t time.Time) int {
|
|
return r.Start.Compare(t)
|
|
}); found {
|
|
return append(result, secondaryRates[idx:]...), nil
|
|
}
|
|
|
|
t.log.WARN.Printf("secondary tariff does not align gapless with primary, ignoring secondary")
|
|
return result, nil
|
|
}
|
|
|
|
// Type implements the api.Tariff interface
|
|
func (t *Merged) Type() api.TariffType {
|
|
return t.primary.Type()
|
|
}
|