Per-person briefings (Eric + Angela) with configurable weather locations
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Angela's Daily Briefing — wrapper for the daily briefing engine."""
|
||||||
|
|
||||||
|
from daily_briefing import run
|
||||||
|
|
||||||
|
CONFIG = {
|
||||||
|
"person": "Angela",
|
||||||
|
"agent_id": "angela-daily-briefing",
|
||||||
|
"wiki_parent_doc_id": "65966bd6-4ef8-4b79-9b79-e4aa62b94e96",
|
||||||
|
"location": {
|
||||||
|
"name": "Providence",
|
||||||
|
"state": "Utah",
|
||||||
|
"country": "US",
|
||||||
|
"lat": 41.7064,
|
||||||
|
"lon": -111.8133,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run(CONFIG)
|
||||||
+40
-29
@@ -1,34 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Daily Briefing Agent (Orchestrator)
|
Daily Briefing Engine (Reusable)
|
||||||
Calls sub-agents (weather, etc.), collates their output into a single
|
Collects sub-agent data, composes a markdown briefing, and posts to wiki.
|
||||||
markdown briefing, and posts it to the Outline wiki.
|
Called by person-specific wrappers (eric_briefing.py, angela_briefing.py, etc.)
|
||||||
|
with a config dict specifying location, wiki parent, and agent ID.
|
||||||
Hierarchy: Eric's Daily Briefing → {Year} → {Month} → Daily Briefing — {date}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from shared import (
|
from shared import (
|
||||||
MT, WIKI_API, WIKI_COLLECTION_ID, WIKI_PARENT_DOC_ID, MONTH_NAMES,
|
MT, WIKI_API, WIKI_COLLECTION_ID, MONTH_NAMES,
|
||||||
api_request, log_run, wiki_headers, find_child_doc, ensure_child_doc,
|
api_request, log_run, wiki_headers, find_child_doc, ensure_child_doc,
|
||||||
)
|
)
|
||||||
|
|
||||||
AGENT_ID = "daily-briefing"
|
|
||||||
|
|
||||||
|
def collect_sections(config):
|
||||||
def collect_sections():
|
|
||||||
"""Run each sub-agent and collect markdown sections.
|
"""Run each sub-agent and collect markdown sections.
|
||||||
|
|
||||||
Returns a list of (section_name, markdown, summary) tuples.
|
Returns a list of (section_name, markdown, summary) tuples.
|
||||||
Failed sub-agents are logged but don't stop the briefing.
|
Failed sub-agents are logged but don't stop the briefing.
|
||||||
"""
|
"""
|
||||||
sections = []
|
sections = []
|
||||||
|
location = config.get("location")
|
||||||
|
|
||||||
# --- Weather ---
|
# --- Weather ---
|
||||||
try:
|
try:
|
||||||
from weather_agent import run as weather_run
|
from weather_agent import run as weather_run
|
||||||
md, summary = weather_run()
|
md, summary = weather_run(location=location)
|
||||||
sections.append(("Weather", md, summary))
|
sections.append(("Weather", md, summary))
|
||||||
print(f" Weather: {summary}")
|
print(f" Weather: {summary}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -36,42 +34,49 @@ def collect_sections():
|
|||||||
sections.append(("Weather", "## Weather\n\n*Weather data unavailable.*\n", f"error: {e}"))
|
sections.append(("Weather", "## Weather\n\n*Weather data unavailable.*\n", f"error: {e}"))
|
||||||
|
|
||||||
# --- Future sub-agents go here ---
|
# --- Future sub-agents go here ---
|
||||||
|
# Each can receive config params as needed
|
||||||
# try:
|
# try:
|
||||||
# from calendar_agent import run as calendar_run
|
# from calendar_agent import run as calendar_run
|
||||||
# md, summary = calendar_run()
|
# md, summary = calendar_run(calendar_id=config.get("calendar_id"))
|
||||||
# sections.append(("Calendar", md, summary))
|
# sections.append(("Calendar", md, summary))
|
||||||
# except Exception as e:
|
# except Exception as e:
|
||||||
# sections.append(("Calendar", "## Calendar\n\n*Calendar data unavailable.*\n", f"error: {e}"))
|
# sections.append(("Calendar", "## Calendar\n\n*Unavailable.*\n", f"error: {e}"))
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
|
||||||
def compose_briefing(sections):
|
def compose_briefing(config, sections):
|
||||||
"""Compose the full daily briefing markdown from sub-agent sections."""
|
"""Compose the full daily briefing markdown from sub-agent sections."""
|
||||||
now = datetime.now(MT)
|
now = datetime.now(MT)
|
||||||
date_str = now.strftime("%A, %B %d, %Y")
|
date_str = now.strftime("%A, %B %d, %Y")
|
||||||
|
person = config.get("person", "")
|
||||||
|
location = config.get("location", {})
|
||||||
|
loc_label = location.get("name", "")
|
||||||
|
if location.get("state"):
|
||||||
|
loc_label += f", {location['state']}"
|
||||||
|
|
||||||
md = f"# Daily Briefing\n"
|
md = f"# {person}'s Daily Briefing\n"
|
||||||
md += f"**{date_str}** | Providence, Utah\n\n"
|
md += f"**{date_str}** | {loc_label}\n\n"
|
||||||
md += "---\n\n"
|
md += "---\n\n"
|
||||||
|
|
||||||
for _name, section_md, _summary in sections:
|
for _name, section_md, _summary in sections:
|
||||||
md += section_md + "\n\n"
|
md += section_md + "\n\n"
|
||||||
|
|
||||||
md += "---\n"
|
md += "---\n"
|
||||||
md += f"*Generated at {now.strftime('%I:%M %p MT')} by Daily Briefing Agent*\n"
|
md += f"*Generated at {now.strftime('%I:%M %p MT')} by {person}'s Daily Briefing Agent*\n"
|
||||||
|
|
||||||
return md
|
return md
|
||||||
|
|
||||||
|
|
||||||
def post_to_wiki(markdown, date_str):
|
def post_to_wiki(config, markdown, date_str):
|
||||||
"""Post the briefing to wiki under Year/Month hierarchy."""
|
"""Post the briefing to wiki under Year/Month hierarchy."""
|
||||||
|
wiki_parent_id = config["wiki_parent_doc_id"]
|
||||||
now = datetime.now(MT)
|
now = datetime.now(MT)
|
||||||
year_str = str(now.year)
|
year_str = str(now.year)
|
||||||
month_str = MONTH_NAMES[now.month]
|
month_str = MONTH_NAMES[now.month]
|
||||||
|
|
||||||
year_id = ensure_child_doc(
|
year_id = ensure_child_doc(
|
||||||
WIKI_PARENT_DOC_ID, year_str,
|
wiki_parent_id, year_str,
|
||||||
f"Daily briefings for {year_str}.",
|
f"Daily briefings for {year_str}.",
|
||||||
)
|
)
|
||||||
month_id = ensure_child_doc(
|
month_id = ensure_child_doc(
|
||||||
@@ -106,22 +111,32 @@ def post_to_wiki(markdown, date_str):
|
|||||||
return result["data"]["id"], "created"
|
return result["data"]["id"], "created"
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def run(config):
|
||||||
|
"""Run the full daily briefing pipeline for a given config.
|
||||||
|
|
||||||
|
Config keys:
|
||||||
|
person (str): Person's name (e.g., "Eric")
|
||||||
|
agent_id (str): Dashboard agent ID (e.g., "eric-daily-briefing")
|
||||||
|
wiki_parent_doc_id (str): Outline doc ID for this person's briefing root
|
||||||
|
location (dict): {name, state, country, lat, lon}
|
||||||
|
"""
|
||||||
|
agent_id = config["agent_id"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Collecting sub-agent data...")
|
print(f"Collecting sub-agent data for {config['person']}...")
|
||||||
sections = collect_sections()
|
sections = collect_sections(config)
|
||||||
|
|
||||||
print("Composing briefing...")
|
print("Composing briefing...")
|
||||||
markdown = compose_briefing(sections)
|
markdown = compose_briefing(config, sections)
|
||||||
date_str = datetime.now(MT).strftime("%Y-%m-%d")
|
date_str = datetime.now(MT).strftime("%Y-%m-%d")
|
||||||
|
|
||||||
print("Posting to wiki...")
|
print("Posting to wiki...")
|
||||||
doc_id, action = post_to_wiki(markdown, date_str)
|
doc_id, action = post_to_wiki(config, markdown, date_str)
|
||||||
print(f"Wiki doc {action}: {doc_id}")
|
print(f"Wiki doc {action}: {doc_id}")
|
||||||
|
|
||||||
summaries = "; ".join(f"{name}: {s}" for name, _, s in sections)
|
summaries = "; ".join(f"{name}: {s}" for name, _, s in sections)
|
||||||
output = f"Briefing {action}. {summaries}"
|
output = f"Briefing {action}. {summaries}"
|
||||||
log_run(AGENT_ID, "success", output=output, metadata={
|
log_run(agent_id, "success", output=output, metadata={
|
||||||
"wiki_doc_id": doc_id,
|
"wiki_doc_id": doc_id,
|
||||||
"action": action,
|
"action": action,
|
||||||
"sub_agents": [name for name, _, _ in sections],
|
"sub_agents": [name for name, _, _ in sections],
|
||||||
@@ -131,9 +146,5 @@ def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_msg = f"{type(e).__name__}: {e}"
|
err_msg = f"{type(e).__name__}: {e}"
|
||||||
print(f"Error: {err_msg}", file=sys.stderr)
|
print(f"Error: {err_msg}", file=sys.stderr)
|
||||||
log_run(AGENT_ID, "failed", err=err_msg)
|
log_run(agent_id, "failed", err=err_msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Eric's Daily Briefing — wrapper for the daily briefing engine."""
|
||||||
|
|
||||||
|
from daily_briefing import run
|
||||||
|
|
||||||
|
CONFIG = {
|
||||||
|
"person": "Eric",
|
||||||
|
"agent_id": "eric-daily-briefing",
|
||||||
|
"wiki_parent_doc_id": "2a891fe8-579b-450b-a663-de93915896b7",
|
||||||
|
"location": {
|
||||||
|
"name": "Providence",
|
||||||
|
"state": "Utah",
|
||||||
|
"country": "US",
|
||||||
|
"lat": 41.7064,
|
||||||
|
"lon": -111.8133,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run(CONFIG)
|
||||||
+69
-25
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Weather Agent
|
Weather Agent
|
||||||
Fetches weather for Providence, UT and returns structured data + markdown section.
|
Fetches weather for a configurable location and returns structured data + markdown.
|
||||||
Called by the Daily Briefing agent. Can also run standalone.
|
Called by Daily Briefing agents with location config. Can also run standalone.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@@ -11,18 +11,14 @@ from shared import MT, api_request, log_run
|
|||||||
|
|
||||||
AGENT_ID = "weather"
|
AGENT_ID = "weather"
|
||||||
|
|
||||||
# Providence, UT
|
# Default location (Providence, UT)
|
||||||
LAT = 41.7064
|
DEFAULT_LOCATION = {
|
||||||
LON = -111.8133
|
"name": "Providence",
|
||||||
|
"state": "Utah",
|
||||||
WEATHER_URL = (
|
"country": "US",
|
||||||
f"https://api.open-meteo.com/v1/forecast?"
|
"lat": 41.7064,
|
||||||
f"latitude={LAT}&longitude={LON}"
|
"lon": -111.8133,
|
||||||
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 = {
|
WMO_CODES = {
|
||||||
0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
|
0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
|
||||||
@@ -38,13 +34,41 @@ WMO_CODES = {
|
|||||||
DAY_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
DAY_NAMES = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||||||
|
|
||||||
|
|
||||||
def fetch_weather():
|
def build_url(location):
|
||||||
"""Fetch weather data from Open-Meteo."""
|
"""Build the Open-Meteo API URL for a given location."""
|
||||||
return api_request(WEATHER_URL)
|
lat = location["lat"]
|
||||||
|
lon = location["lon"]
|
||||||
|
return (
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_section(weather):
|
def location_label(location):
|
||||||
|
"""Human-readable location string."""
|
||||||
|
parts = [location.get("name", "")]
|
||||||
|
if location.get("state"):
|
||||||
|
parts.append(location["state"])
|
||||||
|
if location.get("country") and location["country"] != "US":
|
||||||
|
parts.append(location["country"])
|
||||||
|
return ", ".join(p for p in parts if p)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_weather(location=None):
|
||||||
|
"""Fetch weather data from Open-Meteo for a given location."""
|
||||||
|
loc = location or DEFAULT_LOCATION
|
||||||
|
url = build_url(loc)
|
||||||
|
return api_request(url)
|
||||||
|
|
||||||
|
|
||||||
|
def format_section(weather, location=None):
|
||||||
"""Format weather into a markdown section and a one-line summary."""
|
"""Format weather into a markdown section and a one-line summary."""
|
||||||
|
loc = location or DEFAULT_LOCATION
|
||||||
|
label = location_label(loc)
|
||||||
now = datetime.now(MT)
|
now = datetime.now(MT)
|
||||||
current = weather["current"]
|
current = weather["current"]
|
||||||
daily = weather["daily"]
|
daily = weather["daily"]
|
||||||
@@ -55,7 +79,7 @@ def format_section(weather):
|
|||||||
wind = round(current["wind_speed_10m"])
|
wind = round(current["wind_speed_10m"])
|
||||||
humidity = current["relative_humidity_2m"]
|
humidity = current["relative_humidity_2m"]
|
||||||
|
|
||||||
md = "## Weather\n\n"
|
md = f"## Weather — {label}\n\n"
|
||||||
md += "### Current Conditions\n\n"
|
md += "### Current Conditions\n\n"
|
||||||
md += "| | |\n|---|---|\n"
|
md += "| | |\n|---|---|\n"
|
||||||
md += f"| **Condition** | {condition} |\n"
|
md += f"| **Condition** | {condition} |\n"
|
||||||
@@ -88,21 +112,41 @@ def format_section(weather):
|
|||||||
sunset = daily["sunset"][0].split("T")[1] if daily["sunset"][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**Sunrise:** {sunrise} | **Sunset:** {sunset}\n"
|
||||||
|
|
||||||
summary = f"{condition}, {temp}°F (feels like {feels}°F), wind {wind} mph"
|
summary = f"{label}: {condition}, {temp}°F (feels like {feels}°F), wind {wind} mph"
|
||||||
return md, summary
|
return md, summary
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run(location=None):
|
||||||
"""Run the weather agent. Returns (markdown_section, summary) or raises."""
|
"""Run the weather agent. Returns (markdown_section, summary) or raises."""
|
||||||
weather = fetch_weather()
|
loc = location or DEFAULT_LOCATION
|
||||||
section, summary = format_section(weather)
|
weather = fetch_weather(loc)
|
||||||
log_run(AGENT_ID, "success", output=summary)
|
section, summary = format_section(weather, loc)
|
||||||
|
log_run(AGENT_ID, "success", output=summary, metadata={"location": location_label(loc)})
|
||||||
return section, summary
|
return section, summary
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import argparse
|
||||||
|
parser = argparse.ArgumentParser(description="Weather Agent")
|
||||||
|
parser.add_argument("--lat", type=float, help="Latitude")
|
||||||
|
parser.add_argument("--lon", type=float, help="Longitude")
|
||||||
|
parser.add_argument("--name", type=str, help="City/location name")
|
||||||
|
parser.add_argument("--state", type=str, help="State")
|
||||||
|
parser.add_argument("--country", type=str, default="US", help="Country code")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
loc = DEFAULT_LOCATION
|
||||||
|
if args.lat and args.lon:
|
||||||
|
loc = {
|
||||||
|
"name": args.name or "Custom",
|
||||||
|
"state": args.state or "",
|
||||||
|
"country": args.country,
|
||||||
|
"lat": args.lat,
|
||||||
|
"lon": args.lon,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
section, summary = run()
|
section, summary = run(loc)
|
||||||
print(section)
|
print(section)
|
||||||
print(f"\nSummary: {summary}")
|
print(f"\nSummary: {summary}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user