Files
Synculous-2/api/routes/victories.py

147 lines
5.1 KiB
Python

"""
Victories API - compute noteworthy achievements from session history
"""
import os
import uuid
from datetime import datetime, timedelta
import flask
import jwt
import core.auth as auth
import core.postgres as postgres
import core.tz as tz
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/victories", methods=["GET"])
def api_getVictories():
"""Compute noteworthy achievements. Query: ?days=30"""
user_uuid = _auth(flask.request)
if not user_uuid:
return flask.jsonify({"error": "unauthorized"}), 401
days = flask.request.args.get("days", 30, type=int)
cutoff = tz.user_now() - timedelta(days=days)
# Convert to naive datetime for comparison with database timestamps
cutoff = cutoff.replace(tzinfo=None)
sessions = postgres.select("routine_sessions", {"user_uuid": user_uuid})
recent = [
s for s in sessions if s.get("created_at") and s["created_at"] >= cutoff
]
completed = [s for s in recent if s.get("status") == "completed"]
victories = []
# Comeback: completed after 2+ day gap
if completed:
sorted_completed = sorted(completed, key=lambda s: s["created_at"])
for i in range(1, len(sorted_completed)):
prev = sorted_completed[i - 1]["created_at"]
curr = sorted_completed[i]["created_at"]
gap = (curr - prev).days
if gap >= 2:
victories.append(
{
"type": "comeback",
"message": f"Came back after {gap} days — that takes real strength",
"date": curr.isoformat()
if hasattr(curr, "isoformat")
else str(curr),
}
)
# Weekend completion
for s in completed:
created = s["created_at"]
if (
hasattr(created, "weekday") and created.weekday() >= 5
): # Saturday=5, Sunday=6
victories.append(
{
"type": "weekend",
"message": "Completed a routine on the weekend",
"date": created.isoformat()
if hasattr(created, "isoformat")
else str(created),
}
)
break # Only show once
# Variety: 3+ different routines in a week
routine_ids_by_week = {}
for s in completed:
created = s["created_at"]
if hasattr(created, "isocalendar"):
week_key = created.isocalendar()[:2]
if week_key not in routine_ids_by_week:
routine_ids_by_week[week_key] = set()
routine_ids_by_week[week_key].add(s.get("routine_id"))
for week_key, routine_ids in routine_ids_by_week.items():
if len(routine_ids) >= 3:
victories.append(
{
"type": "variety",
"message": f"Completed {len(routine_ids)} different routines in one week",
"date": None,
}
)
break
# Full week consistency: completed every day for 7 consecutive days
if completed:
dates_set = set()
for s in completed:
created = s["created_at"]
if hasattr(created, "date"):
dates_set.add(created.date())
sorted_dates = sorted(dates_set)
max_streak = 1
current_streak = 1
for i in range(1, len(sorted_dates)):
if (sorted_dates[i] - sorted_dates[i - 1]).days == 1:
current_streak += 1
max_streak = max(max_streak, current_streak)
else:
current_streak = 1
if max_streak >= 7:
victories.append(
{
"type": "consistency",
"message": f"Completed routines every day for {max_streak} days straight",
"date": None,
}
)
# Limit and deduplicate
seen_types = set()
unique_victories = []
for v in victories:
if v["type"] not in seen_types:
unique_victories.append(v)
seen_types.add(v["type"])
return flask.jsonify(unique_victories[:10]), 200