--- name: sg-weather description: "Singapore weather and air quality - 2-hour forecast by area, 24-hour outlook, PSI. Official NEA data, no API key." metadata: { "openclaw": { "emoji": "๐ŸŒฆ๏ธ" } } --- # Singapore Weather Live weather and air quality from NEA via data.gov.sg. No API key, no sign-up. Worth knowing why this exists as its own skill: Singapore rain is intensely local. It can pour in Bukit Timah while Changi stays dry, so a national forecast is close to useless. The 2-hour forecast is published per area โ€” use it. ## What it does - **Next 2 hours, by area** โ€” 47 areas, refreshed every half hour - **Next 24 hours** โ€” general outlook plus morning/afternoon/night - **PSI** โ€” air quality by region, which matters during haze season ## How to run it Use the Bash tool. These are public endpoints. ```bash # Right now, for one area curl -s "https://api-open.data.gov.sg/v2/real-time/api/two-hr-forecast" \ | python3 -c " import json,sys area='Ang Mo Kio' # change this d=json.load(sys.stdin)['data'] item=d['items'][0] hit=[f['forecast'] for f in item['forecasts'] if f['area']==area] print(f'{area}: {hit[0] if hit else \"area not found\"}') print('valid until', item['validPeriod']['end']) " # Every area at once โ€” useful for 'is it raining anywhere near me' curl -s "https://api-open.data.gov.sg/v2/real-time/api/two-hr-forecast" \ | python3 -c " import json,sys for f in json.load(sys.stdin)['data']['items'][0]['forecasts']: print(f\"{f['area']:22} {f['forecast']}\") " # 24-hour outlook curl -s "https://api-open.data.gov.sg/v2/real-time/api/twenty-four-hr-forecast" \ | python3 -c " import json,sys r=json.load(sys.stdin)['data']['records'][0] print('General:', r['general']['forecast']['text']) print('Temp:', r['general']['temperature']['low'], '-', r['general']['temperature']['high'], 'C') for p in r['periods']: print(p['timePeriod']['text'], '->', p['regions']['central']['text']) " # Air quality (PSI) by region curl -s "https://api-open.data.gov.sg/v2/real-time/api/psi" \ | python3 -c " import json,sys psi=json.load(sys.stdin)['data']['items'][0]['readings']['psi_twenty_four_hourly'] for region,value in psi.items(): band=('Good' if value<=50 else 'Moderate' if value<=100 else 'Unhealthy' if value<=200 else 'Very unhealthy' if value<=300 else 'Hazardous') print(f'{region:8} {value:4} {band}') " ``` ## Working notes - Ask which area they are in, or infer it from context. Do not report a national average โ€” the whole point is that it differs across the island. - The 2-hour forecast is a short text like `Thundery Showers`. Quote it; do not embellish it into a narrative. - PSI bands: โ‰ค50 Good ยท 51โ€“100 Moderate ยท 101โ€“200 Unhealthy ยท 201โ€“300 Very unhealthy ยท >300 Hazardous. During haze, people are deciding whether to run outdoors or keep a child home โ€” give them the number and the band, not a mood. - Times come back in SGT. Report them in SGT. - If the endpoint is down, say so. Do not fall back to guessing from memory โ€” yesterday's weather stated confidently is worse than "I could not reach NEA".