🤓 Stats for Nerds

Under the hood of the Shoreline Lake thermal predictor. ← back to forecast · model history · accuracy by time of day

🧠 The model

38
parameters
37 weights + 1 bias
37
features
hand-picked, physics-led
4.8 KB
model size
the whole brain, in JSON
±1.8 kt
peak-wind error
MAE on held-out test

Two heads on the same features: a logistic regression for the GO/NO probability and a ridge regression for the expected peak knots. No neural net, no gradient-boosted anything — a linear model you could check by hand, which is the point: every weight is inspectable and tied to a physical mechanism. The cleverness is in the features (nonlinear temperature sweet-spots, offshore flags, build-up-window clouds), not the classifier.

Algorithm
Logistic regression (binary) + ridge regression (knots), standardized inputs
Implementation
Pure Python standard library — no numpy, scipy, sklearn, pandas
Optimizer
Batch gradient descent, hand-written (train_model.py)
Regularization
L2 ridge; features z-scored on the train split only
Ground truth
KNUQ (Moffett Field) — good = NW wind holding ≥12 kt for ≥30% of 14–18h

🎓 Training

1,826
labeled days
Apr–Oct seasons
1,289
train days
2016–2023
537
test days
2024–2026, held out
~102 s
training time
1 laptop core, from CSV

Ten winging seasons of data (2016–2026). 2020 is missing — a pandemic-era hole in the archive, not a bug. The train/test split is strictly by time (train on the past, test on the future), so the accuracy numbers are what you'd have actually gotten forecasting forward, not hindsight-inflated. Retraining the whole thing takes under two minutes on a single CPU core — cheap enough to rebuild on a whim, which is how the cloud-timing fix (v5) got shipped the same afternoon it was diagnosed.

📡 Data pipeline

Everything is pulled fresh from free public sources — no paid APIs, no keys except where the source is keyless. Training data lives locally (~104 MB); the live predictor fetches only what it needs each run.

Open-Meteo
Forecast Tmax, clouds, radiation, 850 hPa profile (live features)
MET Norway
Independent Tmax fallback if Open-Meteo is down (added after a 503)
IEM METAR
~97k obs/station: NUQ, SFO, HAF, E16, SAC, ACV, WMC — labels, gradients, verification
NDBC 46012
Half Moon Bay buoy — onshore wind at the cold-air source
ERA5 archive
10 yrs hourly shortwave + cloud cover (training only)
KOAK RAOB
12Z soundings — 850 hPa temp/wind, inversion depth (training)
Live Nation
Shoreline Amphitheatre shows via JSON-LD — traffic warning

All predictions and their outcomes are logged and scored nightly against Moffett Field — the track record is real, not a backtest.

⚙️ Codebase

~2,100
lines of code
Python, stdlib only
11
scripts
fetch · label · train · predict · verify
0
pip dependencies
runs on a bare Python 3
5
cron runs/day
06:15 · 07:30 · 11:45 · 13:30 · 19:15

The forecast sharpens through the day: an evening call the night before, then intraday updates as morning observations arrive. After 13:30 the today-forecast is frozen; the 19:15 run scores the day and issues tomorrow's outlook. The website is static — a laptop at home computes everything and scps a handful of JSON files to the server.

📜 Changelog

Built over 12 days · 45 commits · 3 spots · full technical writeup in model history.