feat(briefing): actionable intelligence — days overdue, project context, suggest next steps

This commit is contained in:
2026-04-08 20:33:16 -04:00
parent f9c6802939
commit 2e3f90384d
@@ -23,14 +23,25 @@ SLOT_NAMES = ("compilation", "morning", "midday", "afternoon")
def format_task(task: dict) -> str:
def format_task(task: dict, today: str | None = None) -> str:
parts = [task.get("title", "Untitled")]
if task.get("status"):
parts.append(f"[{task['status'].replace('_', ' ').title()}]")
if task.get("due_date"):
parts.append(f"due {task['due_date']}")
if today and task["due_date"] < today and task.get("status") not in ("done", "cancelled"):
from datetime import date
try:
due = date.fromisoformat(task["due_date"])
td = date.fromisoformat(today)
days = (td - due).days
parts.append(f"({days} day{'s' if days != 1 else ''} overdue)")
except ValueError:
pass
if task.get("priority") and task["priority"] not in ("none", ""):
parts.append(f"priority: {task['priority']}")
if task.get("project_title"):
parts.append(f"project: {task['project_title']}")
return "".join(parts)
@@ -134,6 +145,13 @@ async def _gather_internal(user_id: int) -> dict:
all_tasks: list[dict] = []
try:
all_task_objs, _total = await list_notes(user_id, is_task=True, limit=100)
# Build project title lookup for enrichment
project_titles: dict[int, str] = {}
try:
for p in await list_projects(user_id):
project_titles[p.id] = p.title or "Untitled"
except Exception:
pass
all_tasks = [
{
"task_id": t.id,
@@ -141,21 +159,22 @@ async def _gather_internal(user_id: int) -> dict:
"status": t.status,
"due_date": t.due_date.isoformat() if t.due_date else None,
"priority": t.priority,
"project_title": project_titles.get(t.project_id) if t.project_id else None,
}
for t in all_task_objs
]
overdue = [
format_task(t) for t in all_tasks
if t.get("due_date") and t["due_date"] < today and t.get("status") != "done"
format_task(t, today) for t in all_tasks
if t.get("due_date") and t["due_date"] < today and t.get("status") not in ("done", "cancelled")
]
due_today = [
format_task(t) for t in all_tasks
if t.get("due_date") == today and t.get("status") != "done"
format_task(t, today) for t in all_tasks
if t.get("due_date") == today and t.get("status") not in ("done", "cancelled")
]
high_priority = [
format_task(t) for t in all_tasks
if t.get("priority") == "high" and t.get("status") not in ("done",) and
format_task(t) not in overdue and format_task(t) not in due_today
format_task(t, today) for t in all_tasks
if t.get("priority") == "high" and t.get("status") not in ("done", "cancelled") and
format_task(t, today) not in overdue and format_task(t, today) not in due_today
][:5]
except Exception:
logger.warning("Failed to gather tasks for briefing", exc_info=True)
@@ -267,8 +286,11 @@ def _unified_system_prompt(profile_body: str) -> str:
"Weave together what matters today: mention the weather in a sentence, note any calendar "
"events or tasks due today, and briefly reference one or two noteworthy news stories. "
"Only mention projects if a task from one is specifically due today. "
"Be warm, concise, and human — aim for 3 to 5 sentences. "
"Future context like emails and messages will be added over time — keep the tone open and helpful.\n\n"
"Be warm, concise, and human — aim for 4 to 8 sentences.\n\n"
"IMPORTANT: Be actionable, not just informational. For overdue or due-today tasks, "
"suggest a concrete next step — offer to reschedule, mark in progress, or break it down. "
"For upcoming events, mention if preparation might help. "
"The user can reply to act on your suggestions via tool calls.\n\n"
+ (f"User profile:\n{profile_body}\n" if profile_body else "")
)
@@ -300,17 +322,23 @@ def _unified_user_prompt(internal_data: dict, external_data: dict, slot: str, te
# Tasks due today
if internal_data.get("due_today"):
lines.append("DUE TODAY:")
lines.append("DUE TODAY (suggest next steps — offer to mark in progress or help prioritize):")
lines.extend(f" - {t}" for t in internal_data["due_today"])
lines.append("")
# Overdue tasks (brief mention only)
# Overdue tasks
if internal_data.get("overdue_tasks"):
overdue = internal_data["overdue_tasks"]
lines.append(f"OVERDUE ({len(overdue)} task{'s' if len(overdue) != 1 else ''}):")
lines.extend(f" - {t}" for t in overdue[:3])
if len(overdue) > 3:
lines.append(f" (and {len(overdue) - 3} more)")
lines.append(f"OVERDUE ({len(overdue)} task{'s' if len(overdue) != 1 else ''}) — suggest rescheduling or breaking down:")
lines.extend(f" - {t}" for t in overdue[:5])
if len(overdue) > 5:
lines.append(f" (and {len(overdue) - 5} more)")
lines.append("")
# High priority in-progress
if internal_data.get("high_priority"):
lines.append("HIGH PRIORITY:")
lines.extend(f" - {t}" for t in internal_data["high_priority"])
lines.append("")
# News highlights (top 3 with excerpts — right panel shows full list)