perf: connection pooling, query consolidation, inline chart data, batch milestones
This commit is contained in:
@@ -44,18 +44,24 @@ def progress_over_time():
|
||||
|
||||
where_sql = " AND ".join(where_clauses)
|
||||
|
||||
# Use CTE for first_weight instead of correlated subquery
|
||||
rows = query(f"""
|
||||
WITH first_weights AS (
|
||||
SELECT DISTINCT ON (user_id) user_id, weight_kg AS first_weight
|
||||
FROM checkins
|
||||
ORDER BY user_id, checked_in_at ASC
|
||||
)
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
u.display_name,
|
||||
u.username,
|
||||
u.starting_weight_kg,
|
||||
(SELECT weight_kg FROM checkins
|
||||
WHERE user_id = u.id ORDER BY checked_in_at ASC LIMIT 1) AS first_weight,
|
||||
fw.first_weight,
|
||||
c.weight_kg,
|
||||
c.checked_in_at
|
||||
FROM checkins c
|
||||
JOIN users u ON u.id = c.user_id
|
||||
LEFT JOIN first_weights fw ON fw.user_id = u.id
|
||||
WHERE {where_sql}
|
||||
ORDER BY u.id, c.checked_in_at ASC
|
||||
""", params)
|
||||
@@ -144,17 +150,26 @@ def chart_data(user_id):
|
||||
@login_required
|
||||
def comparison():
|
||||
"""Return all-user comparison data for bar charts."""
|
||||
# Use CTE with window functions instead of correlated subqueries
|
||||
users = query("""
|
||||
WITH user_weights AS (
|
||||
SELECT
|
||||
user_id,
|
||||
FIRST_VALUE(weight_kg) OVER (PARTITION BY user_id ORDER BY checked_in_at ASC) AS first_weight,
|
||||
FIRST_VALUE(weight_kg) OVER (PARTITION BY user_id ORDER BY checked_in_at DESC) AS current_weight,
|
||||
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY checked_in_at DESC) AS rn
|
||||
FROM checkins
|
||||
)
|
||||
SELECT
|
||||
u.id,
|
||||
u.display_name,
|
||||
u.username,
|
||||
u.starting_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
|
||||
uw.first_weight,
|
||||
uw.current_weight
|
||||
FROM users u
|
||||
WHERE (SELECT COUNT(*) FROM checkins WHERE user_id = u.id) > 0
|
||||
AND u.is_private = FALSE
|
||||
JOIN user_weights uw ON uw.user_id = u.id AND uw.rn = 1
|
||||
WHERE u.is_private = FALSE
|
||||
ORDER BY u.display_name
|
||||
""")
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from datetime import timezone
|
||||
from flask import Blueprint, render_template
|
||||
from app.auth import login_required, get_current_user
|
||||
from app.db import query, query_one
|
||||
from app.config import SYDNEY_TZ
|
||||
from app.utils import calculate_streak, calculate_weight_change
|
||||
|
||||
bp = Blueprint("dashboard", __name__)
|
||||
@@ -10,38 +12,53 @@ bp = Blueprint("dashboard", __name__)
|
||||
@login_required
|
||||
def index():
|
||||
user = get_current_user()
|
||||
uid = user["id"]
|
||||
|
||||
# Get latest check-in
|
||||
latest = query_one(
|
||||
"SELECT * FROM checkins WHERE user_id = %s ORDER BY checked_in_at DESC LIMIT 1",
|
||||
(user["id"],),
|
||||
)
|
||||
|
||||
# Get check-in count
|
||||
stats = query_one(
|
||||
"SELECT COUNT(*) as total_checkins FROM checkins WHERE user_id = %s",
|
||||
(user["id"],),
|
||||
)
|
||||
|
||||
# Calculate weight change
|
||||
first_checkin = query_one(
|
||||
"SELECT weight_kg FROM checkins WHERE user_id = %s ORDER BY checked_in_at ASC LIMIT 1",
|
||||
(user["id"],),
|
||||
)
|
||||
# --- Single query: latest, first, count via window functions ----------
|
||||
summary = query_one("""
|
||||
SELECT
|
||||
total,
|
||||
first_weight,
|
||||
latest_weight,
|
||||
latest_bmi,
|
||||
latest_at
|
||||
FROM (
|
||||
SELECT
|
||||
COUNT(*) OVER () AS total,
|
||||
FIRST_VALUE(weight_kg) OVER (ORDER BY checked_in_at ASC) AS first_weight,
|
||||
FIRST_VALUE(weight_kg) OVER (ORDER BY checked_in_at DESC) AS latest_weight,
|
||||
FIRST_VALUE(bmi) OVER (ORDER BY checked_in_at DESC) AS latest_bmi,
|
||||
FIRST_VALUE(checked_in_at) OVER (ORDER BY checked_in_at DESC) AS latest_at,
|
||||
ROW_NUMBER() OVER (ORDER BY checked_in_at DESC) AS rn
|
||||
FROM checkins
|
||||
WHERE user_id = %s
|
||||
) sub
|
||||
WHERE rn = 1
|
||||
""", (uid,))
|
||||
|
||||
# Build lightweight "latest" dict for the template
|
||||
latest = None
|
||||
weight_change = None
|
||||
weight_change_pct = None
|
||||
if latest and first_checkin:
|
||||
total_checkins = 0
|
||||
|
||||
if summary:
|
||||
total_checkins = summary["total"]
|
||||
latest = {
|
||||
"weight_kg": summary["latest_weight"],
|
||||
"bmi": summary["latest_bmi"],
|
||||
"checked_in_at": summary["latest_at"],
|
||||
}
|
||||
kg_lost, pct_lost = calculate_weight_change(
|
||||
first_checkin["weight_kg"], latest["weight_kg"]
|
||||
summary["first_weight"], summary["latest_weight"]
|
||||
)
|
||||
weight_change = round(-kg_lost, 1) # negative = gained, positive = lost
|
||||
weight_change = round(-kg_lost, 1)
|
||||
weight_change_pct = round(-pct_lost, 1)
|
||||
|
||||
# Recent check-ins (last 5)
|
||||
recent_checkins = query(
|
||||
"SELECT * FROM checkins WHERE user_id = %s ORDER BY checked_in_at DESC LIMIT 5",
|
||||
(user["id"],),
|
||||
(uid,),
|
||||
)
|
||||
|
||||
# Activity feed (recent check-ins from all users)
|
||||
@@ -52,26 +69,57 @@ def index():
|
||||
WHERE u.is_private = FALSE OR u.id = %s
|
||||
ORDER BY c.checked_in_at DESC
|
||||
LIMIT 10
|
||||
""", (user["id"],))
|
||||
""", (uid,))
|
||||
|
||||
# Milestones
|
||||
milestones = query(
|
||||
"SELECT * FROM milestones WHERE user_id = %s ORDER BY achieved_at DESC",
|
||||
(user["id"],),
|
||||
(uid,),
|
||||
)
|
||||
|
||||
# Streak
|
||||
streak = calculate_streak(user["id"])
|
||||
streak = calculate_streak(uid)
|
||||
|
||||
# --- Pre-compute chart data (eliminates 2 client-side fetches) --------
|
||||
chart_checkins = query(
|
||||
"""SELECT weight_kg, bmi, checked_in_at
|
||||
FROM checkins WHERE user_id = %s
|
||||
ORDER BY checked_in_at ASC""",
|
||||
(uid,),
|
||||
)
|
||||
|
||||
chart_labels = []
|
||||
chart_weights = []
|
||||
weekly_labels = []
|
||||
weekly_changes = []
|
||||
|
||||
for i, c in enumerate(chart_checkins):
|
||||
dt = c["checked_in_at"]
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
label = dt.astimezone(SYDNEY_TZ).strftime("%d %b")
|
||||
chart_labels.append(label)
|
||||
chart_weights.append(float(c["weight_kg"]))
|
||||
|
||||
if i > 0:
|
||||
prev_w = float(chart_checkins[i - 1]["weight_kg"])
|
||||
curr_w = float(c["weight_kg"])
|
||||
weekly_labels.append(label)
|
||||
weekly_changes.append(round(curr_w - prev_w, 1))
|
||||
|
||||
return render_template(
|
||||
"dashboard.html",
|
||||
user=user,
|
||||
latest=latest,
|
||||
stats=stats,
|
||||
stats={"total_checkins": total_checkins},
|
||||
weight_change=weight_change,
|
||||
weight_change_pct=weight_change_pct,
|
||||
recent_checkins=recent_checkins,
|
||||
activity=activity,
|
||||
milestones=milestones,
|
||||
streak=streak,
|
||||
chart_labels=chart_labels,
|
||||
chart_weights=chart_weights,
|
||||
weekly_labels=weekly_labels,
|
||||
weekly_changes=weekly_changes,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ from flask import Blueprint, render_template
|
||||
from app.auth import login_required
|
||||
from app.db import query, query_one
|
||||
from app.config import SYDNEY_TZ
|
||||
from app.utils import calculate_streak, calculate_weight_change
|
||||
from app.utils import calculate_streaks_bulk, calculate_weight_change
|
||||
from datetime import timezone
|
||||
|
||||
bp = Blueprint("leaderboard", __name__)
|
||||
@@ -11,23 +11,38 @@ bp = Blueprint("leaderboard", __name__)
|
||||
@bp.route("/leaderboard")
|
||||
@login_required
|
||||
def index():
|
||||
# Get all users with their weight stats
|
||||
# Get all users with weight stats using window functions (no correlated subqueries)
|
||||
users = query("""
|
||||
WITH user_weights AS (
|
||||
SELECT
|
||||
user_id,
|
||||
FIRST_VALUE(weight_kg) OVER (PARTITION BY user_id ORDER BY checked_in_at ASC) AS first_weight,
|
||||
FIRST_VALUE(weight_kg) OVER (PARTITION BY user_id ORDER BY checked_in_at DESC) AS current_weight,
|
||||
COUNT(*) OVER (PARTITION BY user_id) AS total_checkins,
|
||||
MAX(checked_in_at) OVER (PARTITION BY user_id) AS last_checkin,
|
||||
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY checked_in_at DESC) AS rn
|
||||
FROM checkins
|
||||
)
|
||||
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
|
||||
uw.first_weight,
|
||||
uw.current_weight,
|
||||
uw.total_checkins,
|
||||
uw.last_checkin
|
||||
FROM users u
|
||||
JOIN user_weights uw ON uw.user_id = u.id AND uw.rn = 1
|
||||
WHERE u.is_private = FALSE
|
||||
ORDER BY u.created_at
|
||||
""")
|
||||
|
||||
# Batch-compute streaks for all users in one query
|
||||
user_ids = [u["id"] for u in users]
|
||||
all_streaks = calculate_streaks_bulk(user_ids)
|
||||
|
||||
# Calculate rankings
|
||||
ranked = []
|
||||
for u in users:
|
||||
@@ -41,7 +56,7 @@ def index():
|
||||
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
|
||||
|
||||
streak = calculate_streak(u["id"])
|
||||
streak = all_streaks.get(u["id"], {"current": 0, "best": 0})
|
||||
ranked.append({
|
||||
**u,
|
||||
"weight_lost": weight_lost,
|
||||
|
||||
Reference in New Issue
Block a user