"""Daily Digest and Weekly Retrospective generation.""" from datetime import datetime, timedelta from zoneinfo import ZoneInfo from sqlalchemy.orm import Session from sqlalchemy import func from app.models import Task, Event, CallItem BERLIN_TZ = ZoneInfo("Europe/Berlin") def generate_daily_digest(db: Session) -> dict: """Generate a structured daily digest. Returns a dict with sections: greeting, today_events, top_tasks, overdue, due_today, new_since_yesterday, stats. """ now = datetime.now(BERLIN_TZ) today_start = datetime.combine(now.date(), datetime.min.time()).replace(tzinfo=BERLIN_TZ) today_end = datetime.combine(now.date(), datetime.max.time()).replace(tzinfo=BERLIN_TZ) yesterday = today_start - timedelta(days=1) # Greeting based on time of day hour = now.hour if hour < 12: greeting = "Good morning" elif hour < 17: greeting = "Good afternoon" else: greeting = "Good evening" # Today's events todays_events = db.query(Event).filter( Event.start_time >= today_start, Event.start_time <= today_end ).order_by(Event.start_time).all() # Top 5 priority tasks top_tasks = db.query(Task).filter( Task.status == "open" ).order_by(Task.priority, Task.position).limit(5).all() # Overdue tasks overdue = db.query(Task).filter( Task.status == "open", Task.due_date != None, Task.due_date < today_start ).order_by(Task.due_date).all() # Due today due_today = db.query(Task).filter( Task.status == "open", Task.due_date != None, Task.due_date >= today_start, Task.due_date <= today_end ).all() # New tasks since yesterday new_tasks = db.query(Task).filter( Task.status == "open", Task.created_at >= yesterday ).count() # Total open total_open = db.query(Task).filter(Task.status == "open").count() # Pending calls pending_calls = db.query(CallItem).filter( CallItem.status == "pending" ).all() priority_labels = {1: "Very High", 2: "High", 3: "Medium", 4: "Low"} result = { "greeting": greeting, "date": now.strftime("%A, %B %d, %Y"), "total_open": total_open, "events_today": [ { "title": e.title, "time": f"{e.start_time.astimezone(BERLIN_TZ).strftime('%H:%M')} - {e.end_time.astimezone(BERLIN_TZ).strftime('%H:%M')}" } for e in todays_events ], "top_tasks": [ {"id": t.id, "title": t.title, "priority": priority_labels.get(t.priority, "Low")} for t in top_tasks ], "overdue": [ { "id": t.id, "title": t.title, "days_overdue": (now.date() - t.due_date.date()).days } for t in overdue ], "due_today": [ {"id": t.id, "title": t.title} for t in due_today ], "new_since_yesterday": new_tasks, "stats": { "events_count": len(todays_events), "overdue_count": len(overdue), "due_today_count": len(due_today), } } # Only include pending_calls section if there are pending calls if pending_calls: result["pending_calls"] = [ {"person": c.person, "reason": c.reason} for c in pending_calls ] return result def generate_weekly_retro(db: Session) -> dict: """Generate a weekly retrospective summary. Looks at the past 7 days: what was completed, what slipped, what's still open, and workload trends. """ now = datetime.now(BERLIN_TZ) week_ago = now - timedelta(days=7) today_start = datetime.combine(now.date(), datetime.min.time()).replace(tzinfo=BERLIN_TZ) # Tasks completed this week (check for status change isn't tracked, # so we look at tasks with status=completed that exist) completed_this_week = db.query(Task).filter( Task.status == "completed" ).count() # Tasks still open total_open = db.query(Task).filter(Task.status == "open").count() # Tasks in review in_review = db.query(Task).filter(Task.status == "review").count() # Overdue tasks overdue = db.query(Task).filter( Task.status == "open", Task.due_date != None, Task.due_date < today_start ).order_by(Task.due_date).all() # Tasks created this week created_this_week = db.query(Task).filter( Task.created_at >= week_ago, Task.status.notin_(["dismissed"]) ).count() # Tasks by priority priority_breakdown = {} for prio in [1, 2, 3, 4]: count = db.query(Task).filter( Task.status == "open", Task.priority == prio ).count() priority_breakdown[prio] = count # Tasks by origin origin_breakdown = db.query( Task.origin, func.count(Task.id) ).filter(Task.status == "open").group_by(Task.origin).all() # Events this week events_this_week = db.query(Event).filter( Event.start_time >= week_ago, Event.start_time <= now ).count() priority_labels = {1: "Very High", 2: "High", 3: "Medium", 4: "Low"} return { "period": f"{week_ago.strftime('%b %d')} - {now.strftime('%b %d, %Y')}", "completed": completed_this_week, "created": created_this_week, "total_open": total_open, "in_review": in_review, "overdue": [ { "id": t.id, "title": t.title, "days_overdue": (now.date() - t.due_date.date()).days } for t in overdue ], "priority_breakdown": { priority_labels[k]: v for k, v in priority_breakdown.items() }, "origin_breakdown": { (origin or "unknown"): count for origin, count in origin_breakdown }, "events_attended": events_this_week, "net_change": created_this_week - completed_this_week, "health": "growing" if created_this_week > completed_this_week + 5 else "shrinking" if completed_this_week > created_this_week + 3 else "stable", }