File size: 8,023 Bytes
465fb49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
from agentpro.tools import Tool
from typing import Any, List, Tuple, Optional
import datetime
import dateutil.parser
import re


class WeeklyEventSummaryTool(Tool):
    """

    Tool to provide a narrative summary of Google Calendar events for an entire week.

    Users can ask for β€œthis week,” β€œnext week,” β€œlast week,” or specify a date (e.g., β€œweek of June 10, 2025”).

    The tool will gather events from Monday through Sunday of the chosen week and return a day-by-day narrative.

    """

    # ── 1) Class attributes ─────────────────────────────────────────────────────────────────
    name: str = "Weekly Event Summary"
    description: str = (
        "Provide a narrative summary of Google Calendar events for any requested week. "
        "You can ask for:\n"
        "  β€’ β€œMy events this week.”\n"
        "  β€’ β€œShow my schedule next week.”\n"
        "  β€’ β€œWeekly summary for last week.”\n"
        "  β€’ β€œWhat do I have the week of June 10, 2025?”\n"
        "  β€’ β€œEvents for week of 2025-06-10.”\n"
        "If no explicit week is mentioned, defaults to this week (Monday→Sunday)."
    )
    action_type: str = "weekly_event_summary"
    input_format: str = (
        "Natural-language calendar query specifying a week. Examples:\n"
        "  β€’ β€œWhat’s on my calendar this week?”\n"
        "  β€’ β€œShow me my schedule next week.”\n"
        "  β€’ β€œWeekly summary for last week.”\n"
        "  β€’ β€œEvents for the week of June 10, 2025.”\n"
        "  β€’ β€œWeek of 2025-06-10.”"
    )

    # ── 2) We expect a Google Calendar β€œservice” to be passed in at instantiation ───────────
    service: Any

    # ── 3) Main entry point ─────────────────────────────────────────────────────────────────
    def run(self, input_text: Any) -> str:
        """

        Parse a natural-language weekly summary command, identify target week, and return a narrative summary.

        """
        text = str(input_text).strip().lower()

        # 1) Determine which week the user means
        week_start = self._resolve_week_start(text)
        if week_start is None:
            return (
                "Sorry, I couldn't determine which week you meant. "
                "Please ask for 'this week', 'next week', 'last week', or 'week of <date>'."
            )

        # 2) Build a list of dates from Monday through Sunday
        dates = [week_start + datetime.timedelta(days=i) for i in range(7)]
        week_end = dates[-1]

        # 3) For each day in the week, fetch events and build narrative parts
        narrative_parts: List[str] = []
        for day in dates:
            events = self._fetch_events_on_date(day)
            day_str = day.strftime("%A, %B %d, %Y")
            if not events:
                narrative_parts.append(f"On {day_str}, you have no events scheduled.")
            else:
                # Build a single sentence listing each event’s start time, end time, and title
                sentences: List[str] = []
                for idx, ev in enumerate(events):
                    start_raw = ev["start"].get("dateTime")
                    end_raw   = ev["end"].get("dateTime")
                    summary = ev.get("summary", "(no title)")

                    if start_raw and end_raw:
                        start_dt = datetime.datetime.fromisoformat(start_raw.replace("Z", "+00:00"))
                        end_dt   = datetime.datetime.fromisoformat(end_raw.replace("Z", "+00:00"))
                        start_str = start_dt.strftime("%I:%M %p").lstrip("0")
                        end_str   = end_dt.strftime("%I:%M %p").lstrip("0")
                        sentences.append(f"β€œ{summary}” from {start_str} to {end_str}")
                    else:
                        # Should not happen for non-all-day events since we filter them
                        sentences.append(f"β€œ{summary}” (all-day)")

                # Join individual event descriptions with β€œ; ”
                day_events_str = "; ".join(sentences)
                narrative_parts.append(f"On {day_str}, you have: {day_events_str}.")

        # 4) Combine into one multiline narrative
        week_range_str = f"{week_start.strftime('%B %d, %Y')} to {week_end.strftime('%B %d, %Y')}"
        header = f"Weekly summary for {week_range_str}:"
        body = " ".join(narrative_parts)
        return f"{header}\n\n{body}"

    # ────────────────────────────────────────────────────────────────────────────────────────────

    def _resolve_week_start(self, text: str) -> Optional[datetime.date]:
        """

        Determine the Monday (week_start) of the requested week.

        Supports 'this week', 'next week', 'last week', or 'week of <date>'.

        If no keyword found, defaults to this week.

        """
        today = datetime.date.today()
        weekday = today.weekday()  # Monday=0 ... Sunday=6
        monday_this_week = today - datetime.timedelta(days=weekday)

        # 1) Check for 'this week'
        if "this week" in text:
            return monday_this_week

        # 2) 'next week'
        if "next week" in text:
            return monday_this_week + datetime.timedelta(days=7)

        # 3) 'last week'
        if "last week" in text:
            return monday_this_week - datetime.timedelta(days=7)

        # 4) 'week of <date>'
        # Look for a date substring to parse
        # e.g., "week of june 10, 2025" or "week of 2025-06-10"
        match = re.search(
            r"week\s+of\s+(.+)", text
        )
        if match:
            date_part = match.group(1).strip()
            try:
                parsed = dateutil.parser.parse(date_part, fuzzy=True)
                target_date = parsed.date()
                # Find Monday of that week
                wd = target_date.weekday()
                return target_date - datetime.timedelta(days=wd)
            except (ValueError, OverflowError):
                return None

        # 5) If no explicit keyword, default to this week
        return monday_this_week

    def _fetch_events_on_date(self, date_obj: datetime.date) -> List[dict]:
        """

        Fetch all non-all-day events on the provided date (UTC midnight β†’ next midnight).

        Returns a list of event dicts (as returned by Google Calendar API), sorted by start time.

        """
        start_of_day = datetime.datetime.combine(date_obj, datetime.time.min).isoformat() + "Z"
        end_of_day = (datetime.datetime.combine(date_obj, datetime.time.min)
                      + datetime.timedelta(days=1)).isoformat() + "Z"

        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", [])
        # Filter out all-day events (they have 'start.date' instead of 'start.dateTime')
        non_all_day = [ev for ev in items if ev.get("start", {}).get("dateTime")]
        return sorted(
            non_all_day,
            key=lambda ev: datetime.datetime.fromisoformat(ev["start"]["dateTime"].replace("Z", "+00:00"))
        )