feat(briefing): daily planning — prioritized focus list and calendar gap analysis
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user