Live App: aiwindy.schorr.wien
An AI-powered sailing weather analysis app for European waters. Each analysis presents 5 interactive Windy maps paired with AI-generated explanations — zooming in from the continental scale down to the local sailing area:
- Druck & Luftmassen — Europe-wide 850hPa pressure and air mass map (ECMWF)
- Fronten — KNMI weather fronts analysis chart with AI interpretation
- Wind & Welle — Regional high-resolution wind model map (1–7 km) with forecast bullets
- Wolken & Regen — Cloud and precipitation overlay with thunderstorm risk
- Temperatur — Local meteogram with temperature summary
The app combines data from European synoptic services (Meteonews, Wetterzentrale, KNMI), national weather APIs (GeoSphere Austria, DHMZ Croatia, EMY/OpenSkiron Greece), and multiple AI models to generate concise, sailor-relevant analysis. Users can also upload photos or videos for meteorological AI analysis.
Client (React + Vite)
└── POST /api/chat (SSE)
├── Message classification (GPT-4.1-mini)
├── Location detection (Claude Sonnet)
├── European weather data
│ ├── Meteonews (general overview text)
│ ├── Wetterzentrale (850 hPa charts with local timestamps)
│ └── KNMI (front analysis charts with local timestamps)
├── National weather data (per country)
│ ├── AT: GeoSphere Austria JSON APIs
│ ├── HR: DHMZ XML feeds
│ └── GR: EMY gale warnings + OpenSkiron WRF 4km GRIB
├── LLM preprocessing (Claude Haiku/Sonnet)
└── Weather output generation (5 sections, Claude/GPT)
└── POST /api/upload (SSE)
├── Photo: EXIF GPS/timestamp → GPT-4.1 Vision analysis
└── Video: ffprobe metadata + ffmpeg thumbnail → Gemini 2.5 Flash analysis
The backend pipeline is JSON-based: raw data → preprocessed structured text → AI-generated output sections. Each stage writes to a persistent analysis JSON file (analyses/).
GPT-4.1-mini classifies each user message:
- ANALYSE <location> — Location-specific weather query → full 5-section analysis
- CHAT — General meteorology/sailing question or follow-up → direct GPT-4.1 answer (with full last analysis context: meta, sections, preprocessed data)
- UNCLEAR — Ambiguous query → asks user to specify location
Claude Sonnet detects sailingArea + city from user input, matched against data/sailingareas.json (133 sailing areas across 20 countries). The full sailing area list is sent as system prompt with Anthropic prompt caching (cache_control: ephemeral) to reduce cost and latency. Geocoding via Nominatim.
Windy wind model is selected via static JSON lookup — no LLM call:
data/sailingareas.json— each sailing area haswindyModel(highest priority)data/countries.json— country-level fallbackserver/location.ts— coordinate-based fallback for unlisted countries
Models: aromeHd (1.3km), czeAladin (2.3km), ukv (2km), iconEu (7km), gfs (22km).
| Source | URL | Data | File |
|---|---|---|---|
| Meteonews | meteonews.at/de/Allgemeine_Lage/K33/Europa |
Allgemeine Wetterlage Europa (HTML scrape) | server/weather-europe.ts |
| Wetterzentrale | wetterzentrale.de/maps |
850hPa ECMWF Temperatur-/Druckkarten (Bild-URLs) | server/weather-europe.ts |
| KNMI | cdn.knmi.nl/.../weerkaarten |
Frontenanalyse + Frontenprognose (GIF charts) | server/weather-europe.ts |
| Windy | embed.windy.com |
Interaktive Karten: 850hPa, Wind, Wolken, Meteogramm | Frontend iframes |
| Country | Source | URL | Data | File |
|---|---|---|---|---|
| 🇦🇹 Austria | GeoSphere Austria | dataset.api.hub.geosphere.at |
Hourly wind, gusts, temperature, precipitation, cloud cover (JSON API) | server/weather-national-austria.ts |
| 🇦🇹 Austria | Austrocontrol | austrocontrol.at/wetter/wetter_fuer_alle/wettervorhersage |
Flugwetter Wetterlage + FXOS Vorhersage (HTML scrape) | server/weather-national-austria.ts |
| 🇦🇹 Austria | LSZ Burgenland | lsz-b.at/fuer-buergerinnen/sturmwarnung-webcams/ |
Neusiedler See Sturmwarnungen (HTML scrape) | server/weather-national-austria.ts |
| 🇭🇷 Croatia | DHMZ | prognoza.hr/jadran_h.xml |
Adria Segelwetter-Vorhersage (XML API) | server/weather-national-croatia.ts |
| 🇭🇷 Croatia | DHMZ | prognoza.hr/pomorci.xml |
Maritime Warnungen, Wind, Seegang, Sicht (XML API) | server/weather-national-croatia.ts |
| 🇭🇷 Croatia | DHMZ | prognoza.hr/sedam/hrvatska/7d_meteogrami.xml |
Städte-Meteogramme, Temperatur (XML API) | server/weather-national-croatia.ts |
| 🇬🇷 Greece | HNMS/EMY | newportal.hnms.gr/emy/.../naftilia_deltio_thalasson_ektiposi |
Sturmwarnungen, Seewetter-Bulletin (HTML scrape) | server/weather-national-greece.ts |
| 🇬🇷 Greece | OpenSkiron | openskiron.org/en/openwrf |
WRF 4km GRIB: Wind, Welle (Douglas), Wolken, Temperatur, CAPE | server/weather-national-greece.ts + openskiron_fetch.py |
Other countries (20 total in sailingareas.json): analysis uses Europe-wide data + Windy maps. LLM preprocessing pipeline available for future integrations.
Python subprocess (scripts/openskiron_fetch.py) handles GRIB1 fetch and extraction since GRIB parsing requires native ecCodes.
python scripts/openskiron_fetch.py <domain> <wind_lat> <wind_lon> <city_lat> <city_lon> [grib_url]
- Node.js discovers current GRIB URL, checks DB cache (URL-based invalidation), passes URL as optional 7th arg
- Downloads + bz2-decompresses if not cached in
cache/openskiron/;.urlsidecar detects staleness - Opens GRIB1 with
cfgrib(non-standard WRF table → uses parameter IDs, not shortNames) - Extracts 49-step hourly time series at nearest grid point → JSON to stdout; results cached in DB per coordinate pair
4 domains cover all Greek sailing areas (openskiron_domain in sailingareas.json):
Ionian_Islands_4km, Aegean_SW_4km, Aegean_NW_4km, Aegean_SE_4km
Two-stage LLM preprocessing (server/weather-national.ts):
- National synopsis (
preprocessNationalWeather) — extracts/translates the country-level weather overview (e.g. DHMZ Adria synopsis, EMY marine bulletin, Austrocontrol Wetterlage) - Local weather (
preprocessLocalWeather) — extracts area-specific data: warnings, wind, wave, cloud/rain/thunderstorm, temperature. Each country module has dedicated functions (e.g.extractDhmzWarning,preprocessGreeceLocalWind)
LLM calls (Claude Sonnet / GPT-4.1-mini) extract and translate from raw XML/HTML into structured German text. Numeric data (GeoSphere JSON, OpenSkiron GRIB) is preprocessed without LLM.
Single Claude Sonnet 4.6 call (server/weather-output.ts) with all preprocessed data + weather chart images (Wetterzentrale 850hPa, KNMI fronts). Returns a JSON object with 5 section texts (bullet-point style). Each section has strict rules for content scope, bullet count, and word limits.
| Layer | Key Pattern | Storage | Invalidation | File |
|---|---|---|---|---|
| Location lookups | loc:{normalized input} |
PostgreSQL cache_store |
No TTL (permanent) | server/cache-db.ts |
| OpenSkiron weather data | openskiron:sa:{domain}:{coords}, openskiron:city:{domain}:{coords} |
PostgreSQL cache_store |
URL-based (new GRIB URL → cache miss) | server/cache-db.ts |
| OpenSkiron GRIB files | cache/openskiron/{domain}.grb2 |
Filesystem | .url sidecar file detects staleness |
scripts/openskiron_fetch.py |
| Analysis results | analyses table (JSONB) |
PostgreSQL | Append-only (1 row per analysis) | server/analysis-store.ts |
Camera button in chat input. Accepts images (JPEG, PNG, WebP, HEIC) and videos (MP4, QuickTime, WebM), max 20MB.
| Feature | Photos | Videos |
|---|---|---|
| Metadata | EXIF extraction (exif-parser) → GPS + timestamp | ffprobe → GPS (ISO6709) + creation_time from MP4 atoms |
| AI Analysis | GPT-4.1 Vision | Gemini 2.5 Flash |
| Thumbnail | Original image | ffmpeg: 1s frame extraction, "▶ Video" overlay |
| SSE event | { exifMeta } |
{ videoMeta: { thumbnailBase64, time, locationName, countryCode } } |
Analysis sections: 📷 Aufnahme, ☁️ Wolkentyp, 🌧️ Regen, 🌊 Wellen, 🌫️ Bedeckungsgrad, 🌤️ Wetterentwicklung. Videos additionally: 💨 Windgeschwindigkeit.
If GPS found: reverse geocodes via Nominatim, shows location + date, offers "ja" button to trigger weather analysis for that location.
server/
routes.ts Express API endpoints (chat, upload, geocode, KNMI proxy)
weather-europe.ts European data (Meteonews, Wetterzentrale, KNMI) + time helpers
weather-national.ts National dispatch (AT/HR/GR) + preprocessing pipeline
weather-national-austria.ts GeoSphere Austria + Austrocontrol + LSZ Burgenland
weather-national-croatia.ts DHMZ Croatia integration
weather-national-greece.ts EMY + OpenSkiron Greece integration
weather-output.ts AI output generation (single Claude Sonnet call → 5 sections)
analysis-store.ts Analysis JSON persistence (filesystem + PostgreSQL)
location.ts Location detection (sailing area + city via Claude Sonnet)
cache-db.ts PostgreSQL cache helpers (TTL, location cache, OpenSkiron cache)
data/
sailingareas.json 133 sailing areas with windyModel, coordinates, openskiron_domain, emy_name
countries.json Country-level wind model fallback
windymodels.json Windy model definitions (key + label)
windsystems.json Local wind systems per country (Bora, Meltemi, etc.)
scripts/
openskiron_fetch.py Python: GRIB1 fetch + extraction for Greece
client/src/pages/
home.tsx Single-column chat UI with progressive section rendering
Single-column chat interface built with React + Tailwind + shadcn/ui. Progressive rendering via SSE:
| SSE event | UI update |
|---|---|
{ location } |
Header + Section 1 (Windy 850hPa map, no marker) |
{ weatherEurope } |
Sections 2–5 (KNMI fronts chart + 3 Windy iframes) |
{ weatherOutput } |
Bullet text fills in for all 5 sections |
Chat mode (CHAT classification) uses GPT-4.1 with full last analysis context (meta, sections, preprocessed data) for follow-up questions.
- Node.js 20+ with
npm - Python 3.10+ with pip (for OpenSkiron/Greece only)
npm install
pip install -r requirements.txtDATABASE_URL=postgresql://...
SESSION_SECRET=...
AI_INTEGRATIONS_ANTHROPIC_API_KEY=sk-ant-...
AI_INTEGRATIONS_OPENAI_API_KEY=sk-...
AI_INTEGRATIONS_GEMINI_API_KEY=...
npm run dev- Domain: aiwindy.schorr.wien
- Redirect from .replit.app/.replit.dev to custom domain in production