From 22a13636e85124cde70faf5c82fb61267a9884b2 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 10 Apr 2026 15:01:01 -0400 Subject: [PATCH] fix(events): null end_dt no longer matches events outside the window services/events.py::list_events treated any event with end_dt IS NULL as perpetually matching, so a point-event created last week would be returned as "happening today" whenever the briefing or list_events tool queried today's window. That manifested as briefings claiming past events were scheduled for the current day. Split the null-end branch: point events now only match when start_dt falls within [date_from, date_to]. Events with an end_dt continue to use standard overlap logic. This bug affected both the legacy briefing synthesis and the agentic list_events tool path, since both call the same events service. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/fabledassistant/services/events.py | 29 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/fabledassistant/services/events.py b/src/fabledassistant/services/events.py index c1679c1..381e655 100644 --- a/src/fabledassistant/services/events.py +++ b/src/fabledassistant/services/events.py @@ -7,7 +7,7 @@ import uuid from datetime import datetime, timedelta, timezone from dateutil.rrule import rrulestr -from sqlalchemy import or_, select +from sqlalchemy import and_, or_, select from fabledassistant.models import async_session from fabledassistant.models.event import Event @@ -85,19 +85,30 @@ async def list_events( dicts (same shape as Event.to_dict()). """ async with async_session() as session: + # Match strategy: + # - Recurring events: fetch all, expand via rrule below. + # - Non-recurring with an end_dt: standard overlap — starts before + # date_to and ends after date_from. + # - Non-recurring with no end_dt: treat as a point event at + # start_dt, include only if start_dt falls within the window. + # (Previously this branch matched any event with a null end_dt, + # returning all past events as "happening today".) result = await session.execute( select(Event).where( Event.user_id == user_id, - # Base window: non-recurring events must overlap range; - # recurring events always need to be fetched so they can be expanded. or_( Event.recurrence.isnot(None), - Event.start_dt <= date_to, - ), - or_( - Event.end_dt.is_(None), - Event.end_dt >= date_from, - Event.recurrence.isnot(None), + and_( + Event.recurrence.is_(None), + Event.start_dt <= date_to, + or_( + Event.end_dt >= date_from, + and_( + Event.end_dt.is_(None), + Event.start_dt >= date_from, + ), + ), + ), ), ).order_by(Event.start_dt) )