161 lines
4.9 KiB
Python
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,
|
|
}
|