Build a Weather Bot, Part 2: Tracking Predictions Through the Day
A temperature prediction changes through the day. In the morning it's a best guess. By early afternoon the answer is almost locked in. By evening it's history.
This is Part 2 of a three-part series on building with the DailyHigh API. Part 1 built a threshold alert bot. Part 3 covers monitoring multiple stations at once.
In this post, we'll follow one station through the full day using three API endpoints, watching the prediction converge toward the final result.
The three endpoints
Each endpoint covers a different stage of the day:
| Endpoint | When to use it | What it gives you |
|---|---|---|
/api/v1/prediction/:icao | Morning through peak | predictedMax, confidence, slope, isPastPeak |
/api/v1/weather/:icao | Anytime (live observation) | todayMax, current temperature, forecast.maxTemp |
/api/v1/history/:icao/:date | After peak or end of day | Final maxTemp, minTemp, full hourly[] array |
The prediction endpoint gives you the forward-looking estimate. The weather endpoint shows you what's happening right now. The history endpoint gives you the settled truth.
Setup
Same structure as Part 1. One helper per endpoint:
import requests
from datetime import date
API_KEY = "dh_live_xxxxx"
BASE = "https://dailyhigh.app"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def get_prediction(icao: str) -> dict | None:
resp = requests.get(
f"{BASE}/api/v1/prediction/{icao}",
headers=HEADERS,
timeout=10,
)
if resp.status_code == 202:
return None
resp.raise_for_status()
return resp.json()["data"]
def get_weather(icao: str) -> dict:
resp = requests.get(
f"{BASE}/api/v1/weather/{icao}",
headers=HEADERS,
timeout=10,
)
resp.raise_for_status()
return resp.json()["data"]
def get_history(icao: str, dt: str) -> dict:
resp = requests.get(
f"{BASE}/api/v1/history/{icao}/{dt}",
headers=HEADERS,
timeout=10,
)
resp.raise_for_status()
return resp.json()["data"]Phase 1: Morning (before peak)
Early in the day, the prediction endpoint is your primary source. The station hasn't reached its high yet, so observedMax is well below where the day will end up. This is where predictedMax does the heavy lifting.
pred = get_prediction("KLGA")
if pred is None:
print("Prediction not cached yet, retry in 30s")
else:
print(f"Predicted max: {pred['predictedMax']} °C")
print(f"Observed so far: {pred['observedMax']} °C")
print(f"Confidence: {pred['confidence']}/10")
print(f"Hours until peak: {pred['hoursUntilPeak']}")
print(f"Slope: {pred['slope']} °C/h")At 8 AM local time, you might see:
{
"predictedMax": 31.2,
"observedMax": 24.1,
"confidence": 3,
"hoursUntilPeak": 6.0,
"slope": 2.1,
"isPastPeak": false,
"predictionReason": "Warming trend"
}Confidence is low because there's still a lot of day left. The slope tells you temperatures are climbing at 2.1 °C per hour. That rate won't hold all day, but it tells you the morning trend is strong.
Phase 2: Midday (approaching peak)
By noon or early afternoon, the prediction is much tighter. Now we can cross-reference with the live weather observation.
pred = get_prediction("KLGA")
wx = get_weather("KLGA")
print(f"Prediction: {pred['predictedMax']} °C (confidence {pred['confidence']})")
print(f"Current temp: {wx['temperature']} °C")
print(f"Today's max so far: {wx['todayMax']} °C")
print(f"Forecast max: {wx['forecast']['maxTemp']} °C")
print(f"Past peak: {pred['isPastPeak']}")At 1 PM:
{
"predictedMax": 30.8,
"observedMax": 30.2,
"confidence": 7,
"hoursUntilPeak": 1.0,
"slope": 0.4,
"isPastPeak": false
}Confidence is 7. The observed max is already 30.2 °C, and the slope has flattened to 0.4 °C/h. The prediction has narrowed from 31.2 to 30.8 as real data replaced the model estimate. With one hour until peak, the gap between observed and predicted is small.
This is the critical window: the slope is slowing, hoursUntilPeak is low, and the prediction and observation are converging.
Phase 3: After peak
Once isPastPeak flips to true, the station has likely recorded its high for the day. The observed max and the predicted max should be very close now.
pred = get_prediction("KLGA")
print(f"Past peak: {pred['isPastPeak']}")
print(f"Observed max: {pred['observedMax']} °C")
print(f"Predicted max: {pred['predictedMax']} °C")
print(f"Confidence: {pred['confidence']}/10")At 4 PM:
{
"predictedMax": 30.5,
"observedMax": 30.5,
"confidence": 9,
"hoursUntilPeak": 0,
"slope": -0.8,
"isPastPeak": true
}Confidence is 9. The slope is negative (cooling). Predicted and observed have converged to the same value. The day's high is essentially decided.
Phase 4: Get the final result
At end of day (or any time after), the history endpoint gives you the confirmed daily high along with the full hourly profile.
today = date.today().isoformat()
history = get_history("KLGA", today)
print(f"Final daily max: {history['maxTemp']} °C")
print(f"Final daily min: {history['minTemp']} °C")
print(f"Forecast had predicted: {history['forecast']['maxTemp']} °C")
print(f"Hourly observations: {len(history['hourly'])}"){
"date": "2026-02-03",
"maxTemp": 30.5,
"minTemp": 18.2,
"forecast": { "maxTemp": 31, "minTemp": 17 },
"hourly": [
{ "time": "2026-02-03T00:00:00", "temperature": 19.8 },
{ "time": "2026-02-03T01:00:00", "temperature": 19.1 },
"..."
]
}Now you can compare all three numbers:
| Source | Max temp |
|---|---|
| Morning prediction (8 AM) | 31.2 °C |
Forecast (forecast.maxTemp) | 31.0 °C |
Final observed (maxTemp) | 30.5 °C |
The prediction started at 31.2 °C and converged to 30.5 °C as observations came in. The third-party forecast said 31.0 °C. The actual high was 30.5 °C. That convergence pattern is typical: the prediction gets pulled toward reality through the day.
Putting it all together
Here's a script that runs the full lifecycle. Call it three times during the day (or schedule it on a cron) and it logs each phase:
import json
from datetime import date, datetime
from pathlib import Path
LOG_FILE = Path("daily_log.json")
def log_snapshot(icao: str):
pred = get_prediction(icao)
wx = get_weather(icao)
today = date.today().isoformat()
snapshot = {
"time": datetime.now().isoformat(),
"currentTemp": wx["temperature"],
"todayMax": wx["todayMax"],
"predictedMax": pred["predictedMax"] if pred else None,
"confidence": pred["confidence"] if pred else None,
"isPastPeak": pred["isPastPeak"] if pred else None,
"slope": pred["slope"] if pred else None,
}
# Append to log
log = []
if LOG_FILE.exists():
log = json.loads(LOG_FILE.read_text())
log.append(snapshot)
LOG_FILE.write_text(json.dumps(log, indent=2))
print(json.dumps(snapshot, indent=2))
# If past peak, also fetch history for the final number
if pred and pred["isPastPeak"]:
history = get_history(icao, today)
print(f"\nFinal daily max: {history['maxTemp']} °C")
log_snapshot("KLGA")Run it at 8 AM, 1 PM, and 5 PM. You'll get a JSON log showing how the prediction evolved:
[
{ "time": "08:00", "todayMax": 24.1, "predictedMax": 31.2, "confidence": 3, "isPastPeak": false },
{ "time": "13:00", "todayMax": 30.2, "predictedMax": 30.8, "confidence": 7, "isPastPeak": false },
{ "time": "17:00", "todayMax": 30.5, "predictedMax": 30.5, "confidence": 9, "isPastPeak": true }
]What to watch for
Confidence jumps. A sudden jump from 4 to 7 usually means the prediction and observation have started converging. That's the point where the outcome becomes much more predictable.
Slope sign change. When slope goes from positive to negative, the station is cooling. This typically happens within an hour of the actual peak.
Forecast vs prediction divergence. The forecastMax field in the prediction endpoint is the third-party forecast. If predictedMax starts pulling away from forecastMax, it means real-time observations are overriding the model. This often happens on unusual weather days.
Next steps
Part 1 of this series built a threshold alert bot using just the prediction endpoint. Part 3 scales the pattern to all tracked stations with a multi-station dashboard.
The full API reference documents every field in each response. Browse the stations index to find ICAO codes for all 12 tracked stations.
Live METAR observations, AI daily high predictions, and temperature alerts for 12 airport stations worldwide. Free to browse, from $7/mo for real-time data and API access.
Related posts
Build a Weather Bot, Part 3: Monitoring Multiple Stations
Use the DailyHigh API to poll all tracked stations, rank them by confidence, and build a daily digest of temperature predictions.
Build a Weather Bot, Part 1: Temperature Threshold Alerts
Use Python and the DailyHigh API to build a bot that alerts you when a station crosses a temperature threshold, or when it won't.
How Weather Forecasts Are Made, Step by Step
From raw observations to the forecast on your phone. A look at the data, models, and human decisions behind every weather prediction.