diff --git a/cache/master.go b/cache/master.go new file mode 100644 index 0000000..938be02 --- /dev/null +++ b/cache/master.go @@ -0,0 +1,80 @@ +package cache + +import ( + "strings" + "sync" + "time" + + "github.com/ceticamarco/zephyr/types" +) + +// cacheType, representing the abstract value of a CacheEntity +type cacheType interface { + types.Weather | types.Metrics | types.Wind | types.DailyForecast | types.HourlyForecast | types.Moon +} + +// CacheEntity, representing the value of the cache +type CacheEntity[T cacheType] struct { + element T + timestamp time.Time +} + +// MasterCache, representing a mapping between a key(str) and a CacheEntity +type MasterCache[T cacheType] struct { + mu sync.RWMutex + Data map[string]CacheEntity[T] +} + +// MasterCaches, representing a grouping of the various caches +type MasterCaches struct { + WeatherCache MasterCache[types.Weather] + MetricsCache MasterCache[types.Metrics] + WindCache MasterCache[types.Wind] + DailyForecastCache MasterCache[types.DailyForecast] + HourlyForecastCache MasterCache[types.HourlyForecast] + MoonCache MasterCache[types.Moon] +} + +func InitMasterCache() *MasterCaches { + return &MasterCaches{ + WeatherCache: MasterCache[types.Weather]{Data: make(map[string]CacheEntity[types.Weather])}, + MetricsCache: MasterCache[types.Metrics]{Data: make(map[string]CacheEntity[types.Metrics])}, + WindCache: MasterCache[types.Wind]{Data: make(map[string]CacheEntity[types.Wind])}, + DailyForecastCache: MasterCache[types.DailyForecast]{Data: make(map[string]CacheEntity[types.DailyForecast])}, + HourlyForecastCache: MasterCache[types.HourlyForecast]{Data: make(map[string]CacheEntity[types.HourlyForecast])}, + MoonCache: MasterCache[types.Moon]{Data: make(map[string]CacheEntity[types.Moon])}, + } +} + +func (cache *MasterCache[T]) GetEntry(cityName string, ttl int8) (T, bool) { + cache.mu.RLock() + defer cache.mu.RUnlock() + + val, isPresent := cache.Data[strings.ToUpper(cityName)] + + // If key is not present, return a zero value + if !isPresent { + return val.element, false + } + + // Otherwise check whether cache element is expired + currentTime := time.Now() + expired := currentTime.Sub(val.timestamp) > (time.Duration(ttl) * time.Hour) + if expired { + return val.element, false + } + + return val.element, true +} + +func (cache *MasterCache[T]) AddEntry(entry T, cityName string) { + cache.mu.Lock() + defer cache.mu.Unlock() + + currentTime := time.Now() + + cache.Data[strings.ToUpper(cityName)] = CacheEntity[T]{ + element: entry, + timestamp: currentTime, + } +} diff --git a/cache/statistics.go b/cache/statistics.go new file mode 100644 index 0000000..789866f --- /dev/null +++ b/cache/statistics.go @@ -0,0 +1,93 @@ +package cache + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/ceticamarco/zephyr/types" +) + +// statistic cache data type, representing a mapping between a location+date and its daily average temperature +type StatCache struct { + mu sync.RWMutex + db map[string]float64 +} + +func InitStatCache() *StatCache { + return &StatCache{ + db: make(map[string]float64), + } +} + +func (cache *StatCache) AddStatistic(cityName string, statDate string, dailyTemp float64) { + cache.mu.Lock() + defer cache.mu.Unlock() + + // Format key as '@ + key := fmt.Sprintf("%s@%s", statDate, cityName) + + // Insert weather statistic into the database if it doesn't already exist + if _, exists := cache.db[key]; exists { + return + } + + cache.db[key] = dailyTemp +} + +func (cache *StatCache) IsKeyInvalid(key string) bool { + cache.mu.RLock() + defer cache.mu.RUnlock() + + // A key is invalid if it has less than 2 entries within the last 2 days + threshold := time.Now().AddDate(0, 0, -2) + + var validEntries uint = 0 + for storedKey := range cache.db { + if !strings.HasSuffix(storedKey, key) { + continue + } + + // Get from @ + keyDate, err := time.Parse("2006-01-02", strings.Split(storedKey, "@")[0]) + if err != nil { + keyDate = time.Now() // Add a fallback date if parsing fails + } + + if !keyDate.Before(threshold) { + validEntries++ + + // Early skip if we already found two valid entries + if validEntries >= 2 { + return false + } + } + } + + return true +} + +func (cache *StatCache) GetCityStatistics(cityName string) []types.StatElement { + cache.mu.RLock() + defer cache.mu.RUnlock() + + result := make([]types.StatElement, 0) + + for key, record := range cache.db { + if strings.HasSuffix(key, cityName) { + // Get from @ + keyDate, err := time.Parse("2006-01-02", strings.Split(key, "@")[0]) + if err != nil { + keyDate = time.Now() // Add a fallback date if parsing fails + } + + result = append(result, types.StatElement{ + Temperature: record, + Date: keyDate, + }) + } + } + + return result +} diff --git a/controller/controller.go b/controller/controller.go index 70feeb2..897acac 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -4,10 +4,13 @@ import ( "encoding/json" "fmt" "math" + "math/rand" "net/http" "strconv" "strings" + "time" + "github.com/ceticamarco/zephyr/cache" "github.com/ceticamarco/zephyr/model" "github.com/ceticamarco/zephyr/types" ) @@ -102,7 +105,7 @@ func deepCopyForecast[T types.DailyForecast | types.HourlyForecast](original T) return fc_copy } -func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[types.Weather], statDB *types.StatDB, vars *types.Variables) { +func GetWeather(res http.ResponseWriter, req *http.Request, cache *cache.MasterCache[types.Weather], statCache *cache.StatCache, vars *types.Variables) { if req.Method != http.MethodGet { jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed) return @@ -138,7 +141,7 @@ func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[t } // Get city weather - weather, err := model.GetWeather(&city, vars.Token) + weather, dailyTemp, err := model.GetWeather(&city, vars.Token) if err != nil { jsonError(res, "error", err.Error(), http.StatusBadRequest) return @@ -148,7 +151,8 @@ func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[t cache.AddEntry(weather, fmtKey(cityName)) // Insert new statistic entry into the statistics database - statDB.AddStatistic(fmtKey(cityName), weather) + currentDate := time.Now().Format("2006-01-02") + statCache.AddStatistic(fmtKey(cityName), currentDate, dailyTemp) // Format weather object and then return it weather.Temperature = fmtTemperature(weather.Temperature, isImperial) @@ -160,7 +164,7 @@ func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[t } } -func GetMetrics(res http.ResponseWriter, req *http.Request, cache *types.Cache[types.Metrics], vars *types.Variables) { +func GetMetrics(res http.ResponseWriter, req *http.Request, cache *cache.MasterCache[types.Metrics], vars *types.Variables) { if req.Method != http.MethodGet { jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed) return @@ -215,7 +219,7 @@ func GetMetrics(res http.ResponseWriter, req *http.Request, cache *types.Cache[t } } -func GetWind(res http.ResponseWriter, req *http.Request, cache *types.Cache[types.Wind], vars *types.Variables) { +func GetWind(res http.ResponseWriter, req *http.Request, cache *cache.MasterCache[types.Wind], vars *types.Variables) { if req.Method != http.MethodGet { jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed) return @@ -267,8 +271,8 @@ func GetWind(res http.ResponseWriter, req *http.Request, cache *types.Cache[type func GetForecast( res http.ResponseWriter, req *http.Request, - dCache *types.Cache[types.DailyForecast], - hCache *types.Cache[types.HourlyForecast], + dCache *cache.MasterCache[types.DailyForecast], + hCache *cache.MasterCache[types.HourlyForecast], vars *types.Variables, ) { if req.Method != http.MethodGet { @@ -340,7 +344,7 @@ func GetForecast( } } -func GetMoon(res http.ResponseWriter, req *http.Request, cache *types.Cache[types.Moon], vars *types.Variables) { +func GetMoon(res http.ResponseWriter, req *http.Request, cache *cache.MasterCache[types.Moon], vars *types.Variables) { if req.Method != http.MethodGet { jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed) return @@ -370,7 +374,23 @@ func GetMoon(res http.ResponseWriter, req *http.Request, cache *types.Cache[type } } -func GetStatistics(res http.ResponseWriter, req *http.Request, statDB *types.StatDB) { +func addRandomStatistics(statDB *cache.StatCache, city string, n int, meanTemp, stdDev float64) { + now := time.Now().AddDate(0, 0, -1) // Start from yesterday + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + for i := 0; i < n; i++ { + date := now.AddDate(0, 0, -i) + temp := r.NormFloat64()*stdDev + meanTemp + + statDB.AddStatistic( + fmtKey(city), + date.Format("2006-01-02"), + temp, + ) + } +} + +func GetStatistics(res http.ResponseWriter, req *http.Request, statCache *cache.StatCache) { if req.Method != http.MethodGet { jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed) return @@ -389,7 +409,7 @@ func GetStatistics(res http.ResponseWriter, req *http.Request, statDB *types.Sta isImperial := req.URL.Query().Has("i") // Get city statistics - stats, err := model.GetStatistics(fmtKey(cityName), statDB) + stats, err := model.GetStatistics(fmtKey(cityName), statCache) if err != nil { jsonError(res, "error", err.Error(), http.StatusBadRequest) return diff --git a/main.go b/main.go index cf1059c..6b6e3f3 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "os" "strconv" + "github.com/ceticamarco/zephyr/cache" "github.com/ceticamarco/zephyr/controller" "github.com/ceticamarco/zephyr/types" ) @@ -25,8 +26,8 @@ func main() { } // Initialize cache, statDB and vars - cache := types.InitCache() - statDB := types.InitDB() + masterCache := cache.InitMasterCache() + statCache := cache.InitStatCache() vars := types.Variables{ Token: token, TimeToLive: int8(ttl), @@ -34,27 +35,27 @@ func main() { // API endpoints http.HandleFunc("/weather/", func(res http.ResponseWriter, req *http.Request) { - controller.GetWeather(res, req, &cache.WeatherCache, statDB, &vars) + controller.GetWeather(res, req, &masterCache.WeatherCache, statCache, &vars) }) http.HandleFunc("/metrics/", func(res http.ResponseWriter, req *http.Request) { - controller.GetMetrics(res, req, &cache.MetricsCache, &vars) + controller.GetMetrics(res, req, &masterCache.MetricsCache, &vars) }) http.HandleFunc("/wind/", func(res http.ResponseWriter, req *http.Request) { - controller.GetWind(res, req, &cache.WindCache, &vars) + controller.GetWind(res, req, &masterCache.WindCache, &vars) }) http.HandleFunc("/forecast/", func(res http.ResponseWriter, req *http.Request) { - controller.GetForecast(res, req, &cache.DailyForecastCache, &cache.HourlyForecastCache, &vars) + controller.GetForecast(res, req, &masterCache.DailyForecastCache, &masterCache.HourlyForecastCache, &vars) }) http.HandleFunc("/moon", func(res http.ResponseWriter, req *http.Request) { - controller.GetMoon(res, req, &cache.MoonCache, &vars) + controller.GetMoon(res, req, &masterCache.MoonCache, &vars) }) http.HandleFunc("/stats/", func(res http.ResponseWriter, req *http.Request) { - controller.GetStatistics(res, req, statDB) + controller.GetStatistics(res, req, statCache) }) listenAddr := fmt.Sprintf("%s:%s", host, port) diff --git a/model/statisticsModel.go b/model/statisticsModel.go index ca248ad..d3a2a97 100644 --- a/model/statisticsModel.go +++ b/model/statisticsModel.go @@ -5,37 +5,23 @@ import ( "slices" "strconv" + "github.com/ceticamarco/zephyr/cache" "github.com/ceticamarco/zephyr/statistics" "github.com/ceticamarco/zephyr/types" ) -func GetStatistics(cityName string, statDB *types.StatDB) (types.StatResult, error) { +func GetStatistics(cityName string, statCache *cache.StatCache) (types.StatResult, error) { // Check whether there are sufficient and updated records for the given location - if statDB.IsKeyInvalid(cityName) { + if statCache.IsKeyInvalid(cityName) { return types.StatResult{}, errors.New("insufficient or outdated data to perform statistical analysis") } - extractTemps := func(weatherArr []types.Weather) ([]float64, error) { - temps := make([]float64, 0, len(weatherArr)) - - for _, weather := range weatherArr { - temperature, err := strconv.ParseFloat(weather.Temperature, 64) - if err != nil { - return nil, err - } - temps = append(temps, temperature) - } - - return temps, nil - } - // Extract records from the database - stats := statDB.GetCityStatistics(cityName) - - // Extract temperatures from weather statistics - temps, err := extractTemps(stats) - if err != nil { - return types.StatResult{}, err + stats := statCache.GetCityStatistics(cityName) + // Extract temperatures from statistics + temps := make([]float64, len(stats)) + for idx, stat := range stats { + temps[idx] = stat.Temperature } // Detect anomalies diff --git a/model/weatherModel.go b/model/weatherModel.go index a282132..3befd6c 100644 --- a/model/weatherModel.go +++ b/model/weatherModel.go @@ -42,10 +42,10 @@ func GetEmoji(condition string, isNight bool) string { return "❓" } -func GetWeather(city *types.City, apiKey string) (types.Weather, error) { +func GetWeather(city *types.City, apiKey string) (types.Weather, float64, error) { url, err := url.Parse(WTR_URL) if err != nil { - return types.Weather{}, err + return types.Weather{}, 0, err } params := url.Query() @@ -59,7 +59,7 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { res, err := http.Get(url.String()) if err != nil { - return types.Weather{}, err + return types.Weather{}, 0, err } defer res.Body.Close() @@ -77,8 +77,9 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { } `json:"current"` Daily []struct { Temp struct { - Min float64 `json:"min"` - Max float64 `json:"max"` + Daily float64 `json:"day"` + Min float64 `json:"min"` + Max float64 `json:"max"` } `json:"temp"` } `json:"daily"` Alerts []struct { @@ -91,7 +92,7 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { var weather WeatherRes if err := json.NewDecoder(res.Body).Decode(&weather); err != nil { - return types.Weather{}, err + return types.Weather{}, 0, err } // Format UNIX timestamp as 'YYYY-MM-DD' @@ -143,5 +144,5 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { Condition: weather.Current.Weather[0].Title, Emoji: emoji, Alerts: alerts, - }, nil + }, weather.Daily[0].Temp.Daily, nil } diff --git a/statistics/primitives.go b/statistics/primitives.go index 4c59e82..b718ec5 100644 --- a/statistics/primitives.go +++ b/statistics/primitives.go @@ -147,12 +147,10 @@ func RobustZScore(temperatures []float64) []struct { return anomalies } -func DetectAnomalies(weatherArr []types.Weather) []types.WeatherAnomaly { - temps := make([]float64, len(weatherArr)) - - for idx, weather := range weatherArr { - temp, _ := strconv.ParseFloat(weather.Temperature, 64) - temps[idx] = temp +func DetectAnomalies(statsArr []types.StatElement) []types.WeatherAnomaly { + temps := make([]float64, len(statsArr)) + for idx, stat := range statsArr { + temps[idx] = stat.Temperature } // Apply the Robust/MAD Z-Score anomaly detection algorithm @@ -160,7 +158,7 @@ func DetectAnomalies(weatherArr []types.Weather) []types.WeatherAnomaly { result := make([]types.WeatherAnomaly, 0, len(anomalies)) for _, anomaly := range anomalies { result = append(result, types.WeatherAnomaly{ - Date: weatherArr[anomaly.Idx].Date, + Date: types.ZephyrDate{Date: statsArr[anomaly.Idx].Date}, Temp: strconv.FormatFloat(anomaly.Value, 'f', -1, 64), }) } diff --git a/types/cache.go b/types/cache.go deleted file mode 100644 index fb0392f..0000000 --- a/types/cache.go +++ /dev/null @@ -1,78 +0,0 @@ -package types - -import ( - "strings" - "sync" - "time" -) - -// cacheType, representing the abstract value of a CacheEntity -type cacheType interface { - Weather | Metrics | Wind | DailyForecast | HourlyForecast | Moon -} - -// CacheEntity, representing the value of the cache -type CacheEntity[T cacheType] struct { - element T - timestamp time.Time -} - -// Cache, representing a mapping between a key(str) and a CacheEntity -type Cache[T cacheType] struct { - mu sync.RWMutex - Data map[string]CacheEntity[T] -} - -// Caches, representing a grouping of the various caches -type Caches struct { - WeatherCache Cache[Weather] - MetricsCache Cache[Metrics] - WindCache Cache[Wind] - DailyForecastCache Cache[DailyForecast] - HourlyForecastCache Cache[HourlyForecast] - MoonCache Cache[Moon] -} - -func InitCache() *Caches { - return &Caches{ - WeatherCache: Cache[Weather]{Data: make(map[string]CacheEntity[Weather])}, - MetricsCache: Cache[Metrics]{Data: make(map[string]CacheEntity[Metrics])}, - WindCache: Cache[Wind]{Data: make(map[string]CacheEntity[Wind])}, - DailyForecastCache: Cache[DailyForecast]{Data: make(map[string]CacheEntity[DailyForecast])}, - HourlyForecastCache: Cache[HourlyForecast]{Data: make(map[string]CacheEntity[HourlyForecast])}, - MoonCache: Cache[Moon]{Data: make(map[string]CacheEntity[Moon])}, - } -} - -func (cache *Cache[T]) GetEntry(cityName string, ttl int8) (T, bool) { - cache.mu.RLock() - defer cache.mu.RUnlock() - - val, isPresent := cache.Data[strings.ToUpper(cityName)] - - // If key is not present, return a zero value - if !isPresent { - return val.element, false - } - - // Otherwise check whether cache element is expired - currentTime := time.Now() - expired := currentTime.Sub(val.timestamp) > (time.Duration(ttl) * time.Hour) - if expired { - return val.element, false - } - - return val.element, true -} - -func (cache *Cache[T]) AddEntry(entry T, cityName string) { - cache.mu.Lock() - defer cache.mu.Unlock() - - currentTime := time.Now() - - cache.Data[strings.ToUpper(cityName)] = CacheEntity[T]{ - element: entry, - timestamp: currentTime, - } -} diff --git a/types/city.go b/types/city.go deleted file mode 100644 index ff2c36e..0000000 --- a/types/city.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -// The City data type, representing the name, the latitude and the longitude -// of a location -type City struct { - Name string `json:"name"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` -} diff --git a/types/forecast.go b/types/forecast.go deleted file mode 100644 index 68e6a4d..0000000 --- a/types/forecast.go +++ /dev/null @@ -1,35 +0,0 @@ -package types - -// The DailyForecastEntity data type, representing the weather forecast -// of a single day -type DailyForecastEntity struct { - Date ZephyrDate `json:"date"` - Min string `json:"min"` - Max string `json:"max"` - Condition string `json:"condition"` - Emoji string `json:"emoji"` - FeelsLike string `json:"feelsLike"` - Wind Wind `json:"wind"` - RainProb string `json:"rainProbability"` -} - -// The DailyForecast data type, representing a set of DailyForecastEntity -type DailyForecast struct { - Forecast []DailyForecastEntity `json:"forecast"` -} - -// The HourlyForecastEntity data type, representing the weather forecast -// of a single hour -type HourlyForecastEntity struct { - Time ZephyrTime `json:"time"` - Temperature string `json:"temperature"` - Condition string `json:"condition"` - Emoji string `json:"emoji"` - Wind Wind `json:"wind"` - RainProb string `json:"rainProbability"` -} - -// The HourlyForecast data type, representing a set of HourlyForecastEntity -type HourlyForecast struct { - Forecast []HourlyForecastEntity `json:"forecast"` -} diff --git a/types/metrics.go b/types/metrics.go deleted file mode 100644 index f16406b..0000000 --- a/types/metrics.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -// The Metrics data type, representing the humidity, pressure and -// similar miscellaneous values -type Metrics struct { - Humidity string `json:"humidity"` - Pressure string `json:"pressure"` - DewPoint string `json:"dewPoint"` - UvIndex string `json:"uvIndex"` - Visibility string `json:"visibility"` -} diff --git a/types/moon.go b/types/moon.go deleted file mode 100644 index 6f7ee9c..0000000 --- a/types/moon.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -// The Moon data type, representing the moon phase, -// the moon phase icon and the moon progress(%). -type Moon struct { - Icon string `json:"icon"` - Phase string `json:"phase"` - Percentage string `json:"percentage"` -} diff --git a/types/statDB.go b/types/statDB.go deleted file mode 100644 index 9b69ee7..0000000 --- a/types/statDB.go +++ /dev/null @@ -1,75 +0,0 @@ -package types - -import ( - "fmt" - "strings" - "sync" - "time" -) - -// StatDB data type, representing a mapping between a location and its weather -type StatDB struct { - mu sync.RWMutex - db map[string]Weather -} - -func InitDB() *StatDB { - return &StatDB{ - db: make(map[string]Weather), - } -} - -func (statDB *StatDB) AddStatistic(cityName string, weather Weather) { - statDB.mu.Lock() - defer statDB.mu.Unlock() - - key := fmt.Sprintf("%s@%s", weather.Date.Date.Format("2006-01-02"), cityName) - - // Insert weather statistic into the database only if it isn't present - if _, isPresent := statDB.db[key]; isPresent { - return - } - - statDB.db[key] = weather -} - -func (statDB *StatDB) IsKeyInvalid(key string) bool { - statDB.mu.RLock() - defer statDB.mu.RUnlock() - - // A key is invalid if it has less than 2 entries within the last 2 days - threshold := time.Now().AddDate(0, 0, -2) - - var validKeys uint = 0 - for storedKey, record := range statDB.db { - if !strings.HasSuffix(storedKey, key) { - continue - } - - if !record.Date.Date.Before(threshold) { - validKeys++ - - // Early skip if we already found two valid keys - if validKeys >= 2 { - return false - } - } - } - - return true -} - -func (statDB *StatDB) GetCityStatistics(cityName string) []Weather { - statDB.mu.RLock() - defer statDB.mu.RUnlock() - - result := make([]Weather, 0) - - for key, record := range statDB.db { - if strings.HasSuffix(key, cityName) { - result = append(result, record) - } - } - - return result -} diff --git a/types/statistics.go b/types/statistics.go deleted file mode 100644 index d88bbd8..0000000 --- a/types/statistics.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -// The WeatherAnomaly data type, representing -// skewed meteorological events -type WeatherAnomaly struct { - Date ZephyrDate `json:"date"` - Temp string `json:"temperature"` -} - -// The StatResult data type, representing weather statistics -// of past meteorological events -type StatResult struct { - Min string `json:"min"` - Max string `json:"max"` - Count int `json:"count"` - Mean string `json:"mean"` - StdDev string `json:"stdDev"` - Median string `json:"median"` - Mode string `json:"mode"` - Anomaly *[]WeatherAnomaly `json:"anomaly"` -} diff --git a/types/variables.go b/types/variables.go deleted file mode 100644 index d6ab9ed..0000000 --- a/types/variables.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -// Variables type, representing values read from environment variables -type Variables struct { - Token string - TimeToLive int8 -} diff --git a/types/weather.go b/types/weather.go deleted file mode 100644 index 8f59035..0000000 --- a/types/weather.go +++ /dev/null @@ -1,22 +0,0 @@ -package types - -// The WeatherAlert data type, representing a -// weather alert -type WeatherAlert struct { - Event string `json:"event"` - Start ZephyrAlertDate `json:"startDate"` - End ZephyrAlertDate `json:"endDate"` - Description string `json:"description"` -} - -// The Weather data type, representing the weather of a certain location -type Weather struct { - Date ZephyrDate `json:"date"` - Temperature string `json:"temperature"` - Min string `json:"min"` - Max string `json:"max"` - Condition string `json:"condition"` - FeelsLike string `json:"feelsLike"` - Emoji string `json:"emoji"` - Alerts []WeatherAlert `json:"alerts"` -} diff --git a/types/wind.go b/types/wind.go deleted file mode 100644 index cf3af73..0000000 --- a/types/wind.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -// The Wind data type, representing the wind of a certain location -type Wind struct { - Arrow string `json:"arrow"` - Direction string `json:"direction"` - Speed string `json:"speed"` -} diff --git a/types/zephyr.go b/types/zephyr.go new file mode 100644 index 0000000..91d127d --- /dev/null +++ b/types/zephyr.go @@ -0,0 +1,124 @@ +package types + +import "time" + +// Variables type, representing values read from environment variables +type Variables struct { + Token string + TimeToLive int8 +} + +// The City data type, representing the name, the latitude and the longitude +// of a location +type City struct { + Name string `json:"name"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` +} + +// The DailyForecastEntity data type, representing the weather forecast +// of a single day +type DailyForecastEntity struct { + Date ZephyrDate `json:"date"` + Min string `json:"min"` + Max string `json:"max"` + Condition string `json:"condition"` + Emoji string `json:"emoji"` + FeelsLike string `json:"feelsLike"` + Wind Wind `json:"wind"` + RainProb string `json:"rainProbability"` +} + +// The DailyForecast data type, representing a set of DailyForecastEntity +type DailyForecast struct { + Forecast []DailyForecastEntity `json:"forecast"` +} + +// The HourlyForecastEntity data type, representing the weather forecast +// of a single hour +type HourlyForecastEntity struct { + Time ZephyrTime `json:"time"` + Temperature string `json:"temperature"` + Condition string `json:"condition"` + Emoji string `json:"emoji"` + Wind Wind `json:"wind"` + RainProb string `json:"rainProbability"` +} + +// The HourlyForecast data type, representing a set of HourlyForecastEntity +type HourlyForecast struct { + Forecast []HourlyForecastEntity `json:"forecast"` +} + +// The Metrics data type, representing the humidity, pressure and +// similar miscellaneous values +type Metrics struct { + Humidity string `json:"humidity"` + Pressure string `json:"pressure"` + DewPoint string `json:"dewPoint"` + UvIndex string `json:"uvIndex"` + Visibility string `json:"visibility"` +} + +// The Moon data type, representing the moon phase, +// the moon phase icon and the moon progress(%). +type Moon struct { + Icon string `json:"icon"` + Phase string `json:"phase"` + Percentage string `json:"percentage"` +} + +// The WeatherAnomaly data type, representing +// skewed meteorological events +type WeatherAnomaly struct { + Date ZephyrDate `json:"date"` + Temp string `json:"temperature"` +} + +// The StateElement data type, representing a statistical record +// This type is for internal usage +type StatElement struct { + Temperature float64 + Date time.Time +} + +// The StatResult data type, representing weather statistics +// of past meteorological events +type StatResult struct { + Min string `json:"min"` + Max string `json:"max"` + Count int `json:"count"` + Mean string `json:"mean"` + StdDev string `json:"stdDev"` + Median string `json:"median"` + Mode string `json:"mode"` + Anomaly *[]WeatherAnomaly `json:"anomaly"` +} + +// The WeatherAlert data type, representing a +// weather alert +type WeatherAlert struct { + Event string `json:"event"` + Start ZephyrAlertDate `json:"startDate"` + End ZephyrAlertDate `json:"endDate"` + Description string `json:"description"` +} + +// The Weather data type, representing the weather of a certain location +type Weather struct { + Date ZephyrDate `json:"date"` + Temperature string `json:"temperature"` + Min string `json:"min"` + Max string `json:"max"` + Condition string `json:"condition"` + FeelsLike string `json:"feelsLike"` + Emoji string `json:"emoji"` + Alerts []WeatherAlert `json:"alerts"` +} + +// The Wind data type, representing the wind of a certain location +type Wind struct { + Arrow string `json:"arrow"` + Direction string `json:"direction"` + Speed string `json:"speed"` +}