# wtr. **Pogoda z danych IMGW. Prosto. Pięknie. Aktualnie.** `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. 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. 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ść. 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. 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. ## Stack - Next.js z App Router i TypeScript - Tailwind CSS oraz komponenty w stylu shadcn/ui - Framer Motion - Recharts - Lucide React - TanStack Query - Zustand z trwałym stanem `localStorage` - web-push do wysyłki Web Push - własny service worker, manifest i offline fallback ## Uruchomienie Wymagany jest Node.js 20.9 lub nowszy. ```bash npm install npm run dev ``` Aplikacja będzie dostępna pod adresem `http://localhost:3000`. Sprawdzenie jakości i build produkcyjny: ```bash npm run lint npm run build npm run start ``` Worker powiadomień uruchamiany obok aplikacji: ```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`. ## 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 województwa użytkownika. Na tej podstawie tworzy dłuższy opis na dashboard oraz krótszą wersję do powiadomienia. 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 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ą, 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 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 ``` 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 i wysyła poranny brief po 07: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 ` albo `x-cron-secret`. Drugi cron, ustawiony na około 07:00 czasu polskiego, powinien wywoływać `GET /api/notifications/daily-brief` z tym samym nagłówkiem. ## Wdrożenie na Vercel 1. Umieść repozytorium w serwisie Git. 2. Importuj projekt do Vercel jako aplikację Next.js. 3. Nie dodawaj kluczy API: publiczne endpointy IMGW ich nie wymagają. 4. Wdróż standardowym poleceniem builda `npm run build`. Proxy IMGW działa jako route handler Next.js i jest zgodne z hostingiem Vercel. ## Bezpieczeństwo zależności 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.