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 block

Dus 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 tail

De 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 decimal

Dat 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"  otherwise

Verschillen 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 lowered

Cruciaal: 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≥7

Een 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.

Hoe de StarsOut-score wordt berekend · Methodologie