from flask import Blueprint, render_template from app.auth import login_required from app.db import query, query_one from app import SYDNEY_TZ from datetime import timezone bp = Blueprint("leaderboard", __name__) @bp.route("/leaderboard") @login_required def index(): # Get all users with their weight stats users = query(""" SELECT u.id, u.display_name, u.username, u.starting_weight_kg, u.goal_weight_kg, (SELECT weight_kg FROM checkins WHERE user_id = u.id ORDER BY checked_in_at ASC LIMIT 1) as first_weight, (SELECT weight_kg FROM checkins WHERE user_id = u.id ORDER BY checked_in_at DESC LIMIT 1) as current_weight, (SELECT COUNT(*) FROM checkins WHERE user_id = u.id) as total_checkins, (SELECT checked_in_at FROM checkins WHERE user_id = u.id ORDER BY checked_in_at DESC LIMIT 1) as last_checkin FROM users u WHERE u.is_private = FALSE ORDER BY u.created_at """) # Calculate rankings ranked = [] for u in users: start_w = float(u["starting_weight_kg"] or u["first_weight"] or 0) current_w = float(u["current_weight"] or start_w) if start_w > 0: weight_lost = start_w - current_w pct_lost = round((weight_lost / start_w) * 100, 1) else: weight_lost = 0 pct_lost = 0 goal = float(u["goal_weight_kg"]) if u["goal_weight_kg"] else None goal_progress = None if goal and start_w > goal: total_to_lose = start_w - goal goal_progress = min(100, round((weight_lost / total_to_lose) * 100, 1)) if total_to_lose > 0 else 0 ranked.append({ **u, "weight_lost": round(weight_lost, 1), "pct_lost": pct_lost, "goal_progress": goal_progress, }) # Sort by % lost (descending) ranked.sort(key=lambda x: x["pct_lost"], reverse=True) # Get earliest and latest check-in dates for date pickers date_range = query_one(""" SELECT MIN(c.checked_in_at) AS earliest, MAX(c.checked_in_at) AS latest FROM checkins c JOIN users u ON u.id = c.user_id WHERE u.is_private = FALSE """) earliest = "" latest = "" if date_range and date_range["earliest"]: e = date_range["earliest"] l = date_range["latest"] if e.tzinfo is None: e = e.replace(tzinfo=timezone.utc) if l.tzinfo is None: l = l.replace(tzinfo=timezone.utc) earliest = e.astimezone(SYDNEY_TZ).strftime("%Y-%m-%d") latest = l.astimezone(SYDNEY_TZ).strftime("%Y-%m-%d") return render_template("leaderboard.html", ranked=ranked, earliest=earliest, latest=latest)