Code refactoring + made anomaly detection system more stable.
Some checks failed
Docker / docker (push) Has been cancelled
Tests / build (push) Has been cancelled
Some checks failed
Docker / docker (push) Has been cancelled
Tests / build (push) Has been cancelled
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package types
|
||||
|
||||
// Variables type, representing values read from environment variables
|
||||
type Variables struct {
|
||||
Token string
|
||||
TimeToLive int8
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
124
types/zephyr.go
Normal file
124
types/zephyr.go
Normal file
@@ -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"`
|
||||
}
|
||||
Reference in New Issue
Block a user