cr8script · plate VIII.I
github ↗
plate VIII.I · live demo
i / iii

world weather

sixteen beloved cities, polled daily by a cr8script in CI · the snapshot is committed to this repo
last poll source open-meteo.com cities reporting
loading… reading weather-data.json
plate VIII.II · aggregate
ii / iii

at a glance

summary across the cities reporting
waiting on the live fetch…
plate VIII.III · the same in cr8script
iii / iii

the same flow, in cr8

this page renders via JS, with the exact polling script shown below

The snapshot above is produced by a cr8script running in this site's CI. It pulls all sixteen cities, aggregates with pipelines, and emits one JSON document on stdout. A daily cron redirects that to weather-data.json and commits it. The page just reads the committed file -- no third-party calls from your browser, no key, no proxy.

# poll_weather.cr8 -- emits a single JSON document on stdout containing
# current weather + aggregates for beloved world cities. Driven by the
# .github/workflows/poll-weather.yml cron once a day. Intentionally has
# no `show` other than the final json.stringify call so the workflow can
# redirect stdout straight into weather-data.json.
#
#   pip install cr8script
#   cr8script tools/poll_weather.cr8 > weather-data.json
#
# Source: Open-Meteo (https://open-meteo.com) -- free, no API key.

let cities = [
  { name: "Paris",          lat: 48.8566, lon: 2.3522 },
  { name: "London",         lat: 51.5072, lon: -0.1276 },
  { name: "New York",       lat: 40.7128, lon: -74.0060 },
  { name: "Tokyo",          lat: 35.6762, lon: 139.6503 },
  { name: "Rome",           lat: 41.9028, lon: 12.4964 },
  { name: "Barcelona",      lat: 41.3874, lon: 2.1686 },
  { name: "Sydney",         lat: -33.8688, lon: 151.2093 },
  { name: "Istanbul",       lat: 41.0082, lon: 28.9784 },
  { name: "Cape Town",      lat: -33.9249, lon: 18.4241 },
  { name: "Rio de Janeiro", lat: -22.9068, lon: -43.1729 },
  { name: "Bangkok",        lat: 13.7563, lon: 100.5018 },
  { name: "Singapore",      lat: 1.3521, lon: 103.8198 },
  { name: "Dubai",          lat: 25.2048, lon: 55.2708 },
  { name: "Buenos Aires",   lat: -34.6037, lon: -58.3816 },
  { name: "Vancouver",      lat: 49.2827, lon: -123.1207 },
  { name: "Marrakech",      lat: 31.6295, lon: -7.9811 }
]

let base = "https://api.open-meteo.com/v1/forecast"
let qs = "&current=temperature_2m,wind_speed_10m,relative_humidity_2m&temperature_unit=fahrenheit&wind_speed_unit=mph"

var rows = []
var failures = 0

for each c in cities
  let url = base + f"?latitude={c.lat}&longitude={c.lon}" + qs
  let r = http.get(url)
  let healthy = r.ok and r.status is at least 200 and r.status is less than 300

  if healthy is false then
    failures = failures + 1
  else
    let body = json.parse(r.body)
    let cur = body.current
    rows = rows + [{
      name:     c.name,
      lat:      c.lat,
      lon:      c.lon,
      temp_f:   cur.temperature_2m,
      humidity: cur.relative_humidity_2m,
      wind_mph: cur.wind_speed_10m
    }]
  end
end

to compute_aggregate(rows)
  let temps = rows | map temp_f
  let winds = rows | map wind_mph
  let hums  = rows | map humidity

  let hottest = (rows | sort by temp_f descending | take 1).first
  let coldest = (rows | sort by temp_f ascending  | take 1).first
  let windy   = (rows | sort by wind_mph descending | take 1).first

  return {
    n_reporting:  length(rows),
    avg_temp_f:   math.round(average(temps) * 10) / 10,
    min_temp_f:   min(temps),
    max_temp_f:   max(temps),
    avg_humidity: math.round(average(hums)),
    avg_wind_mph: math.round(average(winds) * 10) / 10,
    hottest:      { name: hottest.name, temp_f: hottest.temp_f },
    coldest:      { name: coldest.name, temp_f: coldest.temp_f },
    windiest:     { name: windy.name, wind_mph: windy.wind_mph }
  }
end

let aggregate = if length(rows) is at least 1 then compute_aggregate(rows) else nothing end

let payload = {
  fetched_at_unix: time.now(),
  source:          "open-meteo.com",
  region:          "Beloved world cities",
  target_city_count: length(cities),
  failures:        failures,
  cities:          rows,
  aggregate:       aggregate
}

show json.stringify(payload)