feat: group private apps into andreknie-privat
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
"""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",
|
||||
}
|
||||
Reference in New Issue
Block a user