ui update and some backend functionality adding in accordance with research on adhd and ux design
This commit is contained in:
132
api/routes/victories.py
Normal file
132
api/routes/victories.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
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)
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user