feat(briefing): daily planning — prioritized focus list and calendar gap analysis

This commit is contained in:
2026-04-08 20:59:06 -04:00
parent 2e3f90384d
commit 304affb837
@@ -182,6 +182,7 @@ async def _gather_internal(user_id: int) -> dict:
# Calendar events today — internal store
calendar_events: list[str] = []
internal_events: list[dict] = []
try:
from fabledassistant.services.events import list_events as list_internal_events
today_date = datetime.now(user_tz).date()
@@ -225,6 +226,51 @@ async def _gather_internal(user_id: int) -> dict:
except Exception:
logger.warning("Failed to gather projects for briefing", exc_info=True)
# Build prioritized "Your Day" focus list
# Priority: overdue > due today > high priority in-progress > other in-progress
focus_tasks: list[str] = []
seen_titles: set[str] = set()
for t in sorted(all_tasks, key=lambda x: (
0 if (x.get("due_date") and x["due_date"] < today and x.get("status") not in ("done", "cancelled")) else
1 if (x.get("due_date") == today and x.get("status") not in ("done", "cancelled")) else
2 if (x.get("priority") == "high" and x.get("status") not in ("done", "cancelled")) else
3 if (x.get("status") == "in_progress") else 4
)):
if t.get("status") in ("done", "cancelled"):
continue
formatted = format_task(t, today)
if formatted not in seen_titles:
seen_titles.add(formatted)
focus_tasks.append(formatted)
if len(focus_tasks) >= 5:
break
# Calendar gap analysis — extract event times for LLM
event_time_blocks: list[str] = []
for e in internal_events:
try:
if e.get("all_day"):
continue
start = e.get("start_dt")
end = e.get("end_dt")
if not start:
continue
from datetime import datetime as _dt
if isinstance(start, str):
start = _dt.fromisoformat(start)
if isinstance(end, str):
end = _dt.fromisoformat(end)
s_local = start.astimezone(user_tz) if start.tzinfo else start.replace(tzinfo=timezone.utc).astimezone(user_tz)
s_str = s_local.strftime("%-I:%M %p")
if end:
e_local = end.astimezone(user_tz) if end.tzinfo else end.replace(tzinfo=timezone.utc).astimezone(user_tz)
e_str = e_local.strftime("%-I:%M %p")
event_time_blocks.append(f"{e.get('title', 'Event')}: {s_str}{e_str}")
else:
event_time_blocks.append(f"{e.get('title', 'Event')}: {s_str}?")
except Exception:
pass
return {
"date": today,
"overdue_tasks": overdue,
@@ -233,6 +279,8 @@ async def _gather_internal(user_id: int) -> dict:
"calendar_events": calendar_events,
"active_projects": projects_summary,
"all_tasks_raw": all_tasks,
"focus_tasks": focus_tasks,
"event_time_blocks": event_time_blocks,
}
@@ -341,6 +389,18 @@ def _unified_user_prompt(internal_data: dict, external_data: dict, slot: str, te
lines.extend(f" - {t}" for t in internal_data["high_priority"])
lines.append("")
# Your Day — prioritized focus + calendar gaps (compilation slot only)
if internal_data.get("focus_tasks") and slot == "compilation":
lines.append("YOUR DAY — top tasks to focus on (mention these as a suggested plan):")
for i, t in enumerate(internal_data["focus_tasks"], 1):
lines.append(f" {i}. {t}")
if internal_data.get("event_time_blocks"):
lines.append("")
lines.append(" Scheduled time blocks:")
lines.extend(f" {b}" for b in internal_data["event_time_blocks"])
lines.append(" (Identify free blocks between events for focused work.)")
lines.append("")
# News highlights (top 3 with excerpts — right panel shows full list)
rss = external_data.get("rss_items") or []
if rss: