APIPythondaily hightutorial

Build a Weather Bot, Part 2: Tracking Predictions Through the Day

DailyHigh··6 min read

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:

EndpointWhen to use itWhat it gives you
/api/v1/prediction/:icaoMorning through peakpredictedMax, confidence, slope, isPastPeak
/api/v1/weather/:icaoAnytime (live observation)todayMax, current temperature, forecast.maxTemp
/api/v1/history/:icao/:dateAfter peak or end of dayFinal 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:

SourceMax 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.

Track daily highs with DailyHigh

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