aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md12
-rw-r--r--README.md38
-rw-r--r--cmd/prognosis/resolve_test.go2
-rw-r--r--docs/superpowers/specs/2026-08-10-go-rewrite-design.md8
-rw-r--r--internal/cache/cache_test.go22
-rw-r--r--internal/imgw/imgw_test.go26
-rw-r--r--internal/imgw/testdata/gugik_krakow.json1
-rw-r--r--internal/openmeteo/testdata/forecast.json2
-rw-r--r--internal/openmeteo/testdata/pollen.json2
9 files changed, 67 insertions, 46 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6806130..e38faf9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
Terse, newest first. Versions are git tags.
+## 0.1.2 — 2026-08-25
+
+### Changed
+- Examples, tests and recorded fixtures now use Kraków rather than the author's
+ home village. One fixture was a GUGiK address lookup containing a house
+ number, postal code and metre-precision coordinates.
+- Documentation drift now fails `make ci`: tests assert that every flag reaches
+ `-h`, the README and the man page, that every key in the generated config is
+ documented, and that every column name is explained in both.
+- The README's example output is generated from the binary rather than written
+ by hand, and its flag list is complete.
+
## 0.1.1 — 2026-08-25
### Added
diff --git a/README.md b/README.md
index d4ac8d7..50ce8fd 100644
--- a/README.md
+++ b/README.md
@@ -11,26 +11,28 @@ No API key. Written in Go with **no third-party modules** — `make ci` fails if
one creeps in.
```
-Krakow, PL Tue 25 Aug 05:37 up 19:32 down GMT+2
- day 7-21° dry sun 5h09m of 13h54m daylight
- pollen grass 2.8 low ragweed 1.3 mugwort 0.9 none birch 0.0 none
+Krakow, PL Tue 25 Aug 05:44 up 19:39 down GMT+2
+ day 12-17° rain 1.9mm over 7h sun 0h43m of 13h54m daylight
+ pollen ragweed 6.9 mugwort 4.4 low grass 3.1 low birch 0.0 none
- hr temp feels conditions hum
- 15 21° (19) overcast 48%
- 16 19° (17) 56%
- 17 18° (17) 63%
- 18 17° 64%
- 19 16° 66%
- 20 16° 65%
- 21 15° 69%
- 22 15° (14) 66%
+ hr temp feels conditions hum mm rain
+ 15 16° lt drizzle 79% 0.3 70%
+ 16 16° 80% 0.3 44%
+ 17 16° overcast 77% 0.0 27%
+ 18 16° lt drizzle 78% 0.1 27%
+ 19 15° 86% 0.2 36%
+ 20 15° 87% 0.4 45%
+ 21 15° 85% 0.2 52%
+ 22 15° (14) overcast 80% 0.0 58%
- 21°│████████
- │████████
- 18°│████████████████▄▄▄▄▄▄▄▄
- │████████████████████████▄▄▄▄▄▄▄▄
- 15 17 19 21
- 15-21°
+ 16°│ ████████████████
+ │████████▄▄▄▄▄▄▄▄████████████████
+ 15°│████████████████████████████████
+ │████████████████████████████████▄▄▄▄▄▄▄▄ ████████
+ 15°│████████████████████████████████████████▄▄▄▄▄▄▄▄████████████████
+rain│▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▁▁▁▁▁▁▁▁▃▃▃▃▃▃▃▃▅▅▅▅▅▅▅▅████████▅▅▅▅▅▅▅▅▁▁▁▁▁▁▁▁ max 0.4mm
+ 15 16 17 18 19 20 21 22
+ 15-16°
```
When IMGW has issued a warning for your powiat, it leads the output:
diff --git a/cmd/prognosis/resolve_test.go b/cmd/prognosis/resolve_test.go
index f7f1347..7a9daf5 100644
--- a/cmd/prognosis/resolve_test.go
+++ b/cmd/prognosis/resolve_test.go
@@ -124,7 +124,7 @@ func TestResolvePickBypassesAPoisonedCacheEntry(t *testing.T) {
func TestResolveCachesAnUnambiguousName(t *testing.T) {
only := []openmeteo.Candidate{
- {Geo: cache.Geo{Lat: 50.0617, Lon: 19.9373, Label: "Krakow, PL", Country: "PL"}, Admin1: "Subcarpathia"},
+ {Geo: cache.Geo{Lat: 50.0617, Lon: 19.9373, Label: "Krakow, PL", Country: "PL"}, Admin1: "Lesser Poland"},
}
calls := stubGeocoder(t, only)
store := tmpStore(t)
diff --git a/docs/superpowers/specs/2026-08-10-go-rewrite-design.md b/docs/superpowers/specs/2026-08-10-go-rewrite-design.md
index f592412..b0596be 100644
--- a/docs/superpowers/specs/2026-08-10-go-rewrite-design.md
+++ b/docs/superpowers/specs/2026-08-10-go-rewrite-design.md
@@ -52,7 +52,7 @@ documents itself — the same trick wego's `ingo` uses, without the library.
```
# ~/.config/prognosis/config
-location=Krakow # falls back to location= in ~/.wegorc when unset
+location=Krakow # required (see the note below)
hours=12 # default span; -n / -d override
units=metric # metric | imperial | si
@@ -77,6 +77,12 @@ Precedence: **flags > config file > built-in defaults**. Location additionally
falls back to `~/.wegorc` so the two weather tools never disagree about where
you are.
+> **Superseded in v0.1.1.** The `~/.wegorc` fallback was removed: reading
+> another program's configuration made prognosis useless without wego installed
+> and hid the fact that it has no way to know where you are. `location=` is now
+> required in prognosis' own config. This document is kept as the record of the
+> original design, not as current documentation.
+
`units` is passed to Open-Meteo as `temperature_unit` / `wind_speed_unit` /
`precipitation_unit` rather than converted locally, so rounding matches the
provider: `metric` = °C, km/h, mm; `imperial` = °F, mph, inch; `si` = °C, m/s,
diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go
index 3283db2..9560ff2 100644
--- a/internal/cache/cache_test.go
+++ b/internal/cache/cache_test.go
@@ -126,7 +126,7 @@ func TestGeoIsStoredAsAnArrayForPythonInterop(t *testing.T) {
// A cache written in the Python shape must load here unchanged.
func TestReadsPythonWrittenCache(t *testing.T) {
path := filepath.Join(t.TempDir(), "cache.json")
- body := `{"geo":{"Krakow":[50.06170,19.93730,"Krakow, PL","PL"]},"teryt":{"50.0617,19.9373":"1815"}}`
+ body := `{"geo":{"Krakow":[50.06174,19.93732,"Krakow, PL","PL"]},"teryt":{"50.0617,19.9373":"1261"}}`
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
@@ -135,7 +135,7 @@ func TestReadsPythonWrittenCache(t *testing.T) {
if !ok || g.Label != "Krakow, PL" || g.Country != "PL" {
t.Fatalf("got %+v (%v)", g, ok)
}
- if code, ok := c.Teryt("50.0617,19.9373"); !ok || code != "1815" {
+ if code, ok := c.Teryt("50.0617,19.9373"); !ok || code != "1261" {
t.Fatalf("teryt = %q (%v)", code, ok)
}
}
@@ -156,7 +156,7 @@ func writeCache(t *testing.T, c *Cache, body string) {
// everything that was still fine.
func TestOneMalformedEntryDoesNotDestroyTheOthers(t *testing.T) {
c := tmpCache(t)
- writeCache(t, c, `{"geo":{"Gdansk":[54.35227,18.64912,"Gdansk, PL","PL"],"Broken":[1]},"teryt":{"50.0617,19.9373":"1815"}}`)
+ writeCache(t, c, `{"geo":{"Gdansk":[54.35227,18.64912,"Gdansk, PL","PL"],"Broken":[1]},"teryt":{"50.0617,19.9373":"1261"}}`)
if err := c.PutGeo("Krakow", Geo{Lat: 50.0617, Lon: 19.9373, Label: "Krakow, PL", Country: "PL"}); err != nil {
t.Fatal(err)
@@ -167,7 +167,7 @@ func TestOneMalformedEntryDoesNotDestroyTheOthers(t *testing.T) {
if _, ok := c.Geo("Krakow"); !ok {
t.Error("the new entry was not stored")
}
- if code, ok := c.Teryt("50.0617,19.9373"); !ok || code != "1815" {
+ if code, ok := c.Teryt("50.0617,19.9373"); !ok || code != "1261" {
t.Errorf("teryt section lost too: got %q, %v", code, ok)
}
if _, ok := c.Geo("Broken"); ok {
@@ -202,14 +202,14 @@ func TestPutTerytAlsoMovesAnUnparseableFileAside(t *testing.T) {
c := tmpCache(t)
const broken = `{oops`
writeCache(t, c, broken)
- if err := c.PutTeryt("50.0617,19.9373", "1815"); err != nil {
+ if err := c.PutTeryt("50.0617,19.9373", "1261"); err != nil {
t.Fatalf("the tool must keep working: %v", err)
}
kept, err := os.ReadFile(c.path + ".bad")
if err != nil || string(kept) != broken {
t.Errorf("unparseable file not preserved: %q, %v", kept, err)
}
- if code, ok := c.Teryt("50.0617,19.9373"); !ok || code != "1815" {
+ if code, ok := c.Teryt("50.0617,19.9373"); !ok || code != "1261" {
t.Errorf("fresh cache did not take the entry: %q, %v", code, ok)
}
}
@@ -217,13 +217,13 @@ func TestPutTerytAlsoMovesAnUnparseableFileAside(t *testing.T) {
// The file is read by a human at least as often as by the program.
func TestSaveWritesOneEntryPerLine(t *testing.T) {
c := tmpCache(t)
- if err := c.PutGeo("Krakow", Geo{Lat: 50.06170, Lon: 19.93730, Label: "Krakow, PL", Country: "PL"}); err != nil {
+ if err := c.PutGeo("Krakow", Geo{Lat: 50.06174, Lon: 19.93732, Label: "Krakow, PL", Country: "PL"}); err != nil {
t.Fatal(err)
}
if err := c.PutGeo("Chiang Mai", Geo{Lat: 18.79038, Lon: 98.98468, Label: "Chiang Mai, TH", Country: "TH"}); err != nil {
t.Fatal(err)
}
- if err := c.PutTeryt("50.0617,19.9373", "1815"); err != nil {
+ if err := c.PutTeryt("50.0617,19.9373", "1261"); err != nil {
t.Fatal(err)
}
body, err := os.ReadFile(c.path)
@@ -233,10 +233,10 @@ func TestSaveWritesOneEntryPerLine(t *testing.T) {
want := `{
"geo": {
"Chiang Mai": [18.79038, 98.98468, "Chiang Mai, TH", "TH"],
- "Krakow": [50.06170, 19.93730, "Krakow, PL", "PL"]
+ "Krakow": [50.06174, 19.93732, "Krakow, PL", "PL"]
},
"teryt": {
- "50.0617,19.9373": "1815"
+ "50.0617,19.9373": "1261"
}
}
`
@@ -247,7 +247,7 @@ func TestSaveWritesOneEntryPerLine(t *testing.T) {
func TestSavedFileIsStillValidJSONForTheOtherImplementation(t *testing.T) {
c := tmpCache(t)
- want := Geo{Lat: 50.06170, Lon: 19.93730, Label: "Krakow, PL", Country: "PL"}
+ want := Geo{Lat: 50.06174, Lon: 19.93732, Label: "Krakow, PL", Country: "PL"}
if err := c.PutGeo("Krakow", want); err != nil {
t.Fatal(err)
}
diff --git a/internal/imgw/imgw_test.go b/internal/imgw/imgw_test.go
index ffc678e..fb1bf21 100644
--- a/internal/imgw/imgw_test.go
+++ b/internal/imgw/imgw_test.go
@@ -53,8 +53,8 @@ func TestPowiatTruncatesTerytToFourDigits(t *testing.T) {
if status != StatusOK {
t.Fatalf("status = %v, want StatusOK", status)
}
- if code != "1815" {
- t.Fatalf("code = %q, want 1815 (first four digits of 126101)", code)
+ if code != "1261" {
+ t.Fatalf("code = %q, want 1261 (first four digits of 126101)", code)
}
}
@@ -108,32 +108,32 @@ func TestPowiatAsksForAWideRadius(t *testing.T) {
func TestWarningsKeepOnlyThisPowiat(t *testing.T) {
pinClock(t, time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)) // nothing expired
body := `[
- {"nazwa_zdarzenia":"Upal","stopien":"1","obowiazuje_do":"2030-01-01 00:00:00","teryt":["1815","1234"]},
+ {"nazwa_zdarzenia":"Upal","stopien":"1","obowiazuje_do":"2030-01-01 00:00:00","teryt":["1261","1234"]},
{"nazwa_zdarzenia":"Burze","stopien":"2","obowiazuje_do":"2030-01-01 00:00:00","teryt":["2207"]}
]`
srv := serveText(t, body)
warningsURL = srv.URL
- live, err := Warnings("1815")
+ live, err := Warnings("1261")
if err != nil {
t.Fatal(err)
}
if len(live) != 1 || live[0].Event != "Upal" {
- t.Fatalf("got %+v, want only the warning covering 1815", live)
+ t.Fatalf("got %+v, want only the warning covering 1261", live)
}
}
func TestWarningsDropExpiredButKeepUnparseableDates(t *testing.T) {
pinClock(t, time.Date(2026, 8, 10, 12, 0, 0, 0, time.Local))
body := `[
- {"nazwa_zdarzenia":"Wczorajsze","stopien":"1","obowiazuje_do":"2026-08-09 23:00:00","teryt":["1815"]},
- {"nazwa_zdarzenia":"Trwajace","stopien":"1","obowiazuje_do":"2026-08-10 20:00:00","teryt":["1815"]},
- {"nazwa_zdarzenia":"Bezdaty","stopien":"1","obowiazuje_do":"","teryt":["1815"]}
+ {"nazwa_zdarzenia":"Wczorajsze","stopien":"1","obowiazuje_do":"2026-08-09 23:00:00","teryt":["1261"]},
+ {"nazwa_zdarzenia":"Trwajace","stopien":"1","obowiazuje_do":"2026-08-10 20:00:00","teryt":["1261"]},
+ {"nazwa_zdarzenia":"Bezdaty","stopien":"1","obowiazuje_do":"","teryt":["1261"]}
]`
srv := serveText(t, body)
warningsURL = srv.URL
- live, err := Warnings("1815")
+ live, err := Warnings("1261")
if err != nil {
t.Fatal(err)
}
@@ -151,9 +151,9 @@ func TestWarningsDropExpiredButKeepUnparseableDates(t *testing.T) {
// IMGW has been seen to encode TERYT codes as numbers as well as strings.
func TestWarningsMatchNumericTerytCodes(t *testing.T) {
pinClock(t, time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local))
- srv := serveText(t, `[{"nazwa_zdarzenia":"X","obowiazuje_do":"2030-01-01 00:00:00","teryt":[1815]}]`)
+ srv := serveText(t, `[{"nazwa_zdarzenia":"X","obowiazuje_do":"2030-01-01 00:00:00","teryt":[1261]}]`)
warningsURL = srv.URL
- live, err := Warnings("1815")
+ live, err := Warnings("1261")
if err != nil {
t.Fatal(err)
}
@@ -166,7 +166,7 @@ func TestWarningsUnreachableIsAnError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
srv.Close()
warningsURL = srv.URL
- if _, err := Warnings("1815"); err == nil {
+ if _, err := Warnings("1261"); err == nil {
t.Fatal("expected an error: the caller must be able to say 'could not check'")
}
}
@@ -177,7 +177,7 @@ func TestWarningsParseTheRecordedFeed(t *testing.T) {
srv := serve(t, "warnings.json", nil)
warningsURL = srv.URL
- if _, err := Warnings("1815"); err != nil {
+ if _, err := Warnings("1261"); err != nil {
t.Fatalf("the recorded feed must parse: %v", err)
}
none, err := Warnings("9999")
diff --git a/internal/imgw/testdata/gugik_krakow.json b/internal/imgw/testdata/gugik_krakow.json
new file mode 100644
index 0000000..9bee9a1
--- /dev/null
+++ b/internal/imgw/testdata/gugik_krakow.json
@@ -0,0 +1 @@
+{"type":"address","max results limit":1,"radius":5000,"max polygon area":null,"returned objects":1,"results":{"1":{"city":"Krak\u00f3w","citypart":null,"street":"Rynek G\u0142\u00f3wny","number":"3","teryt":"126101","simc":"0950463","ulic":"38504","code":"31-042","jednostka":"{Polska,ma\u0142opolskie,Krak\u00f3w,Krak\u00f3w}","x":"19.9375622203567","y":"50.0617125271123","geometry_wkt":"POINT(19.9375622203567 50.0617125271123)","distance":"18.8155055148824","id":1}},"request time":0.0011776526769002279} \ No newline at end of file
diff --git a/internal/openmeteo/testdata/forecast.json b/internal/openmeteo/testdata/forecast.json
index b8948f7..287bbed 100644
--- a/internal/openmeteo/testdata/forecast.json
+++ b/internal/openmeteo/testdata/forecast.json
@@ -1 +1 @@
-{"latitude":50.061700,"longitude":19.937300,"generationtime_ms":1.4580488204956055,"utc_offset_seconds":7200,"timezone":"Europe/Warsaw","timezone_abbreviation":"GMT+2","elevation":236.0,"hourly_units":{"time":"iso8601","temperature_2m":"°C","apparent_temperature":"°C","precipitation":"mm","precipitation_probability":"%","weather_code":"wmo code"},"hourly":{"time":["2026-08-10T00:00","2026-08-10T01:00","2026-08-10T02:00","2026-08-10T03:00","2026-08-10T04:00","2026-08-10T05:00","2026-08-10T06:00","2026-08-10T07:00","2026-08-10T08:00","2026-08-10T09:00","2026-08-10T10:00","2026-08-10T11:00","2026-08-10T12:00","2026-08-10T13:00","2026-08-10T14:00","2026-08-10T15:00","2026-08-10T16:00","2026-08-10T17:00","2026-08-10T18:00","2026-08-10T19:00","2026-08-10T20:00","2026-08-10T21:00","2026-08-10T22:00","2026-08-10T23:00","2026-08-11T00:00","2026-08-11T01:00","2026-08-11T02:00","2026-08-11T03:00","2026-08-11T04:00","2026-08-11T05:00","2026-08-11T06:00","2026-08-11T07:00","2026-08-11T08:00","2026-08-11T09:00","2026-08-11T10:00","2026-08-11T11:00","2026-08-11T12:00","2026-08-11T13:00","2026-08-11T14:00","2026-08-11T15:00","2026-08-11T16:00","2026-08-11T17:00","2026-08-11T18:00","2026-08-11T19:00","2026-08-11T20:00","2026-08-11T21:00","2026-08-11T22:00","2026-08-11T23:00"],"temperature_2m":[15.0,14.4,13.4,13.0,12.9,12.1,11.9,14.4,19.2,22.4,24.6,26.2,27.5,28.7,29.3,29.4,29.3,28.9,28.4,27.0,24.8,23.6,22.9,22.6,22.3,22.5,22.4,21.9,21.6,20.3,18.9,19.8,20.9,22.4,22.0,22.1,23.1,22.7,21.5,22.2,21.9,21.8,21.7,21.3,19.5,18.3,16.8,15.6],"apparent_temperature":[13.8,13.3,12.5,11.8,11.5,11.2,11.0,14.0,18.3,21.6,24.1,26.2,27.6,29.1,29.5,28.2,28.3,28.7,28.9,28.2,25.5,24.1,22.2,21.8,21.3,20.4,20.0,19.5,19.7,18.7,18.3,19.4,21.3,23.1,21.6,22.0,23.3,21.8,20.4,20.8,19.8,19.7,20.2,19.6,18.3,16.3,15.1,13.7],"precipitation":[0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00],"precipitation_probability":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,4,3,2,1,0,0,2,6,8,8,6,4,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0],"weather_code":[1,3,2,0,0,0,0,1,0,2,2,3,3,0,3,3,3,3,3,0,0,3,1,0,0,0,0,0,0,0,0,0,3,3,3,3,2,3,3,3,3,3,0,0,0,0,0,0]},"daily_units":{"time":"iso8601","sunrise":"iso8601","sunset":"iso8601","temperature_2m_max":"°C","temperature_2m_min":"°C","precipitation_sum":"mm","precipitation_hours":"h","daylight_duration":"s","sunshine_duration":"s"},"daily":{"time":["2026-08-10","2026-08-11"],"sunrise":["2026-08-10T05:15","2026-08-11T05:16"],"sunset":["2026-08-10T20:01","2026-08-11T19:59"],"temperature_2m_max":[29.4,23.1],"temperature_2m_min":[11.9,15.6],"precipitation_sum":[0.00,0.00],"precipitation_hours":[0.0,0.0],"daylight_duration":[53154.79,52962.40],"sunshine_duration":[43387.38,856.60]}} \ No newline at end of file
+{"latitude":50.057274,"longitude":19.949356,"generationtime_ms":0.13709068298339844,"utc_offset_seconds":7200,"timezone":"Europe/Warsaw","timezone_abbreviation":"GMT+2","elevation":222.0,"hourly_units":{"time":"iso8601","temperature_2m":"°C","apparent_temperature":"°C","precipitation":"mm","precipitation_probability":"%","weather_code":"wmo code"},"hourly":{"time":["2026-08-25T00:00","2026-08-25T01:00","2026-08-25T02:00","2026-08-25T03:00","2026-08-25T04:00","2026-08-25T05:00","2026-08-25T06:00","2026-08-25T07:00","2026-08-25T08:00","2026-08-25T09:00","2026-08-25T10:00","2026-08-25T11:00","2026-08-25T12:00","2026-08-25T13:00","2026-08-25T14:00","2026-08-25T15:00","2026-08-25T16:00","2026-08-25T17:00","2026-08-25T18:00","2026-08-25T19:00","2026-08-25T20:00","2026-08-25T21:00","2026-08-25T22:00","2026-08-25T23:00","2026-08-26T00:00","2026-08-26T01:00","2026-08-26T02:00","2026-08-26T03:00","2026-08-26T04:00","2026-08-26T05:00","2026-08-26T06:00","2026-08-26T07:00","2026-08-26T08:00","2026-08-26T09:00","2026-08-26T10:00","2026-08-26T11:00","2026-08-26T12:00","2026-08-26T13:00","2026-08-26T14:00","2026-08-26T15:00","2026-08-26T16:00","2026-08-26T17:00","2026-08-26T18:00","2026-08-26T19:00","2026-08-26T20:00","2026-08-26T21:00","2026-08-26T22:00","2026-08-26T23:00"],"temperature_2m":[15.7,15.2,14.2,13.4,12.7,12.3,11.7,12.3,13.5,14.7,15.9,16.3,16.4,16.9,16.5,15.8,15.7,16.0,16.0,15.2,14.8,15.1,15.2,15.2,14.6,14.7,14.5,14.2,14.2,14.2,14.1,14.6,15.0,15.9,16.7,17.8,19.4,19.9,21.6,22.5,22.9,23.0,22.7,21.9,20.7,19.7,17.7,16.8],"apparent_temperature":[14.3,14.1,13.0,11.9,11.2,10.5,10.0,10.6,11.7,12.4,13.4,13.8,14.5,15.3,15.4,15.0,14.7,15.2,15.6,14.8,13.9,14.4,14.3,14.4,14.3,14.4,14.1,13.7,13.7,13.5,13.2,13.8,14.2,15.0,15.8,16.9,18.5,18.9,20.3,21.7,21.5,21.3,21.0,20.6,20.0,19.5,16.9,16.3],"precipitation":[0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.30,0.40,0.30,0.00,0.10,0.20,0.40,0.20,0.00,0.00,0.00,0.00,0.00,0.20,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00],"precipitation_probability":[0,0,0,0,0,0,0,0,0,14,33,51,67,82,86,70,44,27,27,36,45,52,58,59,50,36,25,20,18,16,13,10,8,7,8,8,8,7,6,4,2,0,0,0,0,1,2,2],"weather_code":[0,0,0,0,0,0,3,2,3,3,3,3,3,3,51,51,51,3,51,51,51,51,3,3,3,3,3,51,3,3,3,3,3,3,3,3,3,3,2,0,1,0,0,0,0,0,1,1]},"daily_units":{"time":"iso8601","sunrise":"iso8601","sunset":"iso8601","temperature_2m_max":"°C","temperature_2m_min":"°C","precipitation_sum":"mm","precipitation_hours":"h","daylight_duration":"s","sunshine_duration":"s"},"daily":{"time":["2026-08-25","2026-08-26"],"sunrise":["2026-08-25T05:44","2026-08-26T05:46"],"sunset":["2026-08-25T19:39","2026-08-26T19:37"],"temperature_2m_max":[16.9,23.0],"temperature_2m_min":[11.7,14.1],"precipitation_sum":[1.90,0.20],"precipitation_hours":[7.0,1.0],"daylight_duration":[50091.86,49879.20],"sunshine_duration":[2602.91,18000.00]}} \ No newline at end of file
diff --git a/internal/openmeteo/testdata/pollen.json b/internal/openmeteo/testdata/pollen.json
index eba1a73..f377143 100644
--- a/internal/openmeteo/testdata/pollen.json
+++ b/internal/openmeteo/testdata/pollen.json
@@ -1 +1 @@
-{"latitude":50.0,"longitude":21.8,"generationtime_ms":0.19991397857666016,"utc_offset_seconds":7200,"timezone":"Europe/Warsaw","timezone_abbreviation":"GMT+2","elevation":236.0,"hourly_units":{"time":"iso8601","grass_pollen":"grains/m³","birch_pollen":"grains/m³"},"hourly":{"time":["2026-08-10T00:00","2026-08-10T01:00","2026-08-10T02:00","2026-08-10T03:00","2026-08-10T04:00","2026-08-10T05:00","2026-08-10T06:00","2026-08-10T07:00","2026-08-10T08:00","2026-08-10T09:00","2026-08-10T10:00","2026-08-10T11:00","2026-08-10T12:00","2026-08-10T13:00","2026-08-10T14:00","2026-08-10T15:00","2026-08-10T16:00","2026-08-10T17:00","2026-08-10T18:00","2026-08-10T19:00","2026-08-10T20:00","2026-08-10T21:00","2026-08-10T22:00","2026-08-10T23:00","2026-08-11T00:00","2026-08-11T01:00","2026-08-11T02:00","2026-08-11T03:00","2026-08-11T04:00","2026-08-11T05:00","2026-08-11T06:00","2026-08-11T07:00","2026-08-11T08:00","2026-08-11T09:00","2026-08-11T10:00","2026-08-11T11:00","2026-08-11T12:00","2026-08-11T13:00","2026-08-11T14:00","2026-08-11T15:00","2026-08-11T16:00","2026-08-11T17:00","2026-08-11T18:00","2026-08-11T19:00","2026-08-11T20:00","2026-08-11T21:00","2026-08-11T22:00","2026-08-11T23:00"],"grass_pollen":[7.2,6.7,5.4,2.6,1.7,2.6,2.6,6.2,6.3,6.5,6.9,6.6,5.5,4.7,4.5,4.7,5.0,4.9,5.5,6.0,10.6,6.4,6.3,6.5,6.4,5.4,5.4,6.0,5.4,4.9,4.8,4.6,4.6,3.8,3.4,3.5,3.5,3.8,3.9,3.8,3.7,4.4,4.2,4.9,5.8,5.8,6.6,8.0],"birch_pollen":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]}} \ No newline at end of file
+{"latitude":50.1,"longitude":19.900002,"generationtime_ms":0.21314620971679688,"utc_offset_seconds":7200,"timezone":"Europe/Warsaw","timezone_abbreviation":"GMT+2","elevation":222.0,"hourly_units":{"time":"iso8601","grass_pollen":"grains/m³","birch_pollen":"grains/m³"},"hourly":{"time":["2026-08-25T00:00","2026-08-25T01:00","2026-08-25T02:00","2026-08-25T03:00","2026-08-25T04:00","2026-08-25T05:00","2026-08-25T06:00","2026-08-25T07:00","2026-08-25T08:00","2026-08-25T09:00","2026-08-25T10:00","2026-08-25T11:00","2026-08-25T12:00","2026-08-25T13:00","2026-08-25T14:00","2026-08-25T15:00","2026-08-25T16:00","2026-08-25T17:00","2026-08-25T18:00","2026-08-25T19:00","2026-08-25T20:00","2026-08-25T21:00","2026-08-25T22:00","2026-08-25T23:00","2026-08-26T00:00","2026-08-26T01:00","2026-08-26T02:00","2026-08-26T03:00","2026-08-26T04:00","2026-08-26T05:00","2026-08-26T06:00","2026-08-26T07:00","2026-08-26T08:00","2026-08-26T09:00","2026-08-26T10:00","2026-08-26T11:00","2026-08-26T12:00","2026-08-26T13:00","2026-08-26T14:00","2026-08-26T15:00","2026-08-26T16:00","2026-08-26T17:00","2026-08-26T18:00","2026-08-26T19:00","2026-08-26T20:00","2026-08-26T21:00","2026-08-26T22:00","2026-08-26T23:00"],"grass_pollen":[2.9,2.9,1.8,1.6,1.4,1.4,0.8,0.7,1.1,1.6,2.0,2.3,2.7,2.9,1.8,3.0,2.8,3.1,3.0,2.6,2.3,2.2,2.1,2.1,2.1,2.2,2.4,2.1,1.8,1.9,2.0,1.6,2.1,2.0,1.8,2.1,2.3,2.4,2.2,2.1,1.7,1.5,1.5,2.2,2.5,2.9,2.8,2.3],"birch_pollen":[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]}} \ No newline at end of file