docs: reorganize project documentation

This commit is contained in:
zv
2026-06-13 11:57:00 +02:00
parent b2cbc5aceb
commit f317782ef9
7 changed files with 548 additions and 149 deletions

207
README.md
View File

@@ -1,32 +1,33 @@
# wtr. # wtr.
**Pogoda z danych IMGW. Prosto. Pięknie. Aktualnie.** `wtr.` to mobilna PWA pogodowa dla Polski oparta o publiczne dane IMGW oraz jawnie oznaczoną prognozę modelową łączącą IMGW ALARO z Open-Meteo.
`wtr.` to nowoczesna pogodowa PWA dla Polski oparta o publiczne dane IMGW i jawnie oznaczoną prognozę modelową łączącą IMGW ALARO z Open-Meteo. Aplikacja prezentuje bieżącą analizę pogody IMGW Hybrid, odczyty synoptyczne, prognozę godzinową i 7-dniową, regułowy brief dnia, stacje hydrologiczne oraz ostrzeżenia w spokojnym, mobilnym interfejsie z czytelną typografią, opaque surfaces i subtelnymi animacjami. Dashboard pokazuje rozszerzony desktopowy podgląd godzin z podsumowaniem najbliższej doby, a także wykresy temperatury i opadu dla bieżącego dnia. Każdy dzień prognozy można także otworzyć w animowanym widoku szczegółowym z przebiegiem godzinowym oraz wykresami. Aplikacja pokazuje bieżącą analizę IMGW Hybrid, pomiary synoptyczne, prognozę godzinową i 7-dniową, ostrzeżenia meteorologiczne i hydrologiczne, dane hydro oraz deterministyczne briefy pogodowe bez użycia zewnętrznego modelu AI.
Interfejs jest dostępny po polsku i angielsku. Wybrany język jest zapisywany lokalnie w przeglądarce. Oryginalne treści ostrzeżeń oraz nazwy stacji pochodzą bezpośrednio z API IMGW i nie są automatycznie tłumaczone. ## Najważniejsze funkcje
Wyszukiwarka na stronie głównej obsługuje miejscowości w całej Polsce. Nazwa miejscowości jest rozpoznawana przez Open-Meteo Geocoding API oparte o GeoNames. Bieżące warunki są analizowane lokalnie przez IMGW Hybrid, a najbliższa rzeczywista stacja IMGW jest pokazywana jako kontekst i fallback. Interfejs jawnie pokazuje nazwę tej stacji oraz przybliżoną odległość. - Bieżące warunki z lokalnej analizy IMGW Hybrid, z opisanym fallbackiem do stacji synoptycznej.
- Prognoza modelowa 7 dni: IMGW ALARO dla dostępnych godzin oraz Open-Meteo jako uzupełnienie i fallback.
Użytkownik może opcjonalnie udostępnić położenie GPS. Pozycja jest zaokrąglana do trzech miejsc po przecinku, czyli około 100 metrów, a nazwa miejscowości jest ustalana przez Nominatim / OpenStreetMap. Po zgodzie aplikacja wybiera lokalizację, najbliższą stację IMGW i prognozę. Geolocation API wymaga bezpiecznego kontekstu HTTPS. Wyjątkiem jest `localhost`; wejście z iPhone przez lokalny adres typu `http://192.168.x.x:3000` nie uruchomi systemowego pytania Safari. - Wyszukiwanie miejscowości w Polsce oraz opcjonalny wybór lokalizacji GPS.
- Ostrzeżenia IMGW z filtrowaniem meteo po powiecie TERYT, gdy lokalizacja go dostarcza.
Widok ostrzeżeń priorytetyzuje komunikaty dla obszaru wynikającego z miejscowości lub stacji wybranej w pogodzie. Dla ostrzeżeń meteorologicznych, gdy aplikacja rozpozna powiat wybranej miejscowości, filtrowanie odbywa się po kodzie TERYT powiatu; w przeciwnym razie pozostaje fallback wojewódzki. W każdej grupie ostrzeżenia meteorologiczne, np. o burzach lub silnym wietrze, są wyświetlane przed hydrologicznymi. Ostrzeżenia meteorologiczne IMGW przypisuje do regionów na podstawie kodów TERYT, a hydrologiczne na podstawie jawnych pól województwa z API. Pozostałe aktywne komunikaty są wyświetlane niżej. Dashboard pokazuje dodatkowo kompaktowy panel aktywnych i nadchodzących ostrzeżeń meteo dla wybranego obszaru. Panel automatycznie ukrywa komunikaty po upływie ich czasu obowiązywania i nie obejmuje ostrzeżeń hydrologicznych. - Powiadomienia Web Push o nowych ostrzeżeniach, porannym briefie dnia i wieczornym briefie na jutro.
- Widoki dashboardu, prognozy szczegółowej dnia, ostrzeżeń, hydro, stacji i ustawień.
Publiczne endpointy ostrzeżeń IMGW potrafią zwrócić HTTP `404` z treścią `{"status":false,"message":"No products were found"}`. Aplikacja traktuje taki wariant jako poprawną pustą listę ostrzeżeń, a nie awarię źródła danych. - PWA z manifestem, własnym service workerem i podstawowym fallbackiem offline.
- Interfejs po polsku i angielsku.
## Stack ## Stack
- Next.js z App Router i TypeScript - Next.js App Router, React, TypeScript
- Tailwind CSS oraz komponenty w stylu shadcn/ui - Tailwind CSS
- TanStack Query
- Zustand
- Framer Motion - Framer Motion
- Recharts - Recharts
- Lucide React - Lucide React
- TanStack Query - `web-push`
- Zustand z trwałym stanem `localStorage` - własny service worker i manifest PWA
- web-push do wysyłki Web Push
- własny service worker, manifest i offline fallback
## Uruchomienie ## Szybki Start
Wymagany jest Node.js 20.9 lub nowszy. Wymagany jest Node.js 20.9 lub nowszy.
@@ -37,131 +38,11 @@ npm run dev
Aplikacja będzie dostępna pod adresem `http://localhost:3000`. Aplikacja będzie dostępna pod adresem `http://localhost:3000`.
Sprawdzenie jakości i build produkcyjny: ## Konfiguracja
```bash Do podstawowego uruchomienia aplikacji pogodowej nie są potrzebne klucze API. Publiczne dane IMGW, Open-Meteo i Nominatim są pobierane przez route handlery Next.js.
npm run lint
npm run build
npm run start
```
Worker powiadomień uruchamiany obok aplikacji: Powiadomienia Web Push wymagają zmiennych środowiskowych:
```bash
npm run start
npm run notifications:worker
```
Worker musi mieć dostęp do działającej aplikacji Next.js. Domyślnie odpytuje `http://127.0.0.1:3000`; jeśli aplikacja działa pod innym adresem albo portem, ustaw `WTR_APP_URL`. Skrypt workera sam wczytuje `.env` i `.env.local`, a zmienne podane bezpośrednio w shellu mają pierwszeństwo.
## Źródła danych
Bieżące pomiary i komunikaty pochodzą z rzeczywistych publicznych danych IMGW:
- bieżąca analiza IMGW Hybrid używana przez oficjalny portal: `https://meteo.imgw.pl/api/v1/forecast/fcapi`
- prognoza godzinowa IMGW ALARO używana przez oficjalny portal: `https://meteo.imgw.pl/api/v1/forecast/fcapi?m=alaro`
- dane synoptyczne: `https://danepubliczne.imgw.pl/api/data/synop`
- pojedyncza stacja synoptyczna: `https://danepubliczne.imgw.pl/api/data/synop/id/{id}`
- dane hydrologiczne: `https://danepubliczne.imgw.pl/api/data/hydro/`
- ostrzeżenia meteorologiczne: `https://danepubliczne.imgw.pl/api/data/warningsmeteo`
- ostrzeżenia hydrologiczne: `https://danepubliczne.imgw.pl/api/data/warningshydro`
- dane meteorologiczne: `https://danepubliczne.imgw.pl/api/data/meteo/`
- lista produktów: `https://danepubliczne.imgw.pl/api/data/product`
Prognoza modelowa łączy dwa źródła. IMGW ALARO dostarcza dostępne godziny prognozy, zwykle około 72 godzin od cyklu modelu. Open-Meteo Forecast API (`https://api.open-meteo.com/v1/forecast`) dostarcza prawdopodobieństwo opadu dla całego zakresu, uzupełnia dalszy horyzont do pełnych 7 dni i pozostaje fallbackiem, jeśli ALARO chwilowo nie odpowiada. Interfejs pokazuje oba źródła i ich role.
Do wyszukiwania nazw miejscowości używany jest endpoint `https://geocoding-api.open-meteo.com/v1/search`. Przed wdrożeniem komercyjnym należy sprawdzić aktualne warunki korzystania z Open-Meteo lub zastąpić usługę własnym dostawcą.
Opcjonalny reverse geocoding dla GPS korzysta z publicznego endpointu Nominatim: `https://nominatim.openstreetmap.org/reverse`. Wywołanie następuje wyłącznie po zgodzie użytkownika. Interfejs pokazuje atrybucję OpenStreetMap. Przed wdrożeniem o większym ruchu należy sprawdzić aktualną politykę użycia publicznej instancji Nominatim lub zastąpić ją własną usługą.
Przeglądarka pobiera dane przez route handlery Next.js. Proxy IMGW w `app/api/imgw/[...path]/route.ts` pozwala ujednolicić cache, błędy API i bezpiecznie obsłużyć hydro bez mixed content. Bieżącą analizę pogody obsługuje `app/api/imgw-current/route.ts`, prognozę `app/api/forecast/route.ts`, a reverse geocoding GPS `app/api/locations/reverse/route.ts`.
## Wewnętrzne endpointy API
| Metoda | Endpoint | Przeznaczenie |
| --- | --- | --- |
| `GET` | `/api/forecast?latitude={lat}&longitude={lon}` | Zwraca 7-dniową prognozę modelową dla współrzędnych, łącząc IMGW ALARO z fallbackiem Open-Meteo. Odpowiedź ma cache `s-maxage=900`. |
| `GET` | `/api/imgw-current?latitude={lat}&longitude={lon}` | Pobiera lokalną analizę IMGW Hybrid z oficjalnego portalu IMGW dla współrzędnych. Cache jest krótki, `s-maxage=120`. |
| `GET` | `/api/imgw/{path}` | Proxy allowlistowanych publicznych endpointów IMGW `danepubliczne.imgw.pl`, m.in. `synop`, `synop/id/{id}`, `hydro`, `meteo`, `warningsmeteo`, `warningshydro` i `product`. Empty `warningsmeteo`/`warningshydro` z komunikatem `No products were found` jest normalizowane do pustej listy. |
| `GET` | `/api/locations/search?name={query}` | Wyszukuje miejscowości w Polsce przez Open-Meteo Geocoding i zwraca znormalizowane lokalizacje dla wyszukiwarki. Cache `s-maxage=86400`. |
| `GET` | `/api/locations/reverse?latitude={lat}&longitude={lon}` | Ustala nazwę miejsca dla pozycji GPS przez Nominatim / OpenStreetMap po zgodzie użytkownika. Cache `s-maxage=86400`. |
| `GET` | `/api/notifications/vapid-key` | Zwraca publiczny klucz VAPID i informację, czy Web Push jest skonfigurowany. Używane przez `/settings`. |
| `POST` | `/api/notifications/subscriptions` | Zapisuje lub aktualizuje subskrypcję Web Push dla urządzenia, wraz z preferencjami ostrzeżeń, briefu o 7:00, briefu o 18:00, lokalizacją i powiatem TERYT. Wymaga skonfigurowanych kluczy VAPID. |
| `DELETE` | `/api/notifications/subscriptions` | Usuwa subskrypcję Web Push na podstawie endpointu subskrypcji. |
| `GET` | `/api/notifications/check` | Endpoint harmonogramu sprawdzający nowe ostrzeżenia meteorologiczne IMGW i wysyłający Web Push do pasujących subskrypcji. W produkcji wymaga `Authorization: Bearer <NOTIFICATIONS_CRON_SECRET>` albo `x-cron-secret`. |
| `GET` | `/api/notifications/daily-brief` | Endpoint harmonogramu wysyłający raz dziennie poranny brief o 7:00 dla subskrypcji z włączoną opcją i zapisaną lokalizacją. W produkcji wymaga sekretu crona. |
| `GET` | `/api/notifications/tomorrow-brief` | Endpoint harmonogramu wysyłający wieczorny brief o 18:00 z prognozą na jutro, w tym sygnałami burz z modelu. W produkcji wymaga sekretu crona. |
| `POST` | `/api/notifications/test` | Wysyła testowe powiadomienie Web Push na konkretny endpoint subskrypcji. Używane wyłącznie z `/settings`. |
## Ograniczenia API
Dashboard korzysta z publicznego endpointu IMGW Hybrid używanego przez oficjalny portal `meteo.imgw.pl`. Bieżące warunki są wybierane z pierwszego pełnego rekordu analizy Hybrid dla współrzędnych miejscowości, preferując rekord 10-minutowy i wymagając realnych wartości liczbowych. Jeśli pełny rekord 10-minutowy jest wyraźnie starszy niż pełny rekord godzinowy z tej samej odpowiedzi, aplikacja wybiera świeższy rekord godzinowy zamiast nadpisywać aktualny fallback starymi danymi. Dzięki temu `null` z rekordów MERGE nie jest traktowany jako pełny pomiar. Interfejs może dodatkowo pokazać rzeczywisty opad z ostatnich 10 minut, oddzielnie opisuje lokalną analizę Hybrid oraz najbliższą stację pomiarową IMGW. Pokrycie Hybrid może być częściowe: jeśli IMGW publikuje lokalny opad MERGE bez pełnego rekordu parametrów, hero zachowuje lokalny opad, a temperaturę, wiatr, wilgotność i ciśnienie jawnie uzupełnia fallbackiem ze stacji. Jeśli usługa Hybrid nie odpowiada, hero zachowuje cały pomiar `synop` jako oznaczony fallback. Gdy fallback pochodzi ze stacji oddalonej od miejscowości o co najmniej 30 km, interfejs ostrzega o możliwej różnicy warunków lokalnych. Endpoint Hybrid jest częścią publicznego frontendu IMGW, ale nie jest opisany w stabilnej dokumentacji `danepubliczne.imgw.pl`, więc integrację należy monitorować przy zmianach portalu.
Publiczny endpoint synoptyczny IMGW udostępnia najnowszy pomiar, a nie historię odczytów. Prognoza modelowa jest wyraźnie oddzielona od pomiarów IMGW oraz bieżącej analizy Hybrid. Parametry ALARO mają pierwszeństwo w godzinach objętych tym modelem, natomiast prawdopodobieństwo opadu i dalszy horyzont pochodzą z Open-Meteo, ponieważ ALARO nie publikuje prawdopodobieństwa opadu i nie obejmuje pełnych 7 dni. `wtr.` nie generuje fikcyjnych prognoz. Widok stacji prezentuje aktualne parametry i jawnie opisany snapshot pomiarowy. Brakujące wartości są oznaczane jako `Brak danych`.
Brief dnia jest deterministycznym podsumowaniem danych, a nie wywołaniem modelu AI. Moduł `lib/weather-brief.ts` analizuje najbliższe 24 godziny prognozy modelowej oraz aktywne i nadchodzące ostrzeżenia meteo IMGW dla obszaru użytkownika. Ten sam moduł przygotowuje osobny brief na jutro z pełnego jutrzejszego dnia prognozy, wykrywając m.in. burze po kodach pogodowych modelu także wtedy, gdy IMGW nie opublikowało jeszcze oficjalnego ostrzeżenia. Na tej podstawie tworzy dłuższy opis na dashboard oraz krótsze wersje do powiadomień. Dzięki temu funkcja nie wymaga klucza OpenAI API i działa przewidywalnie na małym serwerze, np. Raspberry Pi.
Pole `suma_opadu` z endpointu synoptycznego jest prezentowane jako akumulowana suma opadu. Nie służy do wnioskowania, że w danej chwili pada, ani do uruchamiania animacji deszczu.
Czas aktualizacji parametrów hydrologicznych może się różnić. Interfejs pokazuje czas pomiaru, aby starsze odczyty nie wyglądały na bieżące.
## Stany pogody i efekty wizualne
Prognoza godzinowa i dzienna rozpoznaje następujące stany warunków pogodowych:
| Stan | Opis w interfejsie |
| --- | --- |
| `clear` | Bezchmurnie |
| `partlyCloudy` | Częściowe zachmurzenie |
| `cloudy` | Pochmurno |
| `fog` | Mgła |
| `drizzle` | Mżawka |
| `rain` | Opady deszczu |
| `snow` | Opady śniegu |
| `thunderstorm` | Burza |
| `unknown` | Brak opisu |
Bieżąca analiza IMGW Hybrid rozpoznaje bezpośrednio opad deszczu, śnieg i burzę. Gdy żadne z tych zjawisk nie występuje, hero może pokazać pomocniczy opis: `Silny wiatr`, `Wilgotne warunki` albo `Spokojne warunki`.
Hero aktualnej pogody korzysta z uproszczonego nastroju wizualnego do wyboru ikony, tekstu i małego akcentu stanu. Nie steruje już pełnoekranowym gradientem.
| Mood | Obecna reguła |
| --- | --- |
| `night` | godzina przed `06:00` lub od `21:00` |
| `wind` | wiatr od `8 m/s` |
| `cold` | temperatura do `3°C` |
| `cloudy` | wilgotność od `80%` |
| `warm` | temperatura od `20°C` |
| `mild` | pozostałe przypadki |
Warstwa efektów wizualnych jest ograniczona do subtelnych efektów informacyjnych: kropli przy lokalnym opadzie oraz błysku przy burzy. Mood hero jest obecnie heurystyką opartą o porę dnia, temperaturę, wilgotność i wiatr. Nie jest jeszcze pełną klasyfikacją sterowaną kodem warunków IMGW Hybrid.
## Struktura projektu
```text
app/ routing, layout, proxy danych, offline fallback
components/forecast/ prognoza godzinowa i dzienna IMGW ALARO + Open-Meteo
components/charts/ wykresy odczytów i szczegółów prognozy
components/dashboard dashboard aplikacji
components/weather/ hero, stacje, metryki i szczegóły
components/warnings/ alerty meteo i hydro
components/hydro/ lista stacji hydrologicznych
components/settings/ ustawienia języka, motywu i preferencji alertów
components/ui/ bazowe komponenty interfejsu
components/states/ loading, empty i error states
hooks/ zapytania TanStack Query
lib/ API, normalizacja, helpery i stan
types/ typy danych IMGW
public/ manifest, ikony i service worker
```
## PWA i offline
Manifest znajduje się w `public/manifest.json`, a service worker w `public/sw.js`. Rejestracja service workera działa w buildzie produkcyjnym. Powłoka aplikacji ma podstawowy offline fallback. Odpowiedzi API mogą być dostępne z pamięci urządzenia przy braku sieci, ale UI nadal prezentuje czas pomiaru i status świeżości.
Widok `/settings` zawiera konfigurację powiadomień o ostrzeżeniach meteorologicznych IMGW: wybór województwa, zgodę Web Push, zapis subskrypcji urządzenia, poranny brief, wieczorny brief na jutro i wysłanie powiadomienia testowego. Na iOS/iPadOS powiadomienia webowe wymagają dodania PWA do ekranu początkowego i uruchomienia aplikacji z ikony. Na Androidzie i desktopie wystarczy HTTPS oraz przeglądarka obsługująca Web Push; instalacja PWA nie jest wymagana. Endpoint `/api/notifications/check` sprawdza `warningsmeteo` i wysyła tylko nowe ostrzeżenia meteo do pasujących subskrypcji, `/api/notifications/daily-brief` wysyła raz dziennie krótkie podsumowanie dla subskrypcji z zapisaną lokalizacją, `/api/notifications/tomorrow-brief` wysyła wieczorem prognozę na kolejny dzień, a `/api/notifications/test` wysyła test na wskazany endpoint subskrypcji. Endpointy harmonogramu nie uruchamiają się same: na self-hostingu uruchom `npm run notifications:worker` obok `npm run start`, albo użyj zewnętrznego crona. Obecny magazyn subskrypcji działa w pamięci procesu, więc produkcja wymaga podmiany `lib/push-store.ts` na trwałą bazę lub KV.
Do wysyłki Web Push wymagane są zmienne środowiskowe:
```bash ```bash
WEB_PUSH_VAPID_PUBLIC_KEY= WEB_PUSH_VAPID_PUBLIC_KEY=
@@ -174,17 +55,45 @@ NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00 NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
``` ```
Klucze VAPID można wygenerować poleceniem `npx web-push generate-vapid-keys`. Worker `npm run notifications:worker` wczytuje `.env` i `.env.local`, domyślnie odpytuje `http://127.0.0.1:3000`, więc `npm run start` musi działać równolegle na tym samym hoście albo `WTR_APP_URL` musi wskazywać właściwy adres aplikacji. Worker sprawdza ostrzeżenia co 5 minut, wysyła poranny brief po 07:00 i wieczorny brief na jutro po 18:00 czasu polskiego. Jeśli używasz zewnętrznego crona zamiast workera, cron ostrzeżeń powinien wywoływać `GET /api/notifications/check` z nagłówkiem `Authorization: Bearer <NOTIFICATIONS_CRON_SECRET>` albo `x-cron-secret`. Drugi cron, ustawiony na około 07:00 czasu polskiego, powinien wywoływać `GET /api/notifications/daily-brief`, a trzeci cron około 18:00 `GET /api/notifications/tomorrow-brief` z tym samym nagłówkiem. Przykład znajduje się w [.env.example](.env.example).
## Wdrożenie na Vercel ## Komendy
1. Umieść repozytorium w serwisie Git. | Komenda | Opis |
2. Importuj projekt do Vercel jako aplikację Next.js. | --- | --- |
3. Nie dodawaj kluczy API: publiczne endpointy IMGW ich nie wymagają. | `npm run dev` | Uruchamia serwer deweloperski Next.js. |
4. Wdróż standardowym poleceniem builda `npm run build`. | `npm run lint` | Uruchamia ESLint. |
| `npm run build` | Buduje aplikację produkcyjnie i uruchamia kontrolę TypeScript wykonywaną przez Next.js. |
| `npm run start` | Uruchamia zbudowaną aplikację. |
| `npm run notifications:worker` | Uruchamia self-hostowany worker powiadomień. Wymaga działającej aplikacji Next.js. |
Proxy IMGW działa jako route handler Next.js i jest zgodne z hostingiem Vercel. Repozytorium nie ma obecnie osobnego skryptu testów, type-check ani formattera.
## Bezpieczeństwo zależności ## Struktura Projektu
Projekt używa stabilnego Next.js `16.2.6`. `npm audit --omit=dev` raportuje obecnie umiarkowane zgłoszenie `GHSA-qx2v-qp2m-jg93` dla PostCSS `8.4.31` bundlowanego bezpośrednio przez najnowszy Next.js. Główna konfiguracja projektu korzysta z poprawionego PostCSS `8.5.x`, ale wewnętrznej kopii Next.js nie należy ręcznie podmieniać. Po publikacji poprawki upstream należy zaktualizować Next.js i ponownie uruchomić audyt. ```text
app/ routing, layout, route handlery i strony
components/ komponenty widoków, UI, stanów, prognozy, hydro i ostrzeżeń
hooks/ hooki TanStack Query
lib/ fetchery, normalizacja danych, Web Push, i18n, store i helpery
types/ typy danych IMGW, prognozy, lokalizacji i powiadomień
public/ manifest, service worker i ikony PWA
scripts/ worker powiadomień dla self-hostingu
docs/ szczegółowa dokumentacja techniczna
```
## Dokumentacja
- [Architektura i przepływ danych](docs/architecture.md)
- [Wewnętrzne endpointy API](docs/api.md)
- [Źródła danych i cache](docs/data-sources.md)
- [Logika pogody i fallbacki](docs/weather-logic.md)
- [Powiadomienia Web Push](docs/notifications.md)
- [Wdrożenie i uruchamianie](docs/deployment.md)
## Status i Ograniczenia
- `lib/push-store.ts` przechowuje subskrypcje Web Push w pamięci procesu. Produkcyjne wdrożenie wymaga trwałego magazynu, np. bazy danych albo KV.
- Endpoint IMGW Hybrid używany przez dashboard pochodzi z publicznego frontendu `meteo.imgw.pl`, a nie ze stabilnie opisanej dokumentacji `danepubliczne.imgw.pl`.
- Publiczne API IMGW potrafi zwrócić `404` z komunikatem `No products were found` dla pustych list ostrzeżeń; aplikacja traktuje to jako brak ostrzeżeń.
- Prognoza jest prognozą modelową, nie pomiarem IMGW. Bieżące pomiary i prognozy są w UI rozdzielane.

66
docs/api.md Normal file
View File

@@ -0,0 +1,66 @@
# Wewnętrzne Endpointy API
Wszystkie zewnętrzne źródła danych są wywoływane przez route handlery Next.js w `app/api`. Dzięki temu aplikacja ma jedno miejsce na walidację parametrów, cache, normalizację błędów i obsługę ograniczeń przeglądarki.
## Dane Pogodowe i Lokalizacje
| Metoda | Endpoint | Przeznaczenie |
| --- | --- | --- |
| `GET` | `/api/forecast?latitude={lat}&longitude={lon}` | Zwraca 7-dniową prognozę modelową dla współrzędnych. Łączy IMGW ALARO z Open-Meteo. Niepoprawne współrzędne zwracają `400`, awaria źródła `502`. |
| `GET` | `/api/imgw-current?latitude={lat}&longitude={lon}` | Pobiera surową lokalną analizę IMGW Hybrid dla współrzędnych. Niepoprawne współrzędne zwracają `400`, awaria źródła `502`. |
| `GET` | `/api/imgw/{path}` | Proxy allowlistowanych endpointów IMGW `danepubliczne.imgw.pl`. Obsługuje kolekcje `synop`, `hydro`, `meteo`, `warningsmeteo`, `warningshydro`, `product` oraz szczegół `synop/id/{id}`. Nieobsługiwana ścieżka zwraca `404`. |
| `GET` | `/api/locations/search?query={query}&language={pl\|en}` | Wyszukuje miejscowości w Polsce przez Open-Meteo Geocoding. Zapytania krótsze niż 2 znaki albo dłuższe niż 80 znaków zwracają pustą listę. |
| `GET` | `/api/locations/reverse?latitude={lat}&longitude={lon}&language={pl\|en}` | Ustala nazwę miejsca dla pozycji GPS przez Nominatim / OpenStreetMap. Współrzędne są zaokrąglane do trzech miejsc po przecinku. |
## Powiadomienia
Endpointy powiadomień działają w runtime Node.js, bo korzystają z `web-push` i pamięciowego magazynu subskrypcji.
| Metoda | Endpoint | Przeznaczenie |
| --- | --- | --- |
| `GET` | `/api/notifications/vapid-key` | Zwraca publiczny klucz VAPID oraz informację, czy Web Push jest skonfigurowany. |
| `POST` | `/api/notifications/subscriptions` | Zapisuje lub aktualizuje subskrypcję Web Push dla urządzenia. Przyjmuje preferencje ostrzeżeń, briefu porannego, briefu wieczornego, lokalizację, język, województwo i opcjonalny powiat TERYT. |
| `DELETE` | `/api/notifications/subscriptions` | Usuwa subskrypcję Web Push po jej endpointcie. |
| `POST` | `/api/notifications/test` | Wysyła powiadomienie testowe na wskazany endpoint subskrypcji. |
| `GET` | `/api/notifications/check` | Endpoint harmonogramu sprawdzający nowe ostrzeżenia meteorologiczne IMGW i wysyłający Web Push do pasujących subskrypcji. |
| `GET` | `/api/notifications/daily-brief` | Endpoint harmonogramu wysyłający raz dziennie poranny brief dla subskrypcji z włączoną opcją i zapisaną lokalizacją. |
| `GET` | `/api/notifications/tomorrow-brief` | Endpoint harmonogramu wysyłający wieczorny brief z prognozą na kolejny dzień. |
## Autoryzacja Harmonogramu
Endpointy harmonogramu:
- `/api/notifications/check`
- `/api/notifications/daily-brief`
- `/api/notifications/tomorrow-brief`
w produkcji wymagają sekretu `NOTIFICATIONS_CRON_SECRET` przekazanego jednym z nagłówków:
```http
Authorization: Bearer <NOTIFICATIONS_CRON_SECRET>
x-cron-secret: <NOTIFICATIONS_CRON_SECRET>
```
Jeśli `NOTIFICATIONS_CRON_SECRET` nie jest ustawiony, endpointy harmonogramu są dostępne tylko poza `NODE_ENV=production`.
## Cache i Odpowiedzi
| Endpoint | Cache |
| --- | --- |
| `/api/forecast` | `s-maxage=900`, `stale-while-revalidate=1800` |
| `/api/imgw-current` | `s-maxage=120`, `stale-while-revalidate=300` |
| `/api/imgw/{path}` | `s-maxage=300`, `stale-while-revalidate=600` |
| `/api/locations/search` | `s-maxage=86400`, `stale-while-revalidate=604800` |
| `/api/locations/reverse` | `s-maxage=86400`, `stale-while-revalidate=604800` |
Route handlery zwracają kontrolowane odpowiedzi JSON. Błędy zewnętrznych usług są mapowane na czytelne statusy HTTP, zwykle `400`, `404`, `502` albo `503`.
## Szczególny Przypadek IMGW
Publiczne endpointy ostrzeżeń IMGW potrafią zwrócić HTTP `404` z treścią:
```json
{"status":false,"message":"No products were found"}
```
Dla `warningsmeteo` i `warningshydro` aplikacja traktuje ten wariant jako poprawną pustą listę ostrzeżeń, a nie awarię źródła danych.

71
docs/architecture.md Normal file
View File

@@ -0,0 +1,71 @@
# Architektura i Przepływ Danych
`wtr.` jest aplikacją Next.js App Router z komponentami React, cache'owaniem przez TanStack Query i stanem preferencji w Zustand.
## Struktura
```text
app/ routing, layout, route handlery i strony
components/ komponenty widoków, UI, stanów, prognozy, hydro i ostrzeżeń
hooks/ hooki TanStack Query
lib/ fetchery, normalizacja danych, Web Push, i18n, store i helpery
types/ typy danych IMGW, prognozy, lokalizacji i powiadomień
public/ manifest, service worker i ikony PWA
scripts/ worker powiadomień dla self-hostingu
docs/ dokumentacja techniczna
```
## Routing
Najważniejsze widoki:
- `/` - dashboard pogody, wyszukiwarka lokalizacji, hero, prognoza, briefy i ostrzeżenia regionalne,
- `/warnings` - pełny widok ostrzeżeń meteo i hydro,
- `/hydro` - stacje hydrologiczne,
- `/settings` - język, motyw, lokalizacja powiadomień i Web Push,
- `/station/[id]` - szczegóły stacji,
- `/offline` - fallback offline.
## Przepływ Danych
1. Komponenty UI wywołują hooki z `hooks/`.
2. Hooki używają TanStack Query i fetcherów z `lib/`.
3. Fetchery pobierają dane przez route handlery w `app/api/`.
4. Route handlery walidują parametry, odpytują zewnętrzne usługi, ustawiają cache i normalizują błędy.
5. Moduły w `lib/` normalizują odpowiedzi do typów z `types/`.
6. Komponenty prezentują loading, error, retry oraz empty states.
## Stan Aplikacji
Zustand w `lib/store.ts` przechowuje trwałe preferencje użytkownika w `localStorage`, m.in.:
- ulubione stacje,
- wybraną stację albo lokalizację,
- ustawienia powiadomień,
- tryb wyboru województwa dla alertów.
Język interfejsu jest przechowywany osobno w `localStorage` pod kluczem `wtr:language`.
## PWA i Offline
Manifest znajduje się w `public/manifest.json`, a service worker w `public/sw.js`.
Service worker:
- cache'uje powłokę aplikacji,
- obsługuje fallback `/offline` dla nawigacji,
- cache'uje wybrane odpowiedzi API: `/api/imgw/*`, `/api/imgw-current`, `/api/forecast`,
- obsługuje zdarzenia `push` i kliknięcia w powiadomienia.
Rejestracja service workera działa w buildzie produkcyjnym.
## i18n
Interfejs jest dostępny po polsku i angielsku. Teksty są w `lib/i18n.tsx`. Nazwy stacji i oryginalne treści IMGW nie są automatycznie tłumaczone.
## Zasady Integracji
- Dane zewnętrzne przechodzą przez route handlery Next.js.
- IMGW pozostaje źródłem bieżących pomiarów, hydro i ostrzeżeń.
- Prognoza modelowa jest opisana oddzielnie i nie jest przedstawiana jako pomiar IMGW.
- Braki danych są pokazywane jawnie, bez generowania fikcyjnych wartości.

58
docs/data-sources.md Normal file
View File

@@ -0,0 +1,58 @@
# Źródła Danych i Cache
`wtr.` korzysta z publicznych źródeł danych pogodowych i lokalizacyjnych. Dane zewnętrzne są pobierane przez route handlery Next.js, a UI pokazuje brak danych jawnie zamiast uzupełniać je estymacją.
## IMGW
| Dane | Źródło |
| --- | --- |
| Bieżąca analiza IMGW Hybrid | `https://meteo.imgw.pl/api/v1/forecast/fcapi` |
| Prognoza godzinowa IMGW ALARO | `https://meteo.imgw.pl/api/v1/forecast/fcapi?m=alaro` |
| Dane synoptyczne | `https://danepubliczne.imgw.pl/api/data/synop` |
| Pojedyncza stacja synoptyczna | `https://danepubliczne.imgw.pl/api/data/synop/id/{id}` |
| Dane hydrologiczne | `https://danepubliczne.imgw.pl/api/data/hydro/` |
| Ostrzeżenia meteorologiczne | `https://danepubliczne.imgw.pl/api/data/warningsmeteo` |
| Ostrzeżenia hydrologiczne | `https://danepubliczne.imgw.pl/api/data/warningshydro` |
| Dane meteorologiczne | `https://danepubliczne.imgw.pl/api/data/meteo/` |
| Lista produktów | `https://danepubliczne.imgw.pl/api/data/product` |
IMGW jest traktowane jako źródło bieżących pomiarów, hydro i ostrzeżeń. Prognoza modelowa jest w interfejsie rozdzielona od pomiarów.
## Open-Meteo
Open-Meteo Forecast API (`https://api.open-meteo.com/v1/forecast`) dostarcza pełny 7-dniowy horyzont prognozy, prawdopodobieństwo opadu i fallback, jeśli IMGW ALARO nie odpowiada.
Open-Meteo Geocoding API (`https://geocoding-api.open-meteo.com/v1/search`) służy do wyszukiwania miejscowości w Polsce.
Przed większym lub komercyjnym wdrożeniem należy sprawdzić aktualne warunki korzystania z Open-Meteo albo zastąpić usługę własnym dostawcą.
## Nominatim / OpenStreetMap
Opcjonalny reverse geocoding GPS korzysta z publicznego endpointu:
```text
https://nominatim.openstreetmap.org/reverse
```
Wywołanie następuje wyłącznie po zgodzie użytkownika. Interfejs pokazuje atrybucję OpenStreetMap. Pozycja GPS jest zaokrąglana do trzech miejsc po przecinku, czyli około 100 metrów.
Przed wdrożeniem o większym ruchu należy sprawdzić aktualną politykę użycia publicznej instancji Nominatim albo użyć własnej usługi.
## Cache
| Dane | Cache |
| --- | --- |
| IMGW Hybrid | 120 sekund, `stale-while-revalidate=300` |
| Prognoza modelowa | 900 sekund, `stale-while-revalidate=1800` |
| Proxy IMGW `danepubliczne.imgw.pl` | 300 sekund, `stale-while-revalidate=600` |
| Wyszukiwanie miejscowości | 24 godziny, `stale-while-revalidate=7 dni` |
| Reverse geocoding | 24 godziny, `stale-while-revalidate=7 dni` |
Service worker dodatkowo może cache'ować odpowiedzi `GET` dla `/api/imgw/*`, `/api/imgw-current` i `/api/forecast`, aby ostatnio pobrane dane były dostępne przy problemach z siecią.
## Ograniczenia Źródeł
- Endpoint IMGW Hybrid jest częścią publicznego frontendu `meteo.imgw.pl`, ale nie jest opisany w stabilnej dokumentacji `danepubliczne.imgw.pl`.
- Publiczny endpoint synoptyczny IMGW zwraca najnowszy pomiar, a nie historię odczytów.
- IMGW ALARO zwykle dostarcza krótszy horyzont godzinowy niż 7 dni; Open-Meteo uzupełnia dalszy zakres.
- `wtr.` nie generuje fikcyjnych danych pogodowych.

83
docs/deployment.md Normal file
View File

@@ -0,0 +1,83 @@
# Wdrożenie i Uruchamianie
## Lokalnie
Wymagany jest Node.js 20.9 lub nowszy.
```bash
npm install
npm run dev
```
Domyślny adres lokalny:
```text
http://localhost:3000
```
## Build Produkcyjny
```bash
npm run build
npm run start
```
`npm run build` uruchamia build Next.js oraz kontrolę TypeScript wykonywaną przez Next.js.
## Self-Hosting z Workerem Powiadomień
Worker powiadomień musi działać obok uruchomionej aplikacji:
```bash
npm run start
npm run notifications:worker
```
Jeśli aplikacja działa na innym porcie, ustaw `WTR_APP_URL`:
```bash
WTR_APP_URL=http://127.0.0.1:4000 npm run notifications:worker
```
Worker sam wczytuje `.env` i `.env.local`, ale zmienne ustawione w shellu mają pierwszeństwo.
## Vercel
1. Umieść repozytorium w serwisie Git.
2. Importuj projekt do Vercel jako aplikację Next.js.
3. Nie dodawaj kluczy API dla danych pogodowych: publiczne endpointy IMGW, Open-Meteo i Nominatim nie wymagają ich w obecnej integracji.
4. Wdróż standardowym buildem `npm run build`.
Proxy IMGW działa jako route handler Next.js i jest zgodne z hostingiem Vercel.
Jeśli używasz powiadomień na Vercel, endpointy harmonogramu muszą być wywoływane zewnętrznym cronem albo innym schedulerem. Sam route handler nie uruchamia się cyklicznie bez zewnętrznego wywołania.
## Zmienne Środowiskowe
```bash
WEB_PUSH_VAPID_PUBLIC_KEY=
WEB_PUSH_VAPID_PRIVATE_KEY=
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
NOTIFICATIONS_CRON_SECRET=
WTR_APP_URL=http://127.0.0.1:3000
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
```
Do zwykłego uruchomienia aplikacji pogodowej zmienne Web Push nie są wymagane. Są potrzebne dopiero dla powiadomień.
## Jakość
Przed zakończeniem zmian uruchamiaj:
```bash
npm run lint
npm run build
```
Repozytorium nie ma obecnie osobnego skryptu testów, osobnego skryptu type-check ani formattera.
## Bezpieczeństwo Zależności
Projekt używa Next.js `16.2.6`. Znany status zależności obejmuje umiarkowane zgłoszenie `GHSA-qx2v-qp2m-jg93` dla PostCSS `8.4.31` bundlowanego bezpośrednio przez Next.js. Główna konfiguracja projektu korzysta z PostCSS `8.4.49`. Po publikacji poprawki upstream należy zaktualizować Next.js i ponownie uruchomić audyt.

117
docs/notifications.md Normal file
View File

@@ -0,0 +1,117 @@
# Powiadomienia Web Push
Powiadomienia obejmują:
- nowe ostrzeżenia meteorologiczne IMGW,
- poranny brief dnia,
- wieczorny brief z prognozą na jutro,
- testowe powiadomienie wysyłane z `/settings`.
Konfiguracja użytkownika znajduje się w widoku `/settings`.
## Wymagania
Do wysyłki Web Push potrzebne są klucze VAPID:
```bash
WEB_PUSH_VAPID_PUBLIC_KEY=
WEB_PUSH_VAPID_PRIVATE_KEY=
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
```
Klucze można wygenerować poleceniem:
```bash
npx web-push generate-vapid-keys
```
Endpointy harmonogramu wymagają też sekretu crona w produkcji:
```bash
NOTIFICATIONS_CRON_SECRET=
```
## Preferencje Subskrypcji
Subskrypcja Web Push zapisuje:
- endpoint subskrypcji przeglądarki,
- województwo,
- język interfejsu,
- czy ostrzeżenia są aktywne,
- czy aktywny jest brief poranny,
- czy aktywny jest brief na jutro,
- współrzędne i nazwę lokalizacji,
- opcjonalny powiat TERYT.
Ostrzeżenia meteo są filtrowane po powiecie TERYT, jeśli lokalizacja go dostarcza. W przeciwnym razie używany jest fallback wojewódzki.
## Worker Self-Hosted
Worker powiadomień uruchamia się osobno od aplikacji:
```bash
npm run notifications:worker
```
Wymaga równolegle działającej aplikacji Next.js. Domyślnie odpytuje:
```bash
http://127.0.0.1:3000
```
Jeśli aplikacja działa pod innym adresem albo portem, ustaw:
```bash
WTR_APP_URL=http://127.0.0.1:4000
```
Worker wczytuje `.env` i `.env.local`. Zmienne ustawione bezpośrednio w shellu mają pierwszeństwo.
## Harmonogram
Domyślna konfiguracja:
```bash
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
```
Worker:
- sprawdza nowe ostrzeżenia co 5 minut,
- po 07:00 czasu `Europe/Warsaw` wywołuje `/api/notifications/daily-brief`,
- po 18:00 czasu `Europe/Warsaw` wywołuje `/api/notifications/tomorrow-brief`,
- nie blokuje jednego harmonogramu błędem drugiego.
Endpointy harmonogramu nie uruchamiają się same bez workera albo zewnętrznego crona.
## Zewnętrzny Cron
Jeśli nie używasz `npm run notifications:worker`, zewnętrzny cron powinien wywoływać:
```text
GET /api/notifications/check
GET /api/notifications/daily-brief
GET /api/notifications/tomorrow-brief
```
W produkcji dodaj jeden z nagłówków:
```http
Authorization: Bearer <NOTIFICATIONS_CRON_SECRET>
x-cron-secret: <NOTIFICATIONS_CRON_SECRET>
```
## Platformy
- iOS/iPadOS wymaga dodania PWA do ekranu początkowego i uruchomienia aplikacji z ikony.
- Android i desktop nie są blokowane wymogiem `standalone`, jeśli przeglądarka obsługuje Web Push.
- GPS i powiadomienia wymagają bezpiecznego kontekstu HTTPS poza wyjątkami typu `localhost`.
## Ograniczenia
Obecny `lib/push-store.ts` przechowuje subskrypcje i historię wysyłek w pamięci procesu. Po restarcie procesu te dane znikają. Produkcja wymaga trwałego magazynu, np. bazy danych albo KV.
Powiadomienie testowe jest wysyłane na konkretny endpoint subskrypcji przekazany z `/settings`, a nie broadcastowane do wszystkich urządzeń.

95
docs/weather-logic.md Normal file
View File

@@ -0,0 +1,95 @@
# Logika Pogody i Fallbacki
`wtr.` rozdziela bieżące pomiary, lokalną analizę IMGW Hybrid, prognozę modelową i ostrzeżenia. Prognoza nie jest przedstawiana jako pomiar IMGW.
## Bieżące Warunki
Dashboard hero korzysta z publicznego endpointu IMGW Hybrid oficjalnego portalu `meteo.imgw.pl` przez `/api/imgw-current`.
Normalizacja w `lib/imgw-current-api.ts`:
- bierze pod uwagę tylko rekordy `Type_Ten_Minutes` i `Type_Hour`,
- wymaga realnych wartości liczbowych dla pełnego rekordu pogodowego,
- nie traktuje `null` jako kompletnej wartości,
- preferuje pełny rekord 10-minutowy,
- jeśli pełny rekord 10-minutowy jest o ponad 2 godziny starszy od pełnego rekordu godzinowego, wybiera świeższy rekord godzinowy,
- zachowuje lokalny opad MERGE jako częściową analizę, jeśli nie ma pełnych parametrów.
Jeśli Hybrid nie dostarcza pełnych danych, UI jawnie korzysta z fallbacku `synop`. Gdy najbliższa stacja jest oddalona o co najmniej 30 km, aplikacja ostrzega, że lokalne warunki mogą się różnić.
## Prognoza Modelowa
Route handler `/api/forecast` pobiera równolegle:
- pełne 7 dni Open-Meteo,
- godzinowe IMGW ALARO.
W godzinach pokrytych przez ALARO parametry IMGW mają pierwszeństwo. Open-Meteo dostarcza prawdopodobieństwo opadu i dalszy horyzont do 7 dni. Awaria ALARO pozostawia działający fallback Open-Meteo.
Dashboard pokazuje:
- regułowy brief dnia,
- najbliższe 24 przyszłe godziny,
- wykresy pełnego bieżącego dnia.
Widok szczegółowy dnia korzysta z pełnego zestawu godzin dla wybranej daty.
## Ostrzeżenia
Ostrzeżenia meteorologiczne IMGW zawierają kody powiatów TERYT. Ostrzeżenia hydrologiczne zawierają jawne województwa. Normalizacja odbywa się przez `lib/provinces.ts` i `lib/warning-regions.ts`.
Zasady UI:
- lokalny obszar jest priorytetyzowany,
- ostrzeżenia meteo są pokazywane przed hydrologicznymi,
- jeśli lokalizacja ma powiat TERYT, meteo jest filtrowane po powiecie,
- jeśli powiat nie jest znany, stosowany jest fallback wojewódzki,
- dashboard pokazuje kompaktowo tylko aktywne i nadchodzące ostrzeżenia meteo dla wybranego obszaru,
- wygasłe ostrzeżenia są filtrowane po `validTo` względem czasu przeglądarki.
## Briefy
Brief dnia i brief na jutro są generowane deterministycznie w `lib/weather-brief.ts`. Nie są odpowiedzią modelu AI i nie wymagają klucza OpenAI API.
Brief dnia analizuje najbliższe 24 godziny prognozy modelowej oraz aktywne i nadchodzące ostrzeżenia meteo dla obszaru użytkownika.
Brief na jutro analizuje pełny jutrzejszy dzień prognozy. Może informować o burzach na podstawie kodów pogodowych modelu nawet wtedy, gdy IMGW nie opublikowało oficjalnego ostrzeżenia.
## Opad i Pomiary Synoptyczne
Pole `synop.suma_opadu` jest akumulowaną sumą opadu. Nie oznacza, że pada w tej chwili, i nie steruje animacją deszczu.
IMGW Hybrid dostarcza m.in. opad 10-minutowy. Ten parametr jest prezentowany oddzielnie od akumulowanej sumy opadu stacji.
## Stany Pogody
Prognoza godzinowa i dzienna rozpoznaje:
| Stan | Opis w interfejsie |
| --- | --- |
| `clear` | Bezchmurnie |
| `partlyCloudy` | Częściowe zachmurzenie |
| `cloudy` | Pochmurno |
| `fog` | Mgła |
| `drizzle` | Mżawka |
| `rain` | Opady deszczu |
| `snow` | Opady śniegu |
| `thunderstorm` | Burza |
| `unknown` | Brak opisu |
Bieżąca analiza IMGW Hybrid rozpoznaje bezpośrednio opad deszczu, śnieg i burzę. Gdy żadne z tych zjawisk nie występuje, hero może pokazać pomocniczy opis: `Silny wiatr`, `Wilgotne warunki` albo `Spokojne warunki`.
## Mood i Efekty Wizualne
Hero aktualnej pogody używa uproszczonego moodu do wyboru ikony, tekstu i małego akcentu stanu.
| Mood | Reguła |
| --- | --- |
| `night` | godzina przed `06:00` lub od `21:00` |
| `wind` | wiatr od `8 m/s` |
| `cold` | temperatura do `3°C` |
| `cloudy` | wilgotność od `80%` |
| `warm` | temperatura od `20°C` |
| `mild` | pozostałe przypadki |
Warstwa efektów wizualnych jest ograniczona do subtelnych efektów informacyjnych: kropli przy lokalnym opadzie oraz błysku przy burzy. Mood hero jest heurystyką, a nie pełną klasyfikacją sterowaną kodem warunków IMGW Hybrid.