Weather Prediction APIMachine Learning
Two Sydney rain prediction models, deployed as a live API anyone can call.
Will it rain in Sydney seven days from now, and how much over the next three days? I trained a model for each question, then did the part most coursework skips: packaged them with Docker and put them on the internet as a working API. Modest accuracy, honestly reported; what this project shows is the road from notebook to running service.
Two questions a tour operator asks
Sydney's tourism operators plan around rain: harbour cruises, outdoor events, daily itineraries. So the project ships two predictions on Open-Meteo history for Sydney (5,479 days, 2010 to 2024): will it rain exactly seven days from now, and how many millimetres will fall over the next three days. Both targets are built strictly from future values relative to each reference day, so the model never trains on information it would not have. The regression target's distribution explains the difficulty up front: the median three-day total is 1.8 mm but the maximum is 203.7, and a handful of storms dominates any error metric.

Metrics and validation chosen for the job
The metric follows the cost of being wrong: for a tourist without an umbrella, a missed rainy day hurts more than a false alarm, so recall was prioritised, F1 became the headline metric, and accuracy is reported only as a reference. Validation respects time: the earliest 60 percent trains, the next 20 tunes, the most recent 20 is touched once at the end, and the tuning search uses TimeSeriesSplit so no fold ever peeks at the future. The feature work stayed grounded too: the strongest single signal turned out to be daylight duration, a seasonality proxy, alongside engineered flags like evaporation minus precipitation that carry domain sense.


When the simple model wins
The random forest regressor looked brilliant in training (RMSE 4.02, R-squared 0.90) and fell apart on validation (RMSE 11.52), the signature of memorised noise. Plain linear regression trained worse and generalised better, so it won on the only number that matters, validation error, and landed at test RMSE 9.55 with R-squared 0.67. The classifier told a similar story: random forest beat XGBoost on validation F1 and finished at F1 0.66 with recall 0.61 on test. The write-up spells out what that buys: about six in ten risk-prone days caught, a decision-support tool rather than a sole source of truth, and extreme rainfall still under-predicted.


From notebook to a URL
Both models ship behind a FastAPI service, containerised with Docker so the environment cannot drift between laptop and cloud, deployed on Render with Swagger docs, one endpoint per question. It is not a demo shell: at request time the service fetches that day's weather from Open-Meteo, rebuilds the exact engineered features from training, and answers with a dated prediction. That is also why 2025 data was held out of training entirely: it is what the live service runs on.

from sklearn.model_selection import TimeSeriesSplit
ts_cv = TimeSeriesSplit(n_splits=5) # keep time order within training
SCORING = "f1" # main metric per business goal
# class imbalance helper for XGBoost
pos = y_train.sum()
neg = len(y_train) - pos
scale_pos_weight = float(neg) / float(pos) if pos > 0 else 1.0
rf = RandomForestClassifier(
random_state=RANDOM_STATE, class_weight="balanced", n_jobs=-1)
xgb = XGBClassifier(
tree_method="hist", random_state=RANDOM_STATE,
eval_metric="logloss")