Files
ai-agents/agents/weather_briefing.py
T

212 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""
Weather Briefing Agent
Fetches weather for Providence, UT and posts to Outline wiki.
Logs run to the Agent Dashboard API.
"""
import json
import os
import sys
from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo
from urllib import request, error, parse
MT = ZoneInfo("America/Denver")
# --- Config ---
DASHBOARD_API = os.environ.get("DASHBOARD_API", "http://localhost:8550")
AGENT_ID = "weather-briefing"
WIKI_API = "https://wiki.jfamily.io/api"
WIKI_TOKEN = os.environ.get("WIKI_TOKEN", "ol_api_yHXypRyqf4CscWDzPluGfPev9GhdFg6mwrXwkT")
WIKI_COLLECTION_ID = os.environ.get("WIKI_COLLECTION_ID", "9d9e471c-84cd-4ba7-bae5-c70f61805228")
WIKI_PARENT_DOC_ID = os.environ.get("WIKI_PARENT_DOC_ID", "2a891fe8-579b-450b-a663-de93915896b7") # Eric's Daily Briefing
# Providence, UT
LAT = 41.7064
LON = -111.8133
WEATHER_URL = (
f"https://api.open-meteo.com/v1/forecast?"
f"latitude={LAT}&longitude={LON}"
f"&current=temperature_2m,apparent_temperature,weather_code,wind_speed_10m,relative_humidity_2m"
f"&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,sunrise,sunset"
f"&temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch"
f"&timezone=America/Denver&forecast_days=7"
)
WMO_CODES = {
0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
45: "Foggy", 48: "Icy fog", 51: "Light drizzle", 53: "Drizzle",
55: "Heavy drizzle", 61: "Light rain", 63: "Rain", 65: "Heavy rain",
66: "Freezing rain", 67: "Heavy freezing rain",
71: "Light snow", 73: "Snow", 75: "Heavy snow", 77: "Snow grains",
80: "Light showers", 81: "Showers", 82: "Heavy showers",
85: "Light snow showers", 86: "Heavy snow showers",
95: "Thunderstorm", 96: "Thunderstorm w/ hail", 99: "Severe thunderstorm",
}
DAY_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
def api_request(url, data=None, headers=None, method="GET"):
"""Simple HTTP helper using urllib."""
if data is not None:
data = json.dumps(data).encode("utf-8")
req = request.Request(url, data=data, headers=headers or {}, method=method)
if data:
req.add_header("Content-Type", "application/json")
with request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode())
def fetch_weather():
"""Fetch weather data from Open-Meteo."""
return api_request(WEATHER_URL)
def format_briefing(weather):
"""Format weather data into markdown."""
now = datetime.now(MT)
date_str = now.strftime("%A, %B %d, %Y")
current = weather["current"]
daily = weather["daily"]
condition = WMO_CODES.get(current["weather_code"], "Unknown")
temp = round(current["temperature_2m"])
feels = round(current["apparent_temperature"])
wind = round(current["wind_speed_10m"])
humidity = current["relative_humidity_2m"]
md = f"# Daily Weather Briefing\n"
md += f"**{date_str}** | Providence, Utah\n\n"
md += f"---\n\n"
md += f"## Current Conditions\n\n"
md += f"| | |\n|---|---|\n"
md += f"| **Condition** | {condition} |\n"
md += f"| **Temperature** | {temp}°F (feels like {feels}°F) |\n"
md += f"| **Wind** | {wind} mph |\n"
md += f"| **Humidity** | {humidity}% |\n\n"
md += f"## 7-Day Forecast\n\n"
md += f"| Day | Condition | High | Low | Precip | Wind |\n"
md += f"|-----|-----------|------|-----|--------|------|\n"
for i in range(len(daily["time"])):
d = datetime.strptime(daily["time"][i], "%Y-%m-%d")
day_name = DAY_NAMES[d.weekday()]
if i == 0:
day_name = "**Today**"
elif i == 1:
day_name = "Tomorrow"
cond = WMO_CODES.get(daily["weather_code"][i], "?")
hi = round(daily["temperature_2m_max"][i])
lo = round(daily["temperature_2m_min"][i])
precip = daily["precipitation_sum"][i]
wind_max = round(daily["wind_speed_10m_max"][i])
precip_str = f'{precip}"' if precip > 0 else "-"
md += f"| {day_name} | {cond} | {hi}°F | {lo}°F | {precip_str} | {wind_max} mph |\n"
# Sunrise/sunset for today
sunrise = daily["sunrise"][0].split("T")[1] if daily["sunrise"][0] else "?"
sunset = daily["sunset"][0].split("T")[1] if daily["sunset"][0] else "?"
md += f"\n**Sunrise:** {sunrise} | **Sunset:** {sunset}\n"
md += f"\n---\n*Generated at {now.strftime('%I:%M %p MT')} by Weather Briefing Agent*\n"
return md, f"{condition}, {temp}°F (feels like {feels}°F), wind {wind} mph"
def post_to_wiki(markdown, date_str):
"""Create or update a daily briefing doc in Outline."""
headers = {
"Authorization": f"Bearer {WIKI_TOKEN}",
}
# Search for existing doc with today's date
title = f"Daily Briefing — {date_str}"
search_result = api_request(
f"{WIKI_API}/documents.search",
data={"query": title, "collectionId": WIKI_COLLECTION_ID},
headers=headers,
method="POST",
)
doc_id = None
for doc in search_result.get("data", []):
if doc.get("document", {}).get("title") == title:
doc_id = doc["document"]["id"]
break
if doc_id:
api_request(
f"{WIKI_API}/documents.update",
data={"id": doc_id, "text": markdown, "publish": True},
headers=headers,
method="POST",
)
return doc_id, "updated"
else:
result = api_request(
f"{WIKI_API}/documents.create",
data={
"title": title,
"text": markdown,
"collectionId": WIKI_COLLECTION_ID,
"parentDocumentId": WIKI_PARENT_DOC_ID,
"publish": True,
},
headers=headers,
method="POST",
)
return result["data"]["id"], "created"
def log_run(status, output="", err="", metadata=None):
"""Log this run to the dashboard API."""
try:
api_request(
f"{DASHBOARD_API}/api/agents/{AGENT_ID}/runs",
data={
"status": status,
"output": output,
"error": err,
"metadata": metadata or {},
},
method="POST",
)
except Exception as e:
print(f"Warning: failed to log run to dashboard: {e}", file=sys.stderr)
def main():
try:
print("Fetching weather...")
weather = fetch_weather()
print("Formatting briefing...")
date_str = datetime.now(MT).strftime("%Y-%m-%d")
markdown, summary = format_briefing(weather)
print("Posting to wiki...")
doc_id, action = post_to_wiki(markdown, date_str)
print(f"Wiki doc {action}: {doc_id}")
output = f"Weather: {summary}. Wiki doc {action}."
log_run("success", output=output, metadata={"wiki_doc_id": doc_id, "action": action})
print(f"Done: {output}")
except Exception as e:
err_msg = f"{type(e).__name__}: {e}"
print(f"Error: {err_msg}", file=sys.stderr)
log_run("failed", err=err_msg)
sys.exit(1)
if __name__ == "__main__":
main()