First synculous 2 Big-Pickle pass.
This commit is contained in:
154
api/routes/routine_stats.py
Normal file
154
api/routes/routine_stats.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""
|
||||
Routine Stats API - completion statistics and streaks
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import flask
|
||||
import jwt
|
||||
import core.auth as auth
|
||||
import core.postgres as postgres
|
||||
|
||||
|
||||
def _get_user_uuid(token):
|
||||
try:
|
||||
payload = jwt.decode(token, os.getenv("JWT_SECRET"), algorithms=["HS256"])
|
||||
return payload.get("sub")
|
||||
except (jwt.ExpiredSignatureError, jwt.InvalidTokenError):
|
||||
return None
|
||||
|
||||
|
||||
def _auth(request):
|
||||
header = request.headers.get("Authorization", "")
|
||||
if not header.startswith("Bearer "):
|
||||
return None
|
||||
token = header[7:]
|
||||
user_uuid = _get_user_uuid(token)
|
||||
if not user_uuid or not auth.verifyLoginToken(token, userUUID=user_uuid):
|
||||
return None
|
||||
return user_uuid
|
||||
|
||||
|
||||
def register(app):
|
||||
|
||||
@app.route("/api/routines/<routine_id>/stats", methods=["GET"])
|
||||
def api_routineStats(routine_id):
|
||||
"""Get completion stats for a routine. Query: ?days=30"""
|
||||
user_uuid = _auth(flask.request)
|
||||
if not user_uuid:
|
||||
return flask.jsonify({"error": "unauthorized"}), 401
|
||||
routine = postgres.select_one("routines", {"id": routine_id, "user_uuid": user_uuid})
|
||||
if not routine:
|
||||
return flask.jsonify({"error": "routine not found"}), 404
|
||||
days = flask.request.args.get("days", 30, type=int)
|
||||
sessions = postgres.select(
|
||||
"routine_sessions",
|
||||
where={"routine_id": routine_id},
|
||||
limit=days,
|
||||
)
|
||||
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 flask.jsonify({
|
||||
"routine_id": routine_id,
|
||||
"routine_name": routine["name"],
|
||||
"period_days": days,
|
||||
"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,
|
||||
}), 200
|
||||
|
||||
@app.route("/api/routines/streaks", methods=["GET"])
|
||||
def api_userStreaks():
|
||||
"""Get all streaks for the user."""
|
||||
user_uuid = _auth(flask.request)
|
||||
if not user_uuid:
|
||||
return flask.jsonify({"error": "unauthorized"}), 401
|
||||
streaks = postgres.select("routine_streaks", where={"user_uuid": user_uuid})
|
||||
routines = postgres.select("routines", where={"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 flask.jsonify(result), 200
|
||||
|
||||
@app.route("/api/routines/<routine_id>/streak", methods=["GET"])
|
||||
def api_routineStreak(routine_id):
|
||||
"""Get streak for a specific routine."""
|
||||
user_uuid = _auth(flask.request)
|
||||
if not user_uuid:
|
||||
return flask.jsonify({"error": "unauthorized"}), 401
|
||||
routine = postgres.select_one("routines", {"id": routine_id, "user_uuid": user_uuid})
|
||||
if not routine:
|
||||
return flask.jsonify({"error": "routine not found"}), 404
|
||||
streak = postgres.select_one("routine_streaks", {"routine_id": routine_id, "user_uuid": user_uuid})
|
||||
if not streak:
|
||||
return flask.jsonify({
|
||||
"routine_id": routine_id,
|
||||
"routine_name": routine["name"],
|
||||
"current_streak": 0,
|
||||
"longest_streak": 0,
|
||||
"last_completed_date": None,
|
||||
}), 200
|
||||
return flask.jsonify({
|
||||
"routine_id": routine_id,
|
||||
"routine_name": routine["name"],
|
||||
"current_streak": streak["current_streak"],
|
||||
"longest_streak": streak["longest_streak"],
|
||||
"last_completed_date": streak.get("last_completed_date"),
|
||||
}), 200
|
||||
|
||||
@app.route("/api/routines/weekly-summary", methods=["GET"])
|
||||
def api_weeklySummary():
|
||||
"""Get weekly progress summary."""
|
||||
user_uuid = _auth(flask.request)
|
||||
if not user_uuid:
|
||||
return flask.jsonify({"error": "unauthorized"}), 401
|
||||
routines = postgres.select("routines", where={"user_uuid": user_uuid})
|
||||
routine_ids = [r["id"] for r in routines]
|
||||
if not routine_ids:
|
||||
return flask.jsonify({
|
||||
"total_completed": 0,
|
||||
"total_time_minutes": 0,
|
||||
"routines_started": 0,
|
||||
"routines": [],
|
||||
}), 200
|
||||
week_ago = (datetime.now() - timedelta(days=7)).isoformat()
|
||||
sessions = postgres.select("routine_sessions", where={"user_uuid": user_uuid})
|
||||
week_sessions = [s for s in sessions if s.get("created_at") and str(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 flask.jsonify({
|
||||
"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,
|
||||
}), 200
|
||||
Reference in New Issue
Block a user