Hoe de score wordt berekend
Een precieze, licht nerdy uitleg van elke invoer, formule, drempelwaarde en realitycheck die de 0–10 van vanavond produceert. Voor mensen die de bonnetjes willen zien.
De vorm ervan
De score is één getal van 0 tot 10 dat één vraag beantwoordt: "is de hemel vanavond de moeite waard om naar buiten te gaan?" Twee fysieke realiteiten tellen voor sterren-kijken met het blote oog — wolken die het zicht blokkeren, en een felle maan die de zwakkere sterren wegspoelt. Alles wat verder telt is daar afgeleid van.
We berekenen de score in zes kleine stappen op basis van een weersvoorspelling, en doen daarna twee realitychecks: één tegen meerdere onafhankelijke modellen, één tegen daadwerkelijke vliegveldwaarnemingen na zonsondergang. Een aparte gebruikersfeedback-loop kalibreert regionale afwijkingen op de lange termijn.
1. Wolkenvoorspelling (Open-Meteo)
We halen een uurlijkse 8-daagse voorspelling op bij Open-Meteo voor jouw exacte lat/lng. Belangrijk: we vragen de bewolking opgesplitst per hoogte op, niet alleen het samengevatte "totaal" dat de meeste weer-apps tonen.
Elke band is een percentage in [0, 100] en ze overlappen — een hemel met 60% lage wolken en 40% hoge cirrus rapporteert als beide, niet als 100%. Door de split kunnen we hoogtes anders wegen in stap 2.
// Open-Meteo, per location, every hour: cloud_cover_low // 0–100% (< 6500 ft / ~2 km AGL) cloud_cover_mid // 0–100% (6500–20000 ft / 2–6 km) cloud_cover_high // 0–100% (> 20000 ft / 6+ km, mostly cirrus)
2. Laag-weging (laag > midden > hoog)
Voor sterren-kijken met het blote oog zijn lage wolken de vijand. Stratus en stratocumulus blokkeren de hele hemel erachter. Middenwolken (altostratus, altocumulus) dimmen de meeste sterren. Hoge dunne cirrus daarentegen vervaagt slechts licht — je ziet Orion er nog doorheen.
weightedCloud =
cloud_low × 0.5 // blocks everything
+ cloud_mid × 0.3 // blocks most stars
+ cloud_high × 0.2 // thin cirrus dims, doesn't blockDus wegen we op hoogte voordat we middelen. Een hemel vol cirrus en een hemel vol stratus geven heel andere kijkervaring; het ongewogen "totale bewolking"-getal dat de meeste apps gebruiken kan dat niet onderscheiden.
3. Tijds-weging (piekuren > late nacht)
Sterrenkijkers beslissen op wat ze om 22:00 zullen zien, niet om 03:30. Dus wanneer we de nacht middelen, wegen we de "piekuren" (21:00 → 00:59 lokaal) 2× zwaarder dan de late-nacht-staart. Een heldere 04:00 kan een bewolkte 22:00 niet meer omhoogtrekken met een halve punt.
headlineCloud = Σ ( weightedCloud(h) × w(h) ) / Σ w(h)
w(h) = 2 if 21 ≤ h ≤ 24 or h = 0 // prime hours
= 1 otherwise // late-night tailDe uren die we middelen zijn het astronomische-duisternis-venster — zon ≥ 18° onder de horizon, wanneer alle schemering weg is. Dat kan in de zomer ver na 04:00 doorlopen, en daarom telt de tijds-weging het zwaarst in mei–augustus.
4. Maan-penalty (verlichting, met late-nacht-korting)
Een felle maan maakt de hemel niet donkerder — maar overstemt wel alles behalve de helderste sterren. We passen een penalty toe evenredig met verlichting, in drie niveaus:
- 0% penalty als verlichting < 25% (nieuwe maan en sikkel).
- verlichting × 0,5 bij 25–75% (rond eerste/laatste kwartier).
- verlichting × 1,0 boven 75% (afnemend gibbous en vol).
Als het beste 2-uurs venster start tussen 00:00 en 03:00 EN verlichting boven 50% is, vermenigvuldigen we de penalty met 0,6 — de maan zit waarschijnlijk te ondergaan tijdens het venster, dus de werkelijke hemelimpact is lager dan de ruwe verlichting suggereert.
5. Zicht-gate
Zelfs met een perfecte nieuwe-maan-hemel mag een bewolkte nacht niet boven nul scoren. De zicht-helling forceert dat: van volledig bewolkt (helderheid 0%) tot 30% helder schaalt de score lineair naar volle waarde. Onder 30% helderheid wordt zelfs een perfecte donkere hemel met minder dan 1 vermenigvuldigd.
clearness = 100 − headlineCloud visibility = min(1, clearness / 30) // clearness 0–30% → visibility ramps 0 → 1 // clearness 30%+ → visibility = 1
Zonder deze gate scoorde een 100%-bewolkte nacht met nieuwe maan ~4/10 alleen al uit de maan-donkerheid-term — fysiek onmogelijk. Nu scoort dat correct 0,0.
6. De koplijn-formule
darkness = 100 − moonPenalty
score = visibility × ( clearness × 0.6 + darkness × 0.4 ) / 10
// clamped to 0–10, rounded to 1 decimalDat is alles voor de koplijn. De wolkenterm krijgt 60% van het gewicht, de maan-donkerheid-term 40%, en de zicht-gate vetoot het geheel als er teveel wolken zijn. Dezelfde formule berekent de uurlijkse tijdlijn, het rollende 2-uurs Piek-venster, en het vaste 21:00–01:00 Prime-venster.
Twee realitychecks erbovenop
Eén enkele voorspelling kan met overtuiging fout zitten. Twee lagen boven de koplijn vangen dat op.
Geen van de checks tilt de score omhoog. Ze vlaggen alleen onzekerheid (multi-model) of trekken de score richting waargenomen werkelijkheid (nowcast).
Realitycheck 1: meningsverschil tussen modellen
Open-Meteo geeft graag dezelfde voorspelling apart uit drie verschillende globale weermodellen. Wij vragen alle drie en vergelijken wat ze zeggen over de piekuren.
// Three independent global numerical weather models:
// ECMWF IFS (European Centre, broadly best in Europe)
// GFS (NOAA, best US coverage)
// ICON-EU (Deutscher Wetterdienst regional)
primeSpread = max pairwise | model_i − model_j |
over hours 21:00 → 01:00 local
confidence = "low" if primeSpread > 25
= "high" otherwiseVerschillen ze gemiddeld meer dan 25 procentpunt over 21:00–01:00, dan is de voorspelling wankel. De score gaat alsnog door, maar de kaart laat een "Lage zekerheid"-label zien zodat je weet om naar buiten te kijken voordat je commit.
Realitycheck 2: nowcast-clamp na zonsondergang
Zodra de zon onder is, is "vanavond" geen voorspelling meer — het wordt waarneembare werkelijkheid. We halen de meest recente METAR (verplichte uurlijkse waarneming van elk commercieel vliegveld) binnen ~65 km van jou, en parseren de FEW/SCT/BKN/OVC laagcodes naar dezelfde laag/midden/hoog split die we intern gebruiken.
Als de waargenomen gewogen bewolking flink hoger is dan wat de voorspelling middelde voor vanavond, verlagen we de koplijn-score. De drempels zijn afhankelijk van de bron: METAR is echte meting dus vertrouwen we kleine afwijkingen; Open-Meteo current is model + stationmix (hetzelfde model dat de voorspelling produceerde), dus daar houden we een conservatievere drempel om model-fout niet dubbel te tellen.
// Post-sunset only.
// Source priority:
// 1. METAR via aviationweather.gov bbox (~65 km, 130 km fallback)
// 2. Open-Meteo current (model + station blend, last resort)
// Trigger thresholds depend on source trust:
// METAR: Δ > 15 pts AND observed > 35%
// Open-Meteo cur.: Δ > 30 pts AND observed > 50%
// If triggered:
score = min( forecastScore, observationScore )
// never raised — only loweredCruciaal: de clamp verlaagt alleen, nooit verhogen. Als de werkelijkheid er beter uitziet dan de voorspelling tillen we niet op — de voorspelling had haar kans, en overbeloven schaadt vertrouwen meer dan onderbeloven.
Kalibratie: elke alert-mail is een leerlus
Modellen drijven. Specifieke steden hebben specifieke bugs (zeebries-nachten in Amsterdam, late inversies in Salt Lake, marine layer in kust-Californië). De enige manier om de lokale afwijking van een model te leren is voorspelling vs. werkelijkheid vergelijken.
Dus elke alert-mail bevat drie knoppen: ✨ Helder, 🌤 Deels, ☁️ Bewolkt. Elk is een HMAC-getekende link met de hele voorspellingscontext — geen login, geen DB-lookup, één tik. We slaan de klik op in starsout_feedback.
// Each alert email contains 3 buttons. Each button is an
// HMAC-SHA256 signed link carrying the entire prediction context:
{
v: 1,
sub: <subscriber_id>,
d: "2026-05-07",
s: 8.2, // predicted score
c: 18, // predicted cloud cover %
lat, lng,
conf: "high",
src: "metar",
city, locale,
exp: <unix epoch + 7 days>,
o: "clear" | "partly" | "cloudy"
}
// Token is stand-alone: no DB lookup needed to validate.
// One click → upsert into starsout_feedback.
// Per-city rollup (view starsout_feedback_calibration):
hit_rate(city, month) =
100 × clear_or_partly_alerts_with_score≥7 / total_alerts_with_score≥7Een wekelijkse job rolt feedback per stad per maand op. Bij ≥4 weken feedback per stad hebben we genoeg signaal om per-stad bias-correctie live in de score toe te passen. Tot dan publiceren we alleen de kalibratie-view intern en houden we systemische missers in de gaten.
Wat niet in de score zit (en waarom)
- Lichtvervuiling (Bortle-klasse): bepaalt de "Vanavond zichtbaar"-deep-sky-lijst — welke sterrenstelsels en nevels haalbaar zijn vanaf jouw plek — maar verschuift de koplijn niet. Het getal is "werkt de hemel mee?", niet "is jouw hemel ongerept?".
- Wind, vochtigheid, neerslag: zichtbaar in de tijdlijn als je scrollt, maar veranderen de score niet. Ze beïnvloeden comfort en seeing, niet of de hemel ondoorzichtig is voor sterlicht.
- Atmosferische seeing (turbulentie): telt voor telescoopgebruikers (planetair detail, dubbelster-splits) maar niet voor blote-oog-kijken. Vage proxies uit oppervlakteweer zijn niet betrouwbaar genoeg voor de koplijn.
- Aerosolen / bosbrandrook: echte driver van stermagnitude tijdens rookseizoen, maar nog niet in het model. Open-Meteo levert een deel hiervan; meenemen in de zicht-gate staat op de roadmap.
Bronnen
- Open-Meteo — uurlijkse bewolking + zonsopkomst + multi-model spread. Gratis, geen auth, naamsvermelding vereist.
- Aviation Weather Center — METAR-waarnemingen van elk commercieel vliegveld, gratis.
- Synodische maanfase-berekening — pure JS, referentiepunt 2000-01-06, periode 29,530588861 dagen. Geen externe API.
- Voorspellingen verversen ongeveer elke 30 minuten; METAR elk uur; multi-model fetch op elk score-verzoek, 10 minuten gecachet.
Waarom het werkt zoals het werkt
Een sterren-kijk-score moet eerlijk falen. De meeste weer-apps tonen "30% bewolking" als de hemel bewolkt is omdat hun model fout zit; hun UI suggereert dan een vertrouwen dat de data niet verdient. Wij tonen liever 7,9 met een "lage zekerheid"-pil dan 8,5 zonder voorbehoud.
De score wordt nooit perfect — atmosferische wetenschap is moeilijk en de meeste sterren-kijk-failmodes zijn hyper-lokaal. Maar elke laag hier bestaat omdat we de score op een specifieke manier hebben betrapt op liegen, ten minste één keer. De kalibratie-loop is hoe we het blijven leren.