feat: group private apps into andreknie-privat

This commit is contained in:
2026-07-25 15:47:01 +02:00
parent 4c7d48ae6d
commit 2e83750cb7
109 changed files with 0 additions and 0 deletions
@@ -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",
}