Tariffs: add formulas (#17002)

This commit is contained in:
andig 2024-11-06 15:17:46 +01:00 • committed by GitHub
parent c9b40dba69
commit bb8bf49f8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 114 additions and 3 deletions

View file

@ -197,6 +197,13 @@ tariffs:
# uri: https://example.org/price.json
# jq: .price.current
# type: template
# template: energy-charts-api # epex spot market prices
# bzn: DE-LU
# charges: 0.15
# tax: 0.1
# formula: math.Min((price + charges) * (1 + tax), 0.5)
feedin:
# rate for feeding excess (pv) energy to the grid
type: fixed

View file

@ -39,6 +39,10 @@ func NewAwattarFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, err
}
if err := cc.init(); err != nil {
return nil, err
}
t := &Awattar{
embed: &cc.embed,
log: util.NewLogger("awattar"),

View file

@ -53,6 +53,10 @@ func NewEdfTempoFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, errors.New("missing credentials")
}
if err := cc.init(); err != nil {
return nil, err
}
basic := transport.BasicAuthHeader(cc.ClientID, cc.ClientSecret)
log := util.NewLogger("edf-tempo").Redact(basic)

View file

@ -43,6 +43,10 @@ func NewEleringFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, errors.New("missing region")
}
if err := cc.init(); err != nil {
return nil, err
}
t := &Elering{
embed: &cc.embed,
log: util.NewLogger("elering"),

View file

@ -1,12 +1,71 @@
package tariff
import (
"errors"
"fmt"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
type embed struct {
Margin float64 `mapstructure:"margin"`
Charges float64 `mapstructure:"charges"`
Tax float64 `mapstructure:"tax"`
Uplifts float64 `mapstructure:"uplifts"`
Formula string `mapstructure:"formula"`
calc func(float64) (float64, error)
}
func (t *embed) init() error {
if t.Formula == "" {
return nil
}
vm := interp.New(interp.Options{})
if err := vm.Use(stdlib.Symbols); err != nil {
return err
}
if _, err := vm.Eval(`import "math"`); err != nil {
return err
}
if _, err := vm.Eval("var price, charges, tax float64"); err != nil {
return err
}
prg, err := vm.Compile(t.Formula)
if err != nil {
return err
}
t.calc = func(price float64) (float64, error) {
if _, err := vm.Eval(fmt.Sprintf("price = %f", price)); err != nil {
return 0, err
}
res, err := vm.Execute(prg)
if err != nil {
return 0, err
}
if !res.CanFloat() {
return 0, errors.New("formula did not return a float value")
}
return res.Float(), nil
}
// test the formula
_, err = t.calc(0)
return err
}
func (t *embed) totalPrice(price float64) float64 {
return (price*(1+t.Margin)+t.Charges)*(1+t.Tax) + t.Uplifts
if t.calc != nil {
res, _ := t.calc(price)
return res
}
return (price + t.Charges) * (1 + t.Tax)
}

View file

@ -42,6 +42,10 @@ func NewEnerginetFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, errors.New("missing region")
}
if err := cc.init(); err != nil {
return nil, err
}
t := &Energinet{
embed: &cc.embed,
log: util.NewLogger("energinet"),

View file

@ -51,6 +51,10 @@ func NewEntsoeFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, errors.New("missing domain")
}
if err := cc.init(); err != nil {
return nil, err
}
domain, err := entsoe.Area(entsoe.BZN, strings.ToUpper(cc.Domain))
if err != nil {
return nil, err

View file

@ -55,6 +55,10 @@ func NewPunFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, err
}
if err := cc.init(); err != nil {
return nil, err
}
t := &Pun{
log: util.NewLogger("pun"),
embed: &cc,

View file

@ -33,6 +33,10 @@ func NewSmartEnergyFromConfig(other map[string]interface{}) (api.Tariff, error)
return nil, err
}
if err := cc.init(); err != nil {
return nil, err
}
t := &SmartEnergy{
embed: &cc.embed,
log: util.NewLogger("smartenergy"),

View file

@ -46,6 +46,10 @@ func NewConfigurableFromConfig(ctx context.Context, other map[string]interface{}
return nil, fmt.Errorf("must have either price or forecast")
}
if err := cc.init(); err != nil {
return nil, err
}
var (
err error
priceG func() (float64, error)

View file

@ -44,6 +44,10 @@ func NewTibberFromConfig(other map[string]interface{}) (api.Tariff, error) {
return nil, errors.New("missing token")
}
if err := cc.init(); err != nil {
return nil, err
}
log := util.NewLogger("tibber").Redact(cc.Token, cc.HomeID)
t := &Tibber{

View file

@ -333,6 +333,12 @@ presets:
help:
de: Zusätzlicher prozentualer Aufschlag (z.B. 0.2 für 20%)
en: Additional percentage charge (e.g. 0.2 for 20%)
- name: formula
type: string
help:
de: Individuelle Formel zur Berechnung des Preises
en: Individual formula for calculating the price
example: "math.Max((price + charges) * (1 + tax), 0.0)"
eebus:
params:
- name: ski

View file

@ -5,4 +5,7 @@ charges: {{ .charges }}
{{- if .tax }}
tax: {{ .tax }}
{{- end }}
{{- if .formula }}
formula: {{ .formula }}
{{- end }}
{{- end }}