SkyCast is a local-first Flask weather application with live conditions, charts, recent history, local-time comparison, forecasts, air quality, automatically derived advisories, saved-place comparison, and animated rain radar.
- Current weather with Celsius/Fahrenheit switching
- Live searched-place clock compared with the browser's current location
- Singapore fallback when browser location is unavailable
- Next 24 hours in 3-hour intervals and a five-day forecast
- Practical next-five-hour advice for umbrellas/raincoats, snow layers, cold, heat, wind, and low visibility
- Best outdoor-time scoring across the next 24 hours with confidence and explainable penalties
- Activity-specific planning for commuting, running, cycling, picnics, laundry, photography, and beach trips
- Per-device umbrella, cold, heat, wind, AQI, and UV comfort thresholds
- Accessible canvas charts for temperature, rain chance, wind, and humidity
- Seven-day Open-Meteo historical context with temperature and precipitation comparisons
- Precipitation probability/volume, wind, humidity, visibility, pressure, sunrise, and sunset
- OpenWeather AQI with PM2.5, PM10, NO₂, O₃, and health guidance
- Global modelled U.S. AQI, UV forecast, pollen availability, and NOAA heat-index guidance
- Singapore official nearest-area two-hour forecast, regional PSI, and UV observations
- SkyCast-derived thunderstorm, heavy rain/snow, heat/cold, wind, visibility, and poor-AQI advisories
- Optional, user-initiated advisory notifications
- Interactive OpenStreetMap with RainViewer observations/nowcast, timeline, opacity, speed, legend, and full-screen controls
- Favorites and live side-by-side comparisons stored only in the browser
- Exact-place share links, copied summaries, and downloadable image cards
- A per-device dashboard editor for showing, hiding, and reordering sections
- English, Chinese, Malay, and Spanish dashboard/planner localization, including common condition descriptions
- First-character country/state suggestions and exact geocoded place selection
- Persistent temperature-unit preference and remembered location denial
- Temporary country/state filters that reset after an exact selection, result, or Home navigation
- Condition-aware themes, custom SVG icons, fluid rain, and permanent snow animation
- Installable PWA with offline app shell and previously visited page fallback
- PWA quick actions for search, Singapore, and saved places
- Calendar export for the selected best outdoor window
- Viewport-triggered Leaflet loading so map code does not delay initial rendering
- Accessible keyboard combobox, semantic headings, reduced-motion support, and responsive layouts
- Optional OpenWeather One Call 3.0 true-hourly data and official alert provenance
- Optional Redis cache/shared rate limits, Sentry monitoring, and scheduled Web Push alerts
- Custom background alert thresholds, event toggles, and timezone-aware quiet hours
- Enforced CSP/privacy headers, modular planner/i18n assets, GitHub CI, screenshots, Core Web Vitals, and Lighthouse budgets
- Create an OpenWeather API key.
- Copy
.env.exampleto.env. - Replace the placeholder with the API key.
- Create a virtual environment and install
requirements.txt. - Run
server.py. - SkyCast waits for
/healthz, then automatically openshttp://127.0.0.1:8000in a new Google Chrome window.
On this Windows machine, either double-click start_skycast.cmd, run .venv\Scripts\python.exe server.py, or use VS Code's Run Python File button while weather.py is open. Running weather.py now starts the same web app and Chrome; use weather.py --cli only for the original one-city terminal demo. Chrome is discovered from SKYCAST_CHROME_PATH, standard Windows installation folders, PATH, or the Windows App Paths registry. If Chrome is unavailable, SkyCast falls back to the registered default browser.
Startup controls:
SKYCAST_OPEN_BROWSER=falsedisables browser opening for servers and CI.SKYCAST_CHROME_PATHselects a non-standard Chrome executable.SKYCAST_HOSTandSKYCAST_PORTchange the local listener (defaults:127.0.0.1:8000).SKYCAST_BROWSER_URLoverrides the URL opened after health checks pass.
The browser is not opened if Waitress cannot bind the port or SkyCast does not become healthy within 20 seconds.
- Current weather, 5-day/3-hour forecast, geocoding, and air quality: OpenWeather
- Recent historical context: Open-Meteo
- Global UV/AQI and regional pollen where available: Open-Meteo Air Quality API using CAMS data
- Singapore official two-hour forecast, PSI, and UV: data.gov.sg
- Base map: OpenStreetMap
- Recent radar observations and short-range nowcast: RainViewer
- Country/subdivision catalog: pycountry
RainViewer “nowcast” frames are extrapolations rather than official warnings. SkyCast-derived advisories are not government emergency warnings. When the optional One Call integration supplies an official alert, SkyCast labels its issuing authority explicitly.
Pollen forecasts come from CAMS Europe and are currently unavailable outside Europe; SkyCast states this rather than inventing a value. The public Singapore lightning endpoint currently requires additional access, so the UI reports it as unavailable instead of treating missing data as “no lightning.” Public data.gov.sg APIs work without a key for testing; configure DATA_GOV_SG_API_KEY in production for higher limits and maintenance updates.
API_KEYstays server-side in ignored.envand is never sent to the browser.- Current-location clock coordinates are sent by private POST, are not placed in the URL, and are not stored.
- Only the resolved current-location label and UTC offset are cached in the browser tab for 30 minutes.
- Favorites intentionally store place coordinates in local browser storage so exact saved places can be reopened.
- Dashboard layout and language preferences remain in local browser storage.
- Planner thresholds, selected activity, and unsynced alert rules remain in local browser storage.
- Push subscriptions are opt-in and stored in the ignored
data/push_subscriptions.sqlite3file unlessPUSH_DATABASE_PATHis changed. - API responses containing location-derived data use
Cache-Control: no-store. - A local pre-commit hook scans staged content for likely secrets.
- Public-facing API endpoints have per-client in-memory rate limits.
See SECURITY.md for credential rotation guidance.
Server-side in-memory TTLs reduce OpenWeather usage:
- Current conditions: 5 minutes
- Forecast: 10 minutes
- Air quality: 10 minutes
- Geocoding: 6 hours
- Recent history: 6 hours
- Optional One Call hourly data/alerts: 10 minutes
- Global environmental forecast: 30 minutes
- Singapore official feeds: 15 minutes
The default cache is process-local and clears when the server restarts. Set REDIS_URL to use shared JSON caching and shared fixed-window rate limits. If Redis cannot be reached at startup, SkyCast automatically falls back to in-memory behavior.
The service worker caches the application shell, modular planner/localization assets, and successful same-origin page visits. Live APIs are always network-only. OpenStreetMap and RainViewer resources are not prefetched or placed in the app's offline cache. Installed apps expose shortcuts for search, Singapore weather, and saved places.
PWA installation and browser geolocation require HTTPS in production. localhost and 127.0.0.1 are treated as secure development origins.
Run the test suite with:
python -m unittest discover -s tests -v
The fast suite covers weather clients, official/environmental feeds, planner scoring, heat stress, activity profiles, user thresholds, calendar output, CSP, history, One Call normalization, catalogs, caching, push-rule migration/quiet hours, PWA behavior, and secret scanning.
Install requirements-dev.txt, ensure Google Chrome is installed, and set RUN_BROWSER_TESTS=1 to run tests/test_browser.py. The Playwright cases use a temporary deterministic Flask server and cover search, charts, preferences, activities, localization, calendar controls, accessibility names/headings, mobile overflow, screenshot structure, CLS, LCP, and load budgets without spending weather API quota.
GitHub Actions in .github/workflows/ci.yml runs compilation, all Python tests, Playwright, secret scanning, and Lighthouse. .lighthouserc.json sets accessibility, best-practice, CLS, LCP, and performance budgets.
config/deployment.env.template lists only sanitized placeholders for every deployment variable.
Set OPENWEATHER_ONE_CALL_ENABLED=true only after enabling OpenWeather One Call 3.0 for the configured key. SkyCast then uses 24 true-hourly entries and labels upstream official alerts. If the call is disabled or unavailable, the free 3-hour forecast and SkyCast-derived advisories continue working.
/healthzreturns no-secret readiness, uptime, cache-backend, request-count, and feature state.- Responses include
X-Request-IDandServer-Timingheaders. - Set
SENTRY_DSNto enable Sentry with default personally identifiable information collection disabled. - Set
REQUEST_LOGGING_ENABLED=truefor concise method/path/status/timing logs without client IP addresses.
Background push requires HTTPS, VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT, and a public PUBLIC_BASE_URL. Once configured, weather pages expose an opt-in Background alerts button. The private VAPID key never reaches the browser.
Run scripts/send_weather_alerts.py from an external scheduler every 10–15 minutes. The sender deduplicates unchanged advisories, removes expired browser endpoints, and never logs subscription URLs or keys. A scheduler is intentionally external so every web worker does not send duplicate notifications.
Subscribers can configure rain, thunderstorm, snow, heat, cold, wind, and AQI rules plus quiet hours. Existing subscription databases migrate in place and default to the original “notify all” behavior. Quiet hours use the browser's UTC offset and support intervals that cross midnight.
- Serve behind HTTPS.
- Set
SKYCAST_OPEN_BROWSER=falseon headless or production servers. - Set
API_KEYas a deployment secret rather than committing.env. - Keep one or a small number of application processes in mind: cache and rate-limit state is in memory per process.
- For multiple processes, configure
REDIS_URLfor shared cache and rate-limit state. - Monitor
/healthz; configure Sentry if centralized error/performance telemetry is desired. - Run the push sender from exactly one scheduler when Web Push is enabled.
- Preserve visible OpenStreetMap and RainViewer attribution.
- Do not prefetch or bulk-download OpenStreetMap tiles.
- Keep the enforced Content Security Policy current if external map, radar, or monitoring hosts change. Scripts remain restricted to self and pinned Leaflet; inline script attributes and
evalare blocked.