feat(briefing): project continuity — yesterday's activity and stale project warnings
This commit is contained in:
@@ -259,6 +259,45 @@ async def _gather_internal(user_id: int) -> dict:
|
||||
except Exception:
|
||||
logger.debug("Weather cross-reference failed", exc_info=True)
|
||||
|
||||
# Recent activity — what was the user working on yesterday?
|
||||
recent_activity: list[str] = []
|
||||
stale_projects: list[str] = []
|
||||
try:
|
||||
from sqlalchemy import select as _sel, func as _func
|
||||
from fabledassistant.models.note import Note
|
||||
from fabledassistant.models.project import Project
|
||||
|
||||
yesterday = datetime.now(user_tz) - __import__('datetime').timedelta(days=1)
|
||||
async with async_session() as session:
|
||||
# Notes/tasks updated in last 24h, grouped by project
|
||||
result = await session.execute(
|
||||
_sel(Project.title, _func.count(Note.id))
|
||||
.join(Note, Note.project_id == Project.id)
|
||||
.where(Note.user_id == user_id)
|
||||
.where(Note.updated_at >= yesterday)
|
||||
.group_by(Project.title)
|
||||
.order_by(_func.count(Note.id).desc())
|
||||
.limit(3)
|
||||
)
|
||||
for proj_title, count in result.all():
|
||||
recent_activity.append(f"{proj_title}: {count} item{'s' if count != 1 else ''} updated")
|
||||
|
||||
# Stale projects — active projects with no note/task activity in 7+ days
|
||||
week_ago = datetime.now(user_tz) - __import__('datetime').timedelta(days=7)
|
||||
active_projects_q = await session.execute(
|
||||
_sel(Project).where(Project.user_id == user_id, Project.status == "active")
|
||||
)
|
||||
for p in active_projects_q.scalars().all():
|
||||
latest = await session.execute(
|
||||
_sel(_func.max(Note.updated_at))
|
||||
.where(Note.project_id == p.id, Note.user_id == user_id)
|
||||
)
|
||||
last_activity = latest.scalar()
|
||||
if last_activity is None or last_activity < week_ago:
|
||||
stale_projects.append(p.title or "Untitled")
|
||||
except Exception:
|
||||
logger.debug("Failed to gather recent activity for briefing", exc_info=True)
|
||||
|
||||
# Projects: active projects
|
||||
projects_summary = []
|
||||
try:
|
||||
@@ -324,6 +363,8 @@ async def _gather_internal(user_id: int) -> dict:
|
||||
"focus_tasks": focus_tasks,
|
||||
"event_time_blocks": event_time_blocks,
|
||||
"weather_warnings": weather_warnings,
|
||||
"recent_activity": recent_activity,
|
||||
"stale_projects": stale_projects,
|
||||
}
|
||||
|
||||
|
||||
@@ -600,6 +641,17 @@ def _unified_user_prompt(internal_data: dict, external_data: dict, slot: str, te
|
||||
lines.append(" (Identify free blocks between events for focused work.)")
|
||||
lines.append("")
|
||||
|
||||
# Project continuity — what was the user working on?
|
||||
if internal_data.get("recent_activity") and slot == "compilation":
|
||||
lines.append("YESTERDAY'S ACTIVITY (mention what the user was working on to help them pick up):")
|
||||
lines.extend(f" - {a}" for a in internal_data["recent_activity"])
|
||||
lines.append("")
|
||||
|
||||
if internal_data.get("stale_projects") and slot == "compilation":
|
||||
lines.append("STALE PROJECTS (no activity in 7+ days — gently flag):")
|
||||
lines.extend(f" - {p}" for p in internal_data["stale_projects"])
|
||||
lines.append("")
|
||||
|
||||
# News — clustered by topic for thematic synthesis
|
||||
rss = external_data.get("rss_items") or []
|
||||
if rss:
|
||||
|
||||
Reference in New Issue
Block a user