The story
I kept cancelling things because an app said rain, and then it did not rain. That is an ordinary complaint and a completely untestable one, because nobody remembers the times the forecast was right.
The HydroData collector already existed and had the shape of the answer in it: poll a public source on a schedule, keep every version, and compare what was promised against what happened. The only thing missing was a source of truth. Environment Canada publishes station observations every minute, and there is a station close enough to two places I care about in Montréal to use as one.
So the question stopped being does the forecast feel wrong and became something with an answer: for each hour that has now passed, what did each service say about it, and when did they say it?
The product
Two collectors in Docker containers. One reads the Open-Meteo API every five minutes for ECMWF, GFS and HRDPS at Parc Henri-Julien and Montréal—Trudeau, and writes a new record only when a service changes its mind. The other pulls Environment Canada’s SWOB-ML observations from the McTavish and Trudeau stations. Both land in date-partitioned JSONL and mirror hourly to Google Cloud Storage.
The scorer joins them on the instant rather than the hour, refuses any forecast published after the moment it describes, and emits a single self-contained HTML file — inline SVG, no chart library, no network calls once it is open. The horizon control at the top re-grades every measure on the page: the same data asked a different question, which is the question the report exists to let you ask.
Five days is short, and one summer week in one city is not a verdict on forecasting. It is enough to separate the measures that hold from the one that does not, which was the thing I actually wanted to know.