Przegląd techniczny · stan na 20.07.2026

Weathershot — architektura produkcji

Samoodświeżający się serwis map pogodowych i stron lokalizacji: dane z otwartych modeli NOAA i ECMWF, render w Pythonie na wynajętym 16-rdzeniowcu, publikacja przez Cloudflare. Zero własnych serwerów utrzymywanych ręcznie — wszystko zdarzeniowe, 8 przebudów na dobę.

8×/dobęautomatyczne przebudowy
~19 000klatek WebP na pełny bieg
2 × 7 × 13modele × regiony × warstwy
T+4h20świeży bieg na stronie
13,1 mlnzdjęć w bazie appki
~$60/mies.całkowity koszt compute
%%{init: {"flowchart": {"nodeSpacing": 50, "rankSpacing": 70}, "themeVariables": {"fontSize": "16px"}}}%%
flowchart TD
  subgraph ORK[" ORKIESTRACJA — budzik "]
    W["⏰ Cloudflare Worker — Cron Triggers UTC
strzela co do sekundy, 8×/dobę"] GH["GitHub Actions — prywatne repo
definicja pipeline'u"] end subgraph ZR[" ŹRÓDŁA — open data, bez płatnych API "] GFS["🌍 NOAA GFS 0.25° — AWS S3
sztuczka .idx + HTTP Range:
1,5 MB zamiast 500 MB na klatkę"] EC["🌍 ECMWF IFS 0.25° — open data
2 mirrory, failover, sklejanie zakresów"] FS["📸 Firestore appki Weathershot
13,1 mln zdjęć · konto read-only"] end subgraph CMP[" COMPUTE — wynajmowany na minuty "] UB["🐍 Runner Ubicloud 16 vCPU
Python: eccodes dekoduje GRIB2,
matplotlib renderuje WebP na 16 rdzeniach
publikacja dwuetapowa: T+4h20 / T+5h45"] LAB["🏙️ Generator landingów SEO
serie punktowe jedną passą
+ galerie zdjęć z rotacją"] end subgraph ST[" STORAGE / CDN — $0 "] R2["🪣 Cloudflare R2
~19 000 klatek WebP na bieg · manifesty
city_labels.json · kafelki meteo · logi statusu"] end subgraph FE[" FRONTENDY — Cloudflare Pages "] MAPS["🗺️ weathershot-maps
aplikacja map: animacja +
wektorowa warstwa miast, °F/°C"] MOCK["🏙️ weathershot-mockup
lab: 5 landingów miast (Clear Sky)"] WS["🌐 weathershot.com
zaślepka → przyszły dom SEO"] end U(("👥 użytkownicy
+ boty Google/AI")) W == "workflow_dispatch (PAT)" ==> GH GH ==> UB GFS --> UB EC --> UB FS --> LAB UB == "rclone copy — JSON-y na końcu
(miękka atomowość)" ==> R2 UB --> LAB LAB -- "statyczne strony po każdym renderze" --> MOCK GH -- "deploy index.html (sekundy)" --> MAPS R2 -- "hotlink klatek + JSON" --> MAPS R2 -- "hotlink klatek" --> MOCK MAPS ==> U MOCK ==> U WS --> U
Przepływ z góry na dół: budzik → źródła → render → magazyn → strony → użytkownicy. Każda strzałka działa bez człowieka.

Jak to działa — warstwa po warstwie

1 · Skąd dane

Wszystko z otwartych źródeł, bez płatnych API pogodowych

  • NOAA GFS (AWS S3): zamiast ściągać 500 MB GRIB-a na klatkę, czytamy indeks .idx i pobieramy HTTP Range tylko potrzebne pola — ~1,5 MB/klatkę. Biegi 00/06/12/18z, prognoza co 1 h do +48, dalej co 3 h do +384 (16 dni).
  • ECMWF IFS open data: dwa mirrory z automatycznym failoverem i sklejaniem zakresów bajtów (serwery dławią się w godzinie publikacji).
  • Zdjęcia użytkowników z Firestore appki Weathershot — każde z temperaturą i warunkami z chwili zrobienia. Dostęp: konto serwisowe wyłącznie do odczytu.

2 · Kto dyryguje

Budzik poza GitHubem — bo cron GitHuba potrafi spóźnić się 100 minut

  • Cloudflare Worker z Cron Triggers strzela co do sekundy: dwie próby etapu 1 (:15 i :35), etap 2, osobny harmonogram ECMWF.
  • Worker robi POST workflow_dispatch do GitHub API (fine-grained PAT trzymany jako sekret Workera).
  • Definicja pipeline'u mieszka w GitHub Actions (prywatne repo) — ale GitHub tylko wykonuje, nie decyduje kiedy.

3 · Gdzie się liczy

Wynajmowany na minuty runner — płacimy tylko za czas renderu

  • Ubicloud 16 vCPU: Python dekoduje GRIB2 (eccodes) i renderuje mapy (matplotlib) do WebP, równolegle na wszystkich rdzeniach.
  • Publikacja dwuetapowa: najpierw 0–120 h (świeży bieg na stronie ~T+4h20), potem komplet 16 dni (~T+5h45) z --skip-existing.
  • Klatki mapy są czyste — nazwy miast i temperatury to osobny plik city_labels.json, rysowany wektorowo w przeglądarce (skalowalna typografia, przełącznik °F/°C działa na mapie).
  • Ten sam bieg buduje też landingi SEO: ekstrakcja serii punktowych jedną passą po GRIB-ach + zdjęcia z Firestore z deterministyczną rotacją galerii (seed = miasto+data).

4 · Gdzie leży

Jeden magazyn, zero kosztów transferu

  • Cloudflare R2: ~19 tys. klatek WebP na bieg, manifesty, dane warstwy miast, kafelki meteo (klik w mapę → meteogram), logi statusu.
  • Upload przez rclone copy (nigdy sync — nic nie kasuje); JSON-y wgrywane na końcu, więc strona przełącza się na nowy bieg dopiero, gdy klatki już są (miękka atomowość).
  • Publiczny, anonimowy URL R2 — darmowy egress, brak limitu plików Pages.

5 · Gdzie widać

Trzy fronty na Cloudflare Pages, deploy = sekundy (1 plik HTML)

  • weathershot-maps — aplikacja map: czysty JS, animacja klatek hotlinkiem z R2, canvas z warstwą miast (progresywna czcionka, przerzedzanie etykiet wg szerokości okna, słupki opadu).
  • weathershot-mockup — lab 5 landingów miast (NYC / Denver / Cody / Honolulu / Szczecin) w designie „Clear Sky"; przebudowywany po każdym renderze GFS.
  • weathershot.com — na razie zaślepka; docelowy dom stron SEO (robots wpuszcza boty Google i AI).

6 · Jak się pilnuje

Trzy poziomy logów — zdrowie sprawdzalne jednym curlem

  • status/latest.json + miesięczna historia JSONL na R2 — każdy bieg dopisuje wynik (czas, tryb, czasy renderu), także awarie.
  • build_log.json labu — per miasto: liczba dni, zdjęć, klatek mapy, ostrzeżenia.
  • Panel dev-log w aplikacji: wiek danych, odliczanie do następnego biegu, lampki 🟢🟡🔴.
  • Wynik z ostatnich 5 dni: 100% biegów zakończonych sukcesem, zero interwencji ręcznych.