Fix QA findings: null guard on end_datetime, reminders in night briefing, extract filter

- W1: Guard end_datetime null checks in DayBriefing (lines 48, 95, 112)
- W2: Include active reminders in pre-5AM night briefing fallback
- S1: Extract _not_parent_template filter constant in dashboard.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-22 02:36:11 +08:00
parent b18fc0f2c8
commit 084daf5c7f
2 changed files with 15 additions and 6 deletions

View File

@ -14,6 +14,13 @@ from app.routers.auth import get_current_session
router = APIRouter()
# Reusable filter: exclude parent template events (they have a real recurrence_rule).
# Some legacy events have "" instead of NULL, so allow both.
_not_parent_template = or_(
CalendarEvent.recurrence_rule == None,
CalendarEvent.recurrence_rule == "",
)
@router.get("/dashboard")
async def get_dashboard(
@ -31,7 +38,7 @@ async def get_dashboard(
events_query = select(CalendarEvent).where(
CalendarEvent.start_datetime >= today_start,
CalendarEvent.start_datetime <= today_end,
or_(CalendarEvent.recurrence_rule == None, CalendarEvent.recurrence_rule == ""),
_not_parent_template,
)
events_result = await db.execute(events_query)
todays_events = events_result.scalars().all()
@ -77,7 +84,7 @@ async def get_dashboard(
starred_query = select(CalendarEvent).where(
CalendarEvent.is_starred == True,
CalendarEvent.start_datetime > now,
or_(CalendarEvent.recurrence_rule == None, CalendarEvent.recurrence_rule == ""),
_not_parent_template,
).order_by(CalendarEvent.start_datetime.asc()).limit(5)
starred_result = await db.execute(starred_query)
starred_events = starred_result.scalars().all()
@ -158,7 +165,7 @@ async def get_upcoming(
events_query = select(CalendarEvent).where(
CalendarEvent.start_datetime >= today_start,
CalendarEvent.start_datetime <= cutoff_datetime,
or_(CalendarEvent.recurrence_rule == None, CalendarEvent.recurrence_rule == ""),
_not_parent_template,
)
events_result = await db.execute(events_query)
events = events_result.scalars().all()

View File

@ -45,11 +45,13 @@ export default function DayBriefing({ upcomingItems, dashboardData, weatherData
if (hour >= 21 || hour < 5) {
// Before 5 AM, "today" still matters — mention remaining items
if (todayItems.length > 0 && hour < 5) {
const remainingToday = todayEvents.filter((e) => isAfter(new Date(e.end_datetime), now));
const remainingToday = todayEvents.filter((e) => e.end_datetime && isAfter(new Date(e.end_datetime), now));
if (remainingToday.length > 0) {
parts.push(`${remainingToday.length} event${remainingToday.length > 1 ? 's' : ''} still on today.`);
} else if (todayTodos.length > 0) {
parts.push(`${todayTodos.length} task${todayTodos.length > 1 ? 's' : ''} due today.`);
} else if (activeReminders.length > 0) {
parts.push(`${activeReminders.length} reminder${activeReminders.length > 1 ? 's' : ''} set for today.`);
}
}
@ -92,7 +94,7 @@ export default function DayBriefing({ upcomingItems, dashboardData, weatherData
}
// Afternoon (12PM5PM)
else if (hour < 17) {
const remainingEvents = todayEvents.filter((e) => isAfter(new Date(e.end_datetime), now));
const remainingEvents = todayEvents.filter((e) => e.end_datetime && isAfter(new Date(e.end_datetime), now));
const completedTodos = todayTodos.length === 0;
if (remainingEvents.length === 0 && completedTodos) {
parts.push('The rest of your afternoon is clear.');
@ -109,7 +111,7 @@ export default function DayBriefing({ upcomingItems, dashboardData, weatherData
}
// Evening (5PM9PM)
else {
const eveningEvents = todayEvents.filter((e) => isAfter(new Date(e.end_datetime), now));
const eveningEvents = todayEvents.filter((e) => e.end_datetime && isAfter(new Date(e.end_datetime), now));
if (eveningEvents.length === 0 && tomorrowItems.length === 0) {
parts.push('Nothing left tonight, and tomorrow is clear too.');
} else {