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) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import uuid
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from dateutil.rrule import rrulestr
|
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 import async_session
|
||||||
from fabledassistant.models.event import Event
|
from fabledassistant.models.event import Event
|
||||||
@@ -85,19 +85,30 @@ async def list_events(
|
|||||||
dicts (same shape as Event.to_dict()).
|
dicts (same shape as Event.to_dict()).
|
||||||
"""
|
"""
|
||||||
async with async_session() as session:
|
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(
|
result = await session.execute(
|
||||||
select(Event).where(
|
select(Event).where(
|
||||||
Event.user_id == user_id,
|
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_(
|
or_(
|
||||||
Event.recurrence.isnot(None),
|
Event.recurrence.isnot(None),
|
||||||
Event.start_dt <= date_to,
|
and_(
|
||||||
),
|
Event.recurrence.is_(None),
|
||||||
or_(
|
Event.start_dt <= date_to,
|
||||||
Event.end_dt.is_(None),
|
or_(
|
||||||
Event.end_dt >= date_from,
|
Event.end_dt >= date_from,
|
||||||
Event.recurrence.isnot(None),
|
and_(
|
||||||
|
Event.end_dt.is_(None),
|
||||||
|
Event.start_dt >= date_from,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
).order_by(Event.start_dt)
|
).order_by(Event.start_dt)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user