Przegląd techniczny · stan na 20.07.2026
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ę.
%%{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
Wszystko z otwartych źródeł, bez płatnych API pogodowych
.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).Budzik poza GitHubem — bo cron GitHuba potrafi spóźnić się 100 minut
:15 i :35), etap 2, osobny harmonogram ECMWF.POST workflow_dispatch do GitHub API (fine-grained PAT
trzymany jako sekret Workera).Wynajmowany na minuty runner — płacimy tylko za czas renderu
eccodes) i renderuje
mapy (matplotlib) do WebP, równolegle na wszystkich rdzeniach.--skip-existing.city_labels.json, rysowany wektorowo w przeglądarce
(skalowalna typografia, przełącznik °F/°C działa na mapie).Jeden magazyn, zero kosztów transferu
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ść).Trzy fronty na Cloudflare Pages, deploy = sekundy (1 plik HTML)
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.