From eab09df27d6b112647b7378347a3cdfe7f5ba776 Mon Sep 17 00:00:00 2001 From: Ted Pearson <1477390+tedpearson@users.noreply.github.com> Date: Sat, 2 Mar 2024 19:13:02 -0500 Subject: [PATCH] Document methods. --- internal/app/api.go | 6 ++++++ internal/app/main.go | 7 +++++++ internal/app/metrics.go | 2 ++ internal/app/parser.go | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/internal/app/api.go b/internal/app/api.go index 13dc722..b1dedc5 100644 --- a/internal/app/api.go +++ b/internal/app/api.go @@ -12,10 +12,12 @@ import ( "time" ) +// OAuth holds a json web token parsed from the auth response. type OAuth struct { AuthorizationToken string `json:"authorizationToken"` } +// Auth authenticates with the api and returns a json web token for use with the api. func Auth(config UtilityConfig) (string, error) { client := &http.Client{} postData := fmt.Sprintf("userId=%s&password=%s", config.Username, config.Password) @@ -43,6 +45,7 @@ func Auth(config UtilityConfig) (string, error) { return oauth.AuthorizationToken, nil } +// PollRequest is request information sent to the api to fetch data. type PollRequest struct { TimeFrame string `json:"timeFrame"` UserId string `json:"userId"` @@ -55,6 +58,9 @@ type PollRequest struct { EndDateTime int64 `json:"endDateTime"` } +// FetchData calls the api to get data for a particular time period. +// Note that the api may return a PENDING status or actual data. +// However, parsing of the response is handled in ParseReader. func FetchData(start, end time.Time, config UtilityConfig, jwt string) (io.ReadCloser, error) { client := http.Client{} pollRequest := PollRequest{ diff --git a/internal/app/main.go b/internal/app/main.go index b3e76cc..b14fe04 100644 --- a/internal/app/main.go +++ b/internal/app/main.go @@ -12,6 +12,7 @@ import ( "gopkg.in/yaml.v3" ) +// InfluxConfig is the config for the VictoriaMetrics connection, via the influxdb client. type InfluxConfig struct { Host string `yaml:"host"` User string `yaml:"user"` @@ -20,6 +21,10 @@ type InfluxConfig struct { Insecure bool `yaml:"insecure"` } +// UtilityConfig is the config for Novec. +// Password is hashed or encrypted in some unknown way, and must be retrieved from your browser. (TBD) +// Account is your account number, available on your bill. +// ServiceLocation appears to be an internal number, and must be retrieved from your browser. (TBD) type UtilityConfig struct { ApiUrl string `yaml:"api_url"` Username string `yaml:"username"` @@ -28,12 +33,14 @@ type UtilityConfig struct { ServiceLocation string `yaml:"service_location"` } +// Config is the config format for electric-usage-downloader type Config struct { ExtractDays int `yaml:"extract_days"` Utility UtilityConfig `yaml:"utility"` InfluxDB InfluxConfig `yaml:"influxdb"` } +// Main runs the program. func Main() error { configFlag := flag.String("config", "config.yaml", "Config file") startFlag := flag.String("start", "", "Start date of period to extract from electric co.") diff --git a/internal/app/metrics.go b/internal/app/metrics.go index 3ce6a6c..eaa7ea8 100644 --- a/internal/app/metrics.go +++ b/internal/app/metrics.go @@ -9,6 +9,8 @@ import ( "github.com/influxdata/influxdb-client-go/v2/api/write" ) +// WriteMetrics writes ElectricUsage data to victoriametrics. +// This method writes a point every minute instead of following the time span of ElectricUsage. func WriteMetrics(records []ElectricUsage, config InfluxConfig) error { opts := influxdb2.DefaultOptions() if config.Insecure { diff --git a/internal/app/parser.go b/internal/app/parser.go index b4fee69..b975ef4 100644 --- a/internal/app/parser.go +++ b/internal/app/parser.go @@ -9,6 +9,7 @@ import ( "time" ) +// ElectricUsage contains usage and cost information for a defined period of time. type ElectricUsage struct { StartTime time.Time EndTime time.Time @@ -16,37 +17,54 @@ type ElectricUsage struct { CostInCents int64 } +// Response holds the parsed response from the Novec poll api. +// If Status is "PENDING", this means we need to make the same request again +// as data is still being prepared. type Response struct { Status string `json:"status"` Data map[string][]NovecData `json:"data"` } +// NovecData holds parsed response data from the Novec poll api. +// It holds the Type of data ("USAGE" or "COST"), and the Series type NovecData struct { Type string `json:"type"` Series []NovecSeries `json:"series"` } +// NovecSeries holds parsed response data from the Novec poll api. +// It holds a list of NovecPoints. type NovecSeries struct { Data []NovecPoint `json:"data"` } +// NovecPoint holds parsed response data from the Novec poll api. +// It holds a timestamp, UnixMillis, which is actually in the America/New_York timezone instead of +// UTC as it should be. +// It also holds the Value of the point in dollars or kWh. type NovecPoint struct { UnixMillis int64 `json:"x"` Value float64 `json:"y"` } +// RetryableError is an error that indicates to the retryer in Main that another +// poll request to FetchData should be made. type RetryableError struct { Msg string } +// NewRetryableError creates a RetryableError func NewRetryableError(msg string) *RetryableError { return &RetryableError{Msg: msg} } +// Error implements type error for RetryableError. func (t *RetryableError) Error() string { return t.Msg } +// ParseReader parses the json response received in FetchData from the Novec poll api. +// It can return a normal error, a RetryableError, or parsed ElectricUsage. func ParseReader(reader io.ReadCloser) ([]ElectricUsage, error) { defer func() { if err := reader.Close(); err != nil {