Files
Synculous-2/core/stats.py

161 lines
4.9 KiB
Python

"""
core/stats.py - Statistics calculations for routines
This module contains functions for calculating routine statistics,
completion rates, streaks, and weekly summaries.
"""
from datetime import datetime, timedelta, date
import core.postgres as postgres
def get_routine_stats(routine_id, user_uuid, days=30):
"""
Get completion statistics for a routine over a period.
Returns dict with completion_rate, avg_duration, total_time, etc.
"""
sessions = postgres.select(
"routine_sessions",
{"routine_id": routine_id, "user_uuid": user_uuid},
limit=days * 3,
)
completed = sum(1 for s in sessions if s.get("status") == "completed")
aborted = sum(1 for s in sessions if s.get("status") == "aborted")
total_duration = sum(
s.get("actual_duration_minutes", 0) or 0
for s in sessions
if s.get("actual_duration_minutes")
)
avg_duration = total_duration / completed if completed > 0 else 0
completion_rate = (completed / len(sessions) * 100) if sessions else 0
return {
"total_sessions": len(sessions),
"completed": completed,
"aborted": aborted,
"completion_rate_percent": round(completion_rate, 1),
"avg_duration_minutes": round(avg_duration, 1),
"total_time_minutes": total_duration,
}
def get_user_streaks(user_uuid):
"""
Get all streaks for a user across all routines.
Returns list of streak objects with routine names.
"""
streaks = postgres.select("routine_streaks", {"user_uuid": user_uuid})
routines = postgres.select("routines", {"user_uuid": user_uuid})
routine_map = {r["id"]: r["name"] for r in routines}
result = []
for streak in streaks:
result.append({
"routine_id": streak["routine_id"],
"routine_name": routine_map.get(streak["routine_id"], "Unknown"),
"current_streak": streak["current_streak"],
"longest_streak": streak["longest_streak"],
"last_completed_date": streak.get("last_completed_date"),
})
return result
def get_weekly_summary(user_uuid):
"""
Get weekly progress summary for a user.
Returns total completed, total time, routines started, per-routine breakdown.
"""
routines = postgres.select("routines", {"user_uuid": user_uuid})
if not routines:
return {
"total_completed": 0,
"total_time_minutes": 0,
"routines_started": 0,
"routines": [],
}
week_ago = datetime.now() - timedelta(days=7)
sessions = postgres.select("routine_sessions", {"user_uuid": user_uuid})
week_sessions = [
s for s in sessions
if s.get("created_at") and s["created_at"] >= week_ago
]
completed = [s for s in week_sessions if s.get("status") == "completed"]
total_time = sum(
s.get("actual_duration_minutes", 0) or 0
for s in completed
if s.get("actual_duration_minutes")
)
routine_summaries = []
for routine in routines:
r_sessions = [s for s in week_sessions if s.get("routine_id") == routine["id"]]
r_completed = sum(1 for s in r_sessions if s.get("status") == "completed")
routine_summaries.append({
"routine_id": routine["id"],
"name": routine["name"],
"completed_this_week": r_completed,
})
return {
"total_completed": len(completed),
"total_time_minutes": total_time,
"routines_started": len(set(s.get("routine_id") for s in week_sessions)),
"routines": routine_summaries,
}
def calculate_completion_rate(sessions, completed_only=True):
"""Calculate completion rate from a list of sessions."""
if not sessions:
return 0.0
if completed_only:
completed = sum(1 for s in sessions if s.get("status") == "completed")
return (completed / len(sessions)) * 100
return 0.0
def get_monthly_summary(user_uuid, year=None, month=None):
"""
Get monthly progress summary.
Defaults to current month if year/month not specified.
"""
if year is None or month is None:
now = datetime.now()
year = now.year
month = now.month
start_date = datetime(year, month, 1)
if month == 12:
end_date = datetime(year + 1, 1, 1)
else:
end_date = datetime(year, month + 1, 1)
sessions = postgres.select("routine_sessions", {"user_uuid": user_uuid})
month_sessions = [
s for s in sessions
if s.get("created_at") and start_date <= s["created_at"] < end_date
]
completed = [s for s in month_sessions if s.get("status") == "completed"]
total_time = sum(
s.get("actual_duration_minutes", 0) or 0
for s in completed
if s.get("actual_duration_minutes")
)
return {
"year": year,
"month": month,
"total_sessions": len(month_sessions),
"completed": len(completed),
"total_time_minutes": total_time,
}