155 lines
3.4 KiB
Go
155 lines
3.4 KiB
Go
package meteo
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/cenkalti/backoff/v5"
|
|
"github.com/dgraph-io/ristretto/v2"
|
|
)
|
|
|
|
type Service struct {
|
|
cache *ristretto.Cache[string, string]
|
|
}
|
|
|
|
func NewService() *Service {
|
|
cache, err := ristretto.NewCache(&ristretto.Config[string, string]{
|
|
NumCounters: 1024,
|
|
MaxCost: 1 << 30,
|
|
BufferItems: 64,
|
|
})
|
|
if err != nil {
|
|
slog.Error("cannot init cache", "err", err)
|
|
return nil
|
|
}
|
|
|
|
return &Service{
|
|
cache: cache,
|
|
}
|
|
}
|
|
|
|
func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) (MeteoData, error) {
|
|
fromDate, err := time.Parse("2006-01-02", params.Date)
|
|
if err != nil {
|
|
return MeteoData{}, err
|
|
}
|
|
toDate := fromDate.AddDate(0, 0, params.Days-1)
|
|
|
|
operation := func() (*http.Response, error) {
|
|
url := fmt.Sprintf("http://localhost:8080/data?city=%s&from=%s&to=%s",
|
|
params.Location, params.Date, toDate.Format("2006-01-02"))
|
|
|
|
slog.Info("url", "url", url)
|
|
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode == http.StatusBadRequest {
|
|
resp.Body.Close()
|
|
return nil, backoff.Permanent(errors.New("bad request"))
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
result, err := backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff()))
|
|
if err != nil {
|
|
slog.Error("somethin happened")
|
|
return MeteoData{}, err
|
|
}
|
|
defer result.Body.Close()
|
|
|
|
body, err := io.ReadAll(result.Body)
|
|
if err != nil {
|
|
slog.Error("error reading response body", "err", err)
|
|
return MeteoData{}, err
|
|
}
|
|
|
|
var serviceAResponse struct {
|
|
MeteoData []MeteoDataFromServiceA `json:"meteo_data"`
|
|
}
|
|
if err := json.Unmarshal(body, &serviceAResponse); err != nil {
|
|
slog.Error("error unmarshaling response", "err", err)
|
|
return MeteoData{}, err
|
|
}
|
|
|
|
if len(serviceAResponse.MeteoData) == 0 {
|
|
return MeteoData{}, nil
|
|
}
|
|
|
|
if params.Agg == AggDaily {
|
|
return s.processDailyData(serviceAResponse.MeteoData, params)
|
|
}
|
|
|
|
return s.processRolling7Data(serviceAResponse.MeteoData, params)
|
|
}
|
|
|
|
func (s *Service) processDailyData(data []MeteoDataFromServiceA, params GetMeteoData) (MeteoData, error) {
|
|
days := make([]MeteoDataPerDay, 0, len(data))
|
|
|
|
for _, d := range data {
|
|
avgTemp := (d.MaxTemp + d.MinTemp) / 2
|
|
day := MeteoDataPerDay{
|
|
MaxTemp: d.MaxTemp,
|
|
MinTemp: d.MinTemp,
|
|
AvgTemp: avgTemp,
|
|
Rainfall: d.Rainfall,
|
|
Cloudiness: d.Cloudiness,
|
|
}
|
|
|
|
if params.Unit == UnitF {
|
|
day.ConvertValue()
|
|
}
|
|
|
|
days = append(days, day)
|
|
}
|
|
|
|
return MeteoData{
|
|
Location: params.Location,
|
|
Unit: params.Unit,
|
|
From: params.Date,
|
|
Days: &days,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) processRolling7Data(data []MeteoDataFromServiceA, params GetMeteoData) (MeteoData, error) {
|
|
if len(data) < 7 {
|
|
return MeteoData{}, errors.New("insufficient data for rolling 7-day calculation")
|
|
}
|
|
|
|
var sumTemp, sumRainfall float32
|
|
var sumCloudiness int
|
|
|
|
for i := len(data) - 7; i < len(data); i++ {
|
|
avgTemp := (data[i].MaxTemp + data[i].MinTemp) / 2
|
|
sumTemp += avgTemp
|
|
sumRainfall += data[i].Rainfall
|
|
sumCloudiness += data[i].Cloudiness
|
|
}
|
|
|
|
rolling7 := &Rolling7Data{
|
|
AvgTemp: sumTemp / 7,
|
|
AvgCloudiness: sumCloudiness / 7,
|
|
SumRainfall: sumRainfall,
|
|
}
|
|
|
|
if params.Unit == UnitF {
|
|
rolling7.AvgTemp = rolling7.AvgTemp*9/5 + 32
|
|
}
|
|
|
|
return MeteoData{
|
|
Location: params.Location,
|
|
Unit: params.Unit,
|
|
From: params.Date,
|
|
Rolling7: rolling7,
|
|
}, nil
|
|
}
|