add ristretto caching and process daily and rolling data

This commit is contained in:
2025-10-30 17:15:33 +01:00
parent 6f4090fcb3
commit 145028af37
4 changed files with 136 additions and 15 deletions
@@ -2,9 +2,19 @@ package meteo
import (
"errors"
"fmt"
"time"
)
type MeteoDataFromServiceA struct {
Timestamp time.Time `csv:"fecha" json:"timestamp"`
Location string `csv:"ciudad" json:"location"`
MaxTemp float32 `csv:"temperatura maxima" json:"max_temp"`
MinTemp float32 `csv:"temperatura minima" json:"min_temp"`
Rainfall float32 `csv:"precipitacion" json:"rainfall"`
Cloudiness int `csv:"nubosidad" json:"cloudiness"`
}
type MeteoDataPerDay struct {
MaxTemp float32 `json:"max_temp"`
MinTemp float32 `json:"min_temp"`
@@ -33,6 +43,10 @@ type MeteoData struct {
Rolling7 *Rolling7Data `json:"rolling7,omitempty"`
}
func (mt *MeteoData) ComputeCacheKey() string {
return fmt.Sprintf("meteo:%s:%s", mt.Location, mt.From)
}
type Unit string
const (
+107 -15
View File
@@ -2,33 +2,42 @@ package meteo
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"sync"
"time"
"github.com/cenkalti/backoff/v5"
"github.com/dgraph-io/ristretto/v2"
)
type inMemory struct {
data any
mu *sync.RWMutex
expiry time.Time
}
type Service struct {
inMemory
cache *ristretto.Cache[string, string]
}
func NewService() *Service {
return &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) {
func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) (MeteoData, error) {
fromDate, err := time.Parse("2006-01-02", params.Date)
if err != nil {
return nil, err
return MeteoData{}, err
}
toDate := fromDate.AddDate(0, 0, params.Days-1)
@@ -36,13 +45,15 @@ func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) ([]
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
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusBadRequest {
resp.Body.Close()
return nil, backoff.Permanent(errors.New("bad request"))
}
@@ -52,11 +63,92 @@ func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) ([]
result, err := backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff()))
if err != nil {
slog.Error("somethin happened")
return []MeteoData{}, err
return MeteoData{}, err
}
// TODO add ristretto
defer result.Body.Close()
slog.Info("fetched data", "data", result)
body, err := io.ReadAll(result.Body)
if err != nil {
slog.Error("error reading response body", "err", err)
return MeteoData{}, err
}
return []MeteoData{}, nil
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
}