calendar-chatbot / tools /daily_event_summary_tool.py
taha-the-data-scientist's picture
Upload 4 files
465fb49 verified
raw
history blame
9.51 kB
from agentpro.tools import Tool
from typing import Any
import datetime
import dateutil.parser
import re
class DailyEventSummaryTool(Tool):
# ── 1) Class attributes (Pydantic fields) ───────────────────────────
name: str = "Daily Event Summary"
description: str = (
"Provide a narrative summary of Google Calendar events for any requested day. "
"You can ask for today, tomorrow, day after tomorrow, last day of this year, "
"a weekday of the current week (e.g., 'Wednesday'), or an explicit date like 'June 10, 2025'."
)
action_type: str = "daily_event_summary"
input_format: str = (
"Natural language calendar query. Examples:\n"
" β€’ \"What’s on my calendar today?\"\n"
" β€’ \"Show me my schedule tomorrow.\"\n"
" β€’ \"Plan for day after tomorrow.\"\n"
" β€’ \"Events on the last day of this year.\"\n"
" β€’ \"What do I have on Wednesday?\"\n"
" β€’ \"What do I have on June 10, 2025?\"\n"
)
# ── 2) We expect a Google Calendar β€œservice” to be passed in at instantiation ──
service: Any
def run(self, input_text: Any) -> str:
"""
Determine which calendar day the user wants and return a single-sentence
narrative describing each event's start time, end time, and duration.
Supported queries:
- β€œtoday”
- β€œtomorrow”
- β€œday after tomorrow”
- β€œlast day of this year” (Dec 31, current year)
- any weekday of the current week (e.g., β€œMonday”, β€œFriday”)
- explicit dates (e.g., β€œJune 10, 2025” or β€œ2025-06-10”)
"""
text = str(input_text).lower()
# ────────────────────────────────────────────────────────────────────
# A) Handle relative-day keywords and weekdays of this week
# ────────────────────────────────────────────────────────────────────
today_utc = datetime.datetime.utcnow().date()
target_date = None
# 1) β€œtoday”
if "today" in text:
target_date = today_utc
# 2) β€œtomorrow” (but not β€œday after tomorrow”)
elif "tomorrow" in text and "day after" not in text:
target_date = today_utc + datetime.timedelta(days=1)
# 3) β€œday after tomorrow”
elif "day after tomorrow" in text:
target_date = today_utc + datetime.timedelta(days=2)
# 4) β€œlast day of this year” or β€œlast day of the year”
elif "last day of this year" in text or "last day of the year" in text:
year = today_utc.year
target_date = datetime.date(year, 12, 31)
else:
# 5) Try to match a weekday name in the current week
weekdays = {
"monday": 1,
"tuesday": 2,
"wednesday": 3,
"thursday": 4,
"friday": 5,
"saturday": 6,
"sunday": 7
}
for name, iso_num in weekdays.items():
if name in text:
# Compute offset from today's ISO weekday to the requested one
today_iso = today_utc.isoweekday() # Monday=1 ... Sunday=7
delta_days = iso_num - today_iso
target_date = today_utc + datetime.timedelta(days=delta_days)
break
# 6) If still None, try to parse an explicit date
if target_date is None and re.search(r"\d", text):
try:
parsed_dt = dateutil.parser.parse(text, fuzzy=True)
target_date = parsed_dt.date()
except (ValueError, OverflowError):
target_date = None
# If we still don't have a date, return fallback instructions
if target_date is None:
return (
"Sorry, I couldn’t figure out which day you mean.\n"
"Please ask for:\n"
" β€’ β€œtoday”\n"
" β€’ β€œtomorrow”\n"
" β€’ β€œday after tomorrow”\n"
" β€’ β€œlast day of this year”\n"
" β€’ a weekday this week (e.g., β€œWednesday”)\n"
" β€’ or specify an explicit date (e.g., β€œJune 10, 2025”)."
)
# ────────────────────────────────────────────────────────────────────
# B) Build UTC‐based timestamps for that entire day: [00:00 β†’ next 00:00)
# ────────────────────────────────────────────────────────────────────
start_of_day = datetime.datetime.combine(target_date, datetime.time.min).isoformat() + "Z"
end_of_day = (
datetime.datetime.combine(target_date, datetime.time.min)
+ datetime.timedelta(days=1)
).isoformat() + "Z"
# ────────────────────────────────────────────────────────────────────
# C) Query Google Calendar API for events in that window
# ────────────────────────────────────────────────────────────────────
events_res = (
self.service.events()
.list(
calendarId="primary",
timeMin=start_of_day,
timeMax=end_of_day,
singleEvents=True,
orderBy="startTime"
)
.execute()
)
items = events_res.get("items", [])
if not items:
return f"You have no events scheduled for {target_date.strftime('%B %d, %Y')}."
# ────────────────────────────────────────────────────────────────────
# D) Build narrative: β€œOn {date}, first meeting is β€˜X’, which will start at {h:mm AM/PM}
# and end at {h:mm AM/PM} (Duration: {N hours M minutes}), and second meeting is ….”
# ────────────────────────────────────────────────────────────────────
ordinals = [
"first", "second", "third", "fourth", "fifth",
"sixth", "seventh", "eighth", "ninth", "tenth"
]
narrative_parts = []
for idx, ev in enumerate(items):
start_raw = ev["start"].get("dateTime")
end_raw = ev["end"].get("dateTime")
summary = ev.get("summary", "(no title)")
if start_raw and end_raw:
# Timed event
start_dt = datetime.datetime.fromisoformat(start_raw.replace("Z", "+00:00"))
end_dt = datetime.datetime.fromisoformat(end_raw.replace("Z", "+00:00"))
# Format β€œH:MM AM/PM”
start_str = start_dt.strftime("%I:%M %p").lstrip("0")
end_str = end_dt.strftime("%I:%M %p").lstrip("0")
# Compute duration
duration_ts = end_dt - start_dt
total_seconds = int(duration_ts.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
if hours and minutes:
duration_str = f"{hours} hours {minutes} minutes"
elif hours:
duration_str = f"{hours} hours"
else:
duration_str = f"{minutes} minutes"
ordinal = ordinals[idx] if idx < len(ordinals) else f"{idx+1}th"
part = (
f"{ordinal} meeting is β€œ{summary},” which will start at {start_str} "
f"and end at {end_str} (Duration: {duration_str})"
)
else:
# All-day event
start_date = ev["start"].get("date")
ordinal = ordinals[idx] if idx < len(ordinals) else f"{idx+1}th"
part = f"{ordinal} event is β€œ{summary},” which is an all-day event on {start_date}"
narrative_parts.append(part)
# Join all parts with β€œ, and …”
joined = ", and ".join(narrative_parts)
date_str = target_date.strftime("%B %d, %Y")
return f"On {date_str}, {joined}."