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 today — internal store
|
||||||
calendar_events: list[str] = []
|
calendar_events: list[str] = []
|
||||||
|
internal_events: list[dict] = []
|
||||||
try:
|
try:
|
||||||
from fabledassistant.services.events import list_events as list_internal_events
|
from fabledassistant.services.events import list_events as list_internal_events
|
||||||
today_date = datetime.now(user_tz).date()
|
today_date = datetime.now(user_tz).date()
|
||||||
@@ -225,6 +226,51 @@ async def _gather_internal(user_id: int) -> dict:
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("Failed to gather projects for briefing", exc_info=True)
|
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 {
|
return {
|
||||||
"date": today,
|
"date": today,
|
||||||
"overdue_tasks": overdue,
|
"overdue_tasks": overdue,
|
||||||
@@ -233,6 +279,8 @@ async def _gather_internal(user_id: int) -> dict:
|
|||||||
"calendar_events": calendar_events,
|
"calendar_events": calendar_events,
|
||||||
"active_projects": projects_summary,
|
"active_projects": projects_summary,
|
||||||
"all_tasks_raw": all_tasks,
|
"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.extend(f" - {t}" for t in internal_data["high_priority"])
|
||||||
lines.append("")
|
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)
|
# News highlights (top 3 with excerpts — right panel shows full list)
|
||||||
rss = external_data.get("rss_items") or []
|
rss = external_data.get("rss_items") or []
|
||||||
if rss:
|
if rss:
|
||||||
|
|||||||
Reference in New Issue
Block a user