Fixed typos in docs

This commit is contained in:
2025-06-23 05:40:20 +02:00
parent a987c0fb34
commit 82f67515e7
7 changed files with 44 additions and 40 deletions

View File

@@ -2,7 +2,7 @@ name: Docker
on:
push:
branches:
- main
- master
workflow_dispatch:
jobs:
@@ -14,4 +14,4 @@ jobs:
run: |
mv compose.yml docker-compose.yml
echo -e "version: \"2.2\"\n$(cat docker-compose.yml)" > docker-compose.yml
docker compose build
docker compose build

View File

@@ -2,7 +2,7 @@ name: Tests
on:
push:
branches:
- main
- master
workflow_dispatch:
jobs:
@@ -16,4 +16,4 @@ jobs:
with:
go-version: 'stable'
- name: "Running unit tests"
run: go test ./... -v
run: go test ./... -v

View File

@@ -23,7 +23,7 @@ any kind of internet-based project or device. I already use it on a widget
on my phone, on my terminal, on the tmux's status bar, in a couple of
smart bedside clocks I've built and as a standalone web app.
## Basic Usage
## Weather
As state before, Zephyr talks via HTTP using the JSON format. Therefore, you
can query it using any HTTP client of your choice. Below you can find some examples
using `curl`:
@@ -118,26 +118,30 @@ which yields:
{
"forecast": [
{
"condEmoji": "🌧️",
"condition": "Rain",
"date": "Tuesday, 2025/05/06",
"min": "-2°C",
"max": "6°C",
"condition": "Rain",
"emoji": "🌧️",
"feelsLike": "0°C",
"tempMax": "6°C",
"tempMin": "-2°C",
"windArrow": "↗️",
"windDirection": "SSW",
"windSpeed": "14.7 km/h"
"wind": {
"arrow": "↗️",
"direction": "SSW",
"speed": "14.7 km/h"
}
},
{
"condEmoji": "☃️",
"condition": "Snow",
"date": "Wednesday, 2025/05/07",
"min": "2°C",
"max": "9°C",
"condition": "Snow",
"emoji": "☃️",
"feelsLike": "7°C",
"tempMax": "9°C",
"tempMin": "2°C",
"windArrow": "↘️",
"windDirection": "NNW",
"windSpeed": "13.9 km/h"
"wind": {
"arrow": "↘️",
"direction": "NNW",
"speed": "13.9 km/h"
}
}
]
}
@@ -173,7 +177,7 @@ will yield
> $$
## Statistical analysis
In addition to the weather data, Zephyr also provides statistical analysis of pase
In addition to the weather data, Zephyr also provides statistical analysis of past
meteorological records. This is done through the `/stats/:city` endpoint, which
returns additional information about the weather of the previous days such as
the average temperature, the maximum and minimum temperatures, the standard deviation,
@@ -205,9 +209,7 @@ which yields:
"anomaly": null
}
```
After enough data has been collected, the service will also be able to detect
anomalies in the temperature data using a built-in statistical model.
The service is also able to detect anomalies in the temperature data using a built-in statistical model.
For instance, two temperature spikes, such as `+34°C` and `-15°C`, with a mean of `25°C` and a standard deviation of `0.2°C`,
will be flagged as outliers by the model and will be reported as such:
@@ -237,14 +239,14 @@ will be flagged as outliers by the model and will be reported as such:
The anomaly detection algorithm is based on a modified version of the
[Z-Score](https://en.wikipedia.org/wiki/Standard_score) algorithm, which uses the
[Median Absolute Deviation](https://en.wikipedia.org/wiki/Median_absolute_deviation) to measure the variability
in a given sample of quantitative data. The algorithm can be summarized as follows(let $x$ be the sample):
in a given sample of quantitative data. The algorithm can be summarized as follows(let $X$ be the sample):
$$
\tilde{x} = \text{median}({X})
$$
Compute The median absolute deviation
Compute the median absolute deviation
$$
\text{MAD} = \text{median}\{ |x_i - \tilde{x}| : \forall i = 0, \dots, n-1 \}
@@ -258,6 +260,7 @@ $$
$$
Flag $x_i$ as an outlier if:
$$
|z_i| > 4.5
$$
@@ -276,8 +279,8 @@ These constants have been fine-tuned to work well with the weather data of
a wide range of climates and to ignore daily temperature fluctuations while
still being able to detect significant anomalies.
Daily temperatures collected over a short time window(1/2 months, but not less than a few days)
// *should* be normally distributed. This algorithm only work under this assumption.
According to the Q-Q plots, daily temperatures collected over a time window of no more than 1/2 months
but no less than a week, *should* follow a normal distribution.
> [!IMPORTANT]
> The anomaly detection algorithm works under the assumption that the weather data
@@ -285,14 +288,13 @@ Daily temperatures collected over a short time window(1/2 months, but not less t
> with a very small number of samples(e.g. few days of data) or with a large
> number of samples(e.g. multi-seasonal data).
My statistical benchmarks(QQ plots) show that the algorithm works
quite well when these conditions are met, and even with real world data,
The algorithm works quite well when these conditions are met, and even with real world data,
the results were quite satisfactory. However, if it
start to produce false positives, you will need to dump the whole in-memory
database and start from scratch. I recommend to do this at every change of season.
## Embedded Cache System
To minimize the amount of requests sent to the OpenWeatherMap API, Zephyr provides an built-in,
To minimize the amount of requests sent to the OpenWeatherMap API, Zephyr provides a built-in,
in-memory cache data structure that stores fetched weather data. Each time a client requests
weather data for a given location, the service will first check if it's already available on the cache.
If it is found, the cached value will be returned, otherwise a new request will be sent to the OpenWeatherMap API
@@ -309,6 +311,7 @@ Zephyr requires the following environment variables to be set:
| Variable | Meaning |
|----------------------|----------------------------------------|
| `ZEPHYR_ADDR` | Listen address |
| `ZEPHYR_PORT` | Listen port |
| `ZEPHYR_TOKEN` | OpenWeatherMap API key |
| `ZEPHYR_CACHE_TTL` | Cache time-to-live(expressed in hours) |
@@ -342,4 +345,4 @@ You can run the unit tests by issuing the following command:
```
## License
This software is released under the GPLv3 license. You can find a copy of the license with this repository or by visiting the [following page](https://choosealicense.com/licenses/gpl-3.0/).
This software is released under the GPLv3 license. You can find a copy of the license with this repository or by visiting the [following page](https://choosealicense.com/licenses/gpl-3.0/).

View File

@@ -4,9 +4,10 @@ services:
build: .
container_name: "zephyr"
environment:
ZEPHYR_PORT: 3000 # Listen port
ZEPHYR_TOKEN: "" # OpenWeatherMap API Key
ZEPHYR_CACHE_TTL: 3 # Cache time-to-live in hour
ZEPHYR_ADDR: 127.0.0.1 # Listen address
ZEPHYR_PORT: 3000 # Listen port
ZEPHYR_TOKEN: "" # OpenWeatherMap API Key
ZEPHYR_CACHE_TTL: 3 # Cache time-to-live in hour
restart: always
volumes:
- "/etc/localtime:/etc/localtime:ro"

View File

@@ -14,12 +14,13 @@ import (
func main() {
// Retrieve listening port, API token and cache time-to-live from environment variables
var (
host = os.Getenv("ZEPHYR_ADDR")
port = os.Getenv("ZEPHYR_PORT")
token = os.Getenv("ZEPHYR_TOKEN")
ttl, _ = strconv.ParseInt(os.Getenv("ZEPHYR_CACHE_TTL"), 10, 8)
)
if port == "" || token == "" || ttl == 0 {
if host == "" || port == "" || token == "" || ttl == 0 {
log.Fatalf("Environment variables not set")
}
@@ -56,7 +57,7 @@ func main() {
controller.GetStatistics(res, req, statDB)
})
listenAddr := fmt.Sprintf(":%s", port)
listenAddr := fmt.Sprintf("%s:%s", host, port)
log.Printf("Server listening on %s", listenAddr)
http.ListenAndServe(listenAddr, nil)
}

View File

@@ -12,8 +12,7 @@ type ForecastEntity struct {
Wind Wind `json:"wind"`
}
// The Forecast data type, representing the an set
// of ForecastEntity
// The Forecast data type, representing a set of ForecastEntity
type Forecast struct {
Forecast []ForecastEntity
Forecast []ForecastEntity `json:"forecast"`
}

View File

@@ -18,7 +18,7 @@ func InitDB() *StatDB {
}
func (statDB *StatDB) AddStatistic(cityName string, weather Weather) {
key := fmt.Sprintf("%s@%s", weather.Date.Date, cityName)
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 {