diff --git a/agents/angela_briefing.py b/agents/angela_briefing.py new file mode 100644 index 0000000..4a4c8fe --- /dev/null +++ b/agents/angela_briefing.py @@ -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) diff --git a/agents/daily_briefing.py b/agents/daily_briefing.py index bdfdfd0..9365ea9 100644 --- a/agents/daily_briefing.py +++ b/agents/daily_briefing.py @@ -1,34 +1,32 @@ #!/usr/bin/env python3 """ -Daily Briefing Agent (Orchestrator) -Calls sub-agents (weather, etc.), collates their output into a single -markdown briefing, and posts it to the Outline wiki. - -Hierarchy: Eric's Daily Briefing → {Year} → {Month} → Daily Briefing — {date} +Daily Briefing Engine (Reusable) +Collects sub-agent data, composes a markdown briefing, and posts to wiki. +Called by person-specific wrappers (eric_briefing.py, angela_briefing.py, etc.) +with a config dict specifying location, wiki parent, and agent ID. """ import sys from datetime import datetime 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, ) -AGENT_ID = "daily-briefing" - -def collect_sections(): +def collect_sections(config): """Run each sub-agent and collect markdown sections. Returns a list of (section_name, markdown, summary) tuples. Failed sub-agents are logged but don't stop the briefing. """ sections = [] + location = config.get("location") # --- Weather --- try: from weather_agent import run as weather_run - md, summary = weather_run() + md, summary = weather_run(location=location) sections.append(("Weather", md, summary)) print(f" Weather: {summary}") except Exception as e: @@ -36,42 +34,49 @@ def collect_sections(): sections.append(("Weather", "## Weather\n\n*Weather data unavailable.*\n", f"error: {e}")) # --- Future sub-agents go here --- + # Each can receive config params as needed # try: # 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)) # 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 -def compose_briefing(sections): +def compose_briefing(config, sections): """Compose the full daily briefing markdown from sub-agent sections.""" now = datetime.now(MT) 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"**{date_str}** | Providence, Utah\n\n" + md = f"# {person}'s Daily Briefing\n" + md += f"**{date_str}** | {loc_label}\n\n" md += "---\n\n" for _name, section_md, _summary in sections: md += section_md + "\n\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 -def post_to_wiki(markdown, date_str): +def post_to_wiki(config, markdown, date_str): """Post the briefing to wiki under Year/Month hierarchy.""" + wiki_parent_id = config["wiki_parent_doc_id"] now = datetime.now(MT) year_str = str(now.year) month_str = MONTH_NAMES[now.month] year_id = ensure_child_doc( - WIKI_PARENT_DOC_ID, year_str, + wiki_parent_id, year_str, f"Daily briefings for {year_str}.", ) month_id = ensure_child_doc( @@ -106,22 +111,32 @@ def post_to_wiki(markdown, date_str): 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: - print("Collecting sub-agent data...") - sections = collect_sections() + print(f"Collecting sub-agent data for {config['person']}...") + sections = collect_sections(config) print("Composing briefing...") - markdown = compose_briefing(sections) + markdown = compose_briefing(config, sections) date_str = datetime.now(MT).strftime("%Y-%m-%d") 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}") summaries = "; ".join(f"{name}: {s}" for name, _, s in sections) 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, "action": action, "sub_agents": [name for name, _, _ in sections], @@ -131,9 +146,5 @@ def main(): except Exception as e: err_msg = f"{type(e).__name__}: {e}" 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) - - -if __name__ == "__main__": - main() diff --git a/agents/eric_briefing.py b/agents/eric_briefing.py new file mode 100644 index 0000000..45e88ec --- /dev/null +++ b/agents/eric_briefing.py @@ -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) diff --git a/agents/weather_agent.py b/agents/weather_agent.py index 5056a74..384f5ee 100644 --- a/agents/weather_agent.py +++ b/agents/weather_agent.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ Weather Agent -Fetches weather for Providence, UT and returns structured data + markdown section. -Called by the Daily Briefing agent. Can also run standalone. +Fetches weather for a configurable location and returns structured data + markdown. +Called by Daily Briefing agents with location config. Can also run standalone. """ import sys @@ -11,18 +11,14 @@ from shared import MT, api_request, log_run AGENT_ID = "weather" -# 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" -) +# Default location (Providence, UT) +DEFAULT_LOCATION = { + "name": "Providence", + "state": "Utah", + "country": "US", + "lat": 41.7064, + "lon": -111.8133, +} WMO_CODES = { 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"] -def fetch_weather(): - """Fetch weather data from Open-Meteo.""" - return api_request(WEATHER_URL) +def build_url(location): + """Build the Open-Meteo API URL for a given location.""" + 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.""" + loc = location or DEFAULT_LOCATION + label = location_label(loc) now = datetime.now(MT) current = weather["current"] daily = weather["daily"] @@ -55,7 +79,7 @@ def format_section(weather): wind = round(current["wind_speed_10m"]) humidity = current["relative_humidity_2m"] - md = "## Weather\n\n" + md = f"## Weather — {label}\n\n" md += "### Current Conditions\n\n" md += "| | |\n|---|---|\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 "?" 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 -def run(): +def run(location=None): """Run the weather agent. Returns (markdown_section, summary) or raises.""" - weather = fetch_weather() - section, summary = format_section(weather) - log_run(AGENT_ID, "success", output=summary) + loc = location or DEFAULT_LOCATION + weather = fetch_weather(loc) + section, summary = format_section(weather, loc) + log_run(AGENT_ID, "success", output=summary, metadata={"location": location_label(loc)}) return section, summary 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: - section, summary = run() + section, summary = run(loc) print(section) print(f"\nSummary: {summary}") except Exception as e: