The atmospheric intelligence engine powering Windly's cinematic weather experience.
Live API: windly-backend.onrender.com Β |Β Frontend Repo: windly-frontend Β |Β Docs: /docs Β |Β
This is not a simple weather proxy. The Windly backend is a purpose-built atmospheric intelligence service responsible for four distinct concerns:
- Weather Aggregation β Proxies WeatherAPI's forecast and search endpoints, normalises errors, and returns clean structured JSON to the frontend
- AI Prediction Engine β Runs a calibrated Random Forest classifier to estimate rain probability from 5 atmospheric inputs, served via a dedicated
/predictendpoint - TTL Cache Layer β In-memory caching with per-endpoint TTLs prevents redundant API calls on repeated searches and rapid unit-toggle interactions
- Async API Orchestration β A single shared
httpx.AsyncClientpersists across the server's lifetime, reusing TCP connections and keep-alive sessions for every outbound request
The result is a backend that feels fast, handles failure gracefully, and keeps the WeatherAPI key off the client entirely.
Browser / Vercel Frontend
β
β HTTPS
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β FastAPI Backend (Render) β
β β
β βββββββββββββββββββββββββββββββββββ β
β β GZip Middleware β β
β β (compresses responses β₯500B) β β
β ββββββββββββββββ¬βββββββββββββββββββ β
β β β
β βββββββββ΄βββββββββ β
β β β β
β ββββββββΌβββββββ ββββββββΌβββββββ β
β β WeatherAPI β β ML Inferenceβ β
β β Proxy β β Engine β β
β β /forecast β β /predict β β
β β /search β β β β
β ββββββββ¬βββββββ ββββββββ¬βββββββ β
β β β β
β ββββββββΌβββββββ ββββββββΌβββββββ β
β β TTL Cache β β RandomForest β β
β β (in-memory)β β .pkl model β β
β ββββββββ¬βββββββ βββββββββββββββ β
β β β
β ββββββββΌβββββββββββββββββββββββββ β
β β Shared Async httpx.Client β β
β β (connection pool, keep-alive)β β
β ββββββββ¬βββββββββββββββββββββββββ β
βββββββββββΌββββββββββββββββββββββββββββββββ
β
βΌ
WeatherAPI.com
| Method | Endpoint | Cache TTL | Description |
|---|---|---|---|
GET |
/ |
β | Health check |
POST |
/predict |
β | Rain probability prediction |
GET |
/weather/forecast?q={city} |
5 min | 3-day weather forecast |
GET |
/weather/search?q={query} |
15 min | City autocomplete suggestions |
Runs the Random Forest classifier and returns a calibrated rain probability.
Request body:
{
"temperature": 22.5,
"humidity": 78,
"pressure": 1008,
"wind": 35,
"cloud": 60
}Response:
{
"prediction": 1,
"probability": 0.7341
}Field validation (enforced by Pydantic):
| Field | Type | Range |
|---|---|---|
temperature |
float | β90 to 60 Β°C |
humidity |
float | 0 to 100 % |
pressure |
float | 870 to 1085 mb |
wind |
float | 0 to 300 km/h |
cloud |
float | 0 to 100 % |
cURL example:
curl -X POST https://windly-backend.onrender.com/predict \
-H "Content-Type: application/json" \
-d '{
"temperature": 22.5,
"humidity": 78,
"pressure": 1008,
"wind": 35,
"cloud": 60
}'Proxies WeatherAPI's 3-day forecast endpoint. Returns current conditions, hourly data, and forecast days including AQI.
curl "https://windly-backend.onrender.com/weather/forecast?q=London"Returns city name suggestions for autocomplete. Results are cached for 15 minutes β this endpoint fires on every keystroke after 3 characters, so caching meaningfully reduces WeatherAPI usage.
curl "https://windly-backend.onrender.com/weather/search?q=mum"FastAPI generates full interactive documentation automatically from the code.
| Interface | URL | Description |
|---|---|---|
| Swagger UI | /docs |
Try every endpoint live in the browser |
| ReDoc | /redoc |
Clean reference documentation |
No API key needed to explore the docs β the WeatherAPI key is server-side only.
A Random Forest classifier with sigmoid probability calibration, trained on the Global Weather Repository dataset. Raw forest probabilities tend to be overconfident; sigmoid calibration via CalibratedClassifierCV produces realistic, well-distributed outputs across the full 0β1 range.
Model configuration:
RandomForestClassifier(
n_estimators=120,
max_depth=10,
min_samples_split=4,
n_jobs=-1,
random_state=42,
)
# Wrapped with:
CalibratedClassifierCV(rf, method='sigmoid', cv=5)GlobalWeatherRepository.csv
β
βΌ
1. Load CSV β select 5 feature columns + precip_mm
β
βΌ
2. Drop NaN rows β ensures clean training signal
β
βΌ
3. Generate binary target β precip_mm > 0 β RainTomorrow = 1
β
βΌ
4. Stratified 80/20 train/test split (random_state=42)
β
βΌ
5. Fit RandomForest + sigmoid calibration (5-fold CV)
β
βΌ
6. Evaluate β Accuracy, ROC-AUC, classification report
β
βΌ
7. Export β model/random_forest.pkl (joblib)
# Place GlobalWeatherRepository.csv in data/
python model/train.py
# Output: model/random_forest.pkl
# Logs: accuracy, ROC-AUC, rain rate, classification reportA single httpx.AsyncClient is created at server startup via FastAPI's lifespan context manager and stored on app.state. Every weather request reuses this client instead of opening a new TCP connection β saving 30β80ms per request.
app.state.http = httpx.AsyncClient(
timeout=8.0,
limits=httpx.Limits(
max_connections=20,
max_keepalive_connections=10,
keepalive_expiry=30.0,
),
)An in-memory dict cache avoids redundant WeatherAPI calls. Weather data changes slowly enough that caching for a few minutes is invisible to users but saves meaningful API quota.
| Endpoint | TTL | Reason |
|---|---|---|
/weather/forecast |
5 minutes | Forecast data doesn't change second-to-second |
/weather/search |
15 minutes | City names are stable; fires on every keystroke |
GZipMiddleware compresses all responses over 500 bytes. A typical forecast JSON response is 10β20 KB β compression cuts this to 2β4 KB, meaningfully improving load time on mobile connections.
The random_forest.pkl model is loaded once at module import time in predict.py. It is never re-loaded between requests. This eliminates model deserialisation overhead (which can take 200β400ms) from every prediction call.
All WeatherAPI proxy routes are async def, allowing the server to handle other requests while waiting on the external API. Under concurrent load, this prevents request queuing that would occur with synchronous handlers.
| Feature | Benefit |
|---|---|
| Async-native | async def routes handle I/O-bound tasks (external API calls) without blocking the server |
| Pydantic validation | Request body validation and type coercion with zero boilerplate β invalid inputs are rejected automatically with clear error messages |
| Auto-generated docs | /docs and /redoc are generated from the code itself β always up to date, no manual maintenance |
| Lightweight | No ORM, no admin panel, no unnecessary abstractions β just routes, middleware, and models |
| Production-ready | Uvicorn + FastAPI is a mature, battle-tested stack used in production at scale |
backend/
βββ app.py # FastAPI app β routes, middleware, lifespan, caching
βββ requirements.txt
βββ .env # WEATHER_API_KEY β never committed
βββ model/
β βββ train.py # Full training pipeline β loads, cleans, trains, exports
β βββ predict.py # Model loader + inference function
β βββ random_forest.pkl # Trained model artifact β must be committed for deployment
β βββ feature_columns.json # Feature name reference
βββ data/
βββ GlobalWeatherRepository.csv # Training dataset β gitignored, download from Kaggle
git clone https://github.com/nitinmohan18/windly-backend.git
cd windly-backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtecho "WEATHER_API_KEY=your_key_here" > .envGet a free key at weatherapi.com. The free tier covers all of Windly's usage.
Download GlobalWeatherRepository.csv from Kaggle and place it in data/.
python model/train.py
# Outputs: model/random_forest.pkl
# Logs accuracy, ROC-AUC, and classification report to the consoleuvicorn app:app --reload --port 8000- API:
http://localhost:8000 - Interactive docs:
http://localhost:8000/docs
- Push this repo to GitHub (including
model/random_forest.pklβ see note below) - Create a new Web Service on Render
- Configure:
| Setting | Value |
|---|---|
| Runtime | Python 3 |
| Build Command | pip install -r requirements.txt |
| Start Command | uvicorn app:app --host 0.0.0.0 --port $PORT |
- Add environment variable in the Render dashboard:
| Key | Value |
|---|---|
WEATHER_API_KEY |
your WeatherAPI key |
random_forest.pkl must be committed to the repo.
predict.py loads the model at import time β if the file doesn't exist when the server starts, the /predict endpoint will be unavailable. Render has no way to run train.py at deploy time (no training dataset on the server), so the trained artifact must be version-controlled.
Cold starts on the free tier. Render's free tier spins down services after ~10 minutes of inactivity. The first request after sleep can take 30β40 seconds while the server wakes up and loads the model. Windly's frontend handles this with a 50-second timeout and a user-facing waiting message β no action needed on the backend side.
.env and data/ must be gitignored.
Your WeatherAPI key and the training CSV should never be committed. Confirm both are in .gitignore before pushing.
| Layer | Technology |
|---|---|
| Framework | FastAPI |
| Server | Uvicorn |
| HTTP Client | httpx (async, connection-pooled, keep-alive) |
| Compression | Starlette GZipMiddleware |
| Validation | Pydantic v2 |
| ML β Classifier | scikit-learn RandomForestClassifier |
| ML β Calibration | scikit-learn CalibratedClassifierCV (sigmoid) |
| ML β Serialisation | joblib |
| Environment | python-dotenv |
| Hosting | Render |
MIT β fork it, extend it, build on it.