Files

200 lines
6.1 KiB
Python

"""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",
}