Initial commit: Agent Command Center dashboard + weather briefing agent
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
#!/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
|
||||
from urllib import request, error, parse
|
||||
|
||||
# --- 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") # Set after collection creation
|
||||
|
||||
# Providence, UT
|
||||
LAT = 41.7064
|
||||
LON = -111.8133
|
||||
|
||||
WEATHER_URL = (
|
||||
f"https://api.open-meteo.com/v1/forecast?"
|
||||
f"latitude={LAT}&longitude={LON}"
|
||||
f"¤t=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()
|
||||
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,
|
||||
"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().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()
|
||||
Reference in New Issue
Block a user