Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update octopus.go for german api version #18940

Closed
wants to merge 10 commits into from
156 changes: 134 additions & 22 deletions tariff/octopus.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package tariff

import (
"bytes"
"encoding/json"
"errors"
"io"

Check failure on line 7 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Build

"io" imported and not used

Check failure on line 7 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

"io" imported and not used

Check failure on line 7 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

"io" imported and not used

Check failure on line 7 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Integration

"io" imported and not used

Check failure on line 7 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Test

"io" imported and not used
"slices"
"strings"
"sync"
Expand All @@ -16,25 +19,34 @@
)

type Octopus struct {
log *util.Logger
region string
productCode string
apikey string
data *util.Monitor[api.Rates]
log *util.Logger
region string
productCode string
apikey string
token string
goPrice float64
standardPrice float64
data *util.Monitor[api.Rates]
}

var _ api.Tariff = (*Octopus)(nil)

func init() {
registry.Add("octopusenergy", NewOctopusFromConfig)
// Register the Octopus tariff
api.RegisterTariff("octopusenergy", NewOctopusFromConfig)

Check failure on line 36 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Build

undefined: api.RegisterTariff

Check failure on line 36 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: api.RegisterTariff

Check failure on line 36 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: api.RegisterTariff

Check failure on line 36 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Integration

undefined: api.RegisterTariff

Check failure on line 36 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Test

undefined: api.RegisterTariff
}

func NewOctopusFromConfig(other map[string]interface{}) (api.Tariff, error) {
var cc struct {
Region string
Tariff string // DEPRECATED: use ProductCode
ProductCode string
ApiKey string
Region string
Tariff string // DEPRECATED: use ProductCode
ProductCode string
ApiKey string
Email string
Password string
RegionType string
GoPrice float64
StandardPrice float64
}

logger := util.NewLogger("octopus")
Expand All @@ -43,8 +55,8 @@
return nil, err
}

// Allow ApiKey to be missing only if Region and Tariff are not.
if cc.ApiKey == "" {
// Allow ApiKey or Token to be missing only if Region and Tariff are not.
if cc.ApiKey == "" && (cc.Email == "" || cc.Password == "") {
if cc.Region == "" {
return nil, errors.New("missing region")
}
Expand All @@ -56,7 +68,7 @@
if cc.ProductCode == "" {
return nil, errors.New("missing product code")
}
} else {
} else if cc.ApiKey != "" {
// ApiKey validators
if cc.Region != "" || cc.Tariff != "" {
return nil, errors.New("cannot use apikey at same time as product code")
Expand All @@ -67,21 +79,32 @@
}

t := &Octopus{
log: logger,
region: cc.Region,
productCode: cc.ProductCode,
apikey: cc.ApiKey,
data: util.NewMonitor[api.Rates](2 * time.Hour),
log: logger,
region: cc.Region,
productCode: cc.ProductCode,
apikey: cc.ApiKey,
goPrice: cc.GoPrice,
standardPrice: cc.StandardPrice,
data: util.NewMonitor[api.Rates](2 * time.Hour),
}

// Get token if Email and Password are provided
if cc.Email != "" && cc.Password != "" {
token, err := octoGql.GetKrakenToken(cc.Email, cc.Password)

Check failure on line 93 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Build

undefined: octoGql.GetKrakenToken

Check failure on line 93 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: octoGql.GetKrakenToken

Check failure on line 93 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: octoGql.GetKrakenToken

Check failure on line 93 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Integration

undefined: octoGql.GetKrakenToken

Check failure on line 93 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Test

undefined: octoGql.GetKrakenToken
if err != nil {
return nil, err
}
t.token = token
}

done := make(chan error)
go t.run(done)
go t.run(cc.RegionType, done)
err := <-done

return t, err
}

func (t *Octopus) run(done chan error) {
func (t *Octopus) run(regionType string, done chan error) {
var once sync.Once
client := request.NewHelper(t.log)

Expand All @@ -102,6 +125,21 @@
return
}
restQueryUri = octoRest.ConstructRatesAPIFromTariffCode(tariffCode)
} else if t.token != "" && regionType == "DE" {
// Use GraphQL with token to get appropriate tariff code before entering execution loop.
gqlCli, err := octoGql.NewClientWithEmailPassword(t.log, t.token)

Check failure on line 130 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Build

undefined: octoGql.NewClientWithEmailPassword

Check failure on line 130 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: octoGql.NewClientWithEmailPassword

Check failure on line 130 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Integration

undefined: octoGql.NewClientWithEmailPassword

Check failure on line 130 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Test

undefined: octoGql.NewClientWithEmailPassword
if err != nil {
once.Do(func() { done <- err })
t.log.ERROR.Println(err)
return
}
tariffCode, err := gqlCli.GermanTariffCode()
if err != nil {
once.Do(func() { done <- err })
t.log.ERROR.Println(err)
return
}
restQueryUri = octoRest.ConstructRatesAPIFromTariffCode(tariffCode)
} else {
// Construct Rest Query URI using tariff and region codes.
restQueryUri = octoRest.ConstructRatesAPIFromProductAndRegionCode(t.productCode, t.region)
Expand All @@ -125,8 +163,8 @@
ar := api.Rate{
Start: r.ValidityStart,
End: r.ValidityEnd,
// UnitRates are supplied inclusive of tax, though this could be flipped easily with a config flag.
Price: r.PriceInclusiveTax / 1e2,
// Apply GoPrice from 0-5 am UTC or during planned dispatch periods, else use StandardPrice
Price: t.applyPrice(r.ValidityStart, r.ValidityEnd, r.PriceInclusiveTax/1e2),
}
data = append(data, ar)
}
Expand All @@ -136,6 +174,80 @@
}
}

// applyPrice applies the GoPrice or StandardPrice based on the time of day and planned dispatch periods
func (t *Octopus) applyPrice(start, end time.Time, basePrice float64) float64 {
startHour := start.UTC().Hour()
endHour := end.UTC().Hour()

// Check if the rate falls within the GoPrice period (0-5 am UTC)
if (startHour >= 0 && startHour < 5) || (endHour > 0 && endHour <= 5) {
return t.goPrice
}

// Check if the rate falls within a planned dispatch period
if t.isPlannedDispatch(start, end) {
return t.goPrice
}

// Otherwise, use the StandardPrice
return t.standardPrice
}

// isPlannedDispatch checks if the given time period overlaps with a planned dispatch period
func (t *Octopus) isPlannedDispatch(start, end time.Time) bool {
// Implement the logic to check for planned dispatch periods
// Example implementation
var dispatches []struct {
Start time.Time `json:"start"`
End time.Time `json:"end"`
}

// Fetch planned dispatch periods from the API
client := request.NewHelper(t.log)
body, err := json.Marshal(struct {
Query string `json:"query"`
Variables struct {
AccountNumber string `json:"accountNumber"`
} `json:"variables"`
}{
Query: `query getPlannedDispatches($accountNumber: String!) {
plannedDispatches(accountNumber: $accountNumber) {
start
end
}
}`,
Variables: struct {
AccountNumber string `json:"accountNumber"`
}{
AccountNumber: "your_account_number", // Replace with actual account number
},
})
if err != nil {
t.log.ERROR.Println(err)
return false
}

resp, err := client.Post(octoGql.GermanURI, "application/json", bytes.NewBuffer(body))

Check failure on line 230 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Build

undefined: octoGql.GermanURI

Check failure on line 230 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Lint

undefined: octoGql.GermanURI) (typecheck)

Check failure on line 230 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Integration

undefined: octoGql.GermanURI

Check failure on line 230 in tariff/octopus.go

View workflow job for this annotation

GitHub Actions / Test

undefined: octoGql.GermanURI
if err != nil {
t.log.ERROR.Println(err)
return false
}
defer resp.Body.Close()

if err := json.NewDecoder(resp.Body).Decode(&dispatches); err != nil {
t.log.ERROR.Println(err)
return false
}

for _, d := range dispatches {
if start.Before(d.End) && end.After(d.Start) {
return true
}
}

return false
}

// Rates implements the api.Tariff interface
func (t *Octopus) Rates() (api.Rates, error) {
var res api.Rates
Expand Down
Loading