perf: connection pooling, query consolidation, inline chart data, batch milestones
This commit is contained in:
75
app/utils.py
75
app/utils.py
@@ -4,7 +4,7 @@ Shared business-logic helpers.
|
||||
Keep route handlers thin — calculation logic lives here.
|
||||
"""
|
||||
|
||||
from app.db import query, execute
|
||||
from app.db import query, execute_many
|
||||
from app.config import SYDNEY_TZ
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@@ -66,19 +66,11 @@ def calculate_weight_change(start_w, current_w):
|
||||
# Streaks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def calculate_streak(user_id):
|
||||
"""Calculate current and best consecutive-day check-in streaks."""
|
||||
rows = query(
|
||||
"""SELECT DISTINCT (checked_in_at AT TIME ZONE 'UTC' AT TIME ZONE 'Australia/Sydney')::date AS d
|
||||
FROM checkins WHERE user_id = %s ORDER BY d DESC""",
|
||||
(user_id,),
|
||||
)
|
||||
if not rows:
|
||||
def _compute_streak_from_dates(days, today):
|
||||
"""Compute current and best streak from a sorted-desc list of dates."""
|
||||
if not days:
|
||||
return {"current": 0, "best": 0}
|
||||
|
||||
days = [r["d"] for r in rows]
|
||||
today = datetime.now(SYDNEY_TZ).date()
|
||||
|
||||
# Current streak: must include today or yesterday to count
|
||||
current = 0
|
||||
expected = today
|
||||
@@ -105,6 +97,50 @@ def calculate_streak(user_id):
|
||||
return {"current": current, "best": best}
|
||||
|
||||
|
||||
def calculate_streak(user_id):
|
||||
"""Calculate current and best consecutive-day check-in streaks."""
|
||||
rows = query(
|
||||
"""SELECT DISTINCT (checked_in_at AT TIME ZONE 'UTC' AT TIME ZONE 'Australia/Sydney')::date AS d
|
||||
FROM checkins WHERE user_id = %s ORDER BY d DESC""",
|
||||
(user_id,),
|
||||
)
|
||||
days = [r["d"] for r in rows]
|
||||
today = datetime.now(SYDNEY_TZ).date()
|
||||
return _compute_streak_from_dates(days, today)
|
||||
|
||||
|
||||
def calculate_streaks_bulk(user_ids):
|
||||
"""Calculate streaks for multiple users in a single query.
|
||||
|
||||
Returns a dict: {user_id: {"current": int, "best": int}}.
|
||||
"""
|
||||
if not user_ids:
|
||||
return {}
|
||||
|
||||
placeholders = ",".join(["%s"] * len(user_ids))
|
||||
rows = query(
|
||||
f"""SELECT user_id,
|
||||
(checked_in_at AT TIME ZONE 'UTC' AT TIME ZONE 'Australia/Sydney')::date AS d
|
||||
FROM checkins
|
||||
WHERE user_id IN ({placeholders})
|
||||
GROUP BY user_id, d
|
||||
ORDER BY user_id, d DESC""",
|
||||
tuple(user_ids),
|
||||
)
|
||||
|
||||
# Group by user
|
||||
from collections import defaultdict
|
||||
user_days = defaultdict(list)
|
||||
for r in rows:
|
||||
user_days[r["user_id"]].append(r["d"])
|
||||
|
||||
today = datetime.now(SYDNEY_TZ).date()
|
||||
result = {}
|
||||
for uid in user_ids:
|
||||
result[uid] = _compute_streak_from_dates(user_days.get(uid, []), today)
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Milestone checker
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -136,15 +172,12 @@ def check_milestones(user_id, user):
|
||||
("lost_20kg", total_lost >= 20),
|
||||
]
|
||||
|
||||
for key, achieved in milestone_checks:
|
||||
if achieved:
|
||||
try:
|
||||
execute(
|
||||
"INSERT INTO milestones (user_id, milestone_key) VALUES (%s, %s) ON CONFLICT DO NOTHING",
|
||||
(user_id, key),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
achieved = [(user_id, key) for key, ok in milestone_checks if ok]
|
||||
if achieved:
|
||||
execute_many(
|
||||
"INSERT INTO milestones (user_id, milestone_key) VALUES (%s, %s) ON CONFLICT DO NOTHING",
|
||||
achieved,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user