From 92cea4dc99d180d705238a3adf02f921d04d163f Mon Sep 17 00:00:00 2001 From: owain Date: Wed, 1 Jul 2026 21:04:46 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20route=20leaderboards=20rank=20by=20movin?= =?UTF-8?q?g=20time=20not=20elapsed=20=E2=80=94=20top-10=20route=20times?= =?UTF-8?q?=20and=20route=20completions=20list=20now=20use=20COALESCE(movi?= =?UTF-8?q?ng=5Ftime=5Fs,=20duration=5Fs)=20so=20mid-activity=20pauses=20d?= =?UTF-8?q?on't=20inflate=20times=20(consistent=20with=20moving-time=20avg?= =?UTF-8?q?=20pace)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/activities.py | 8 ++++++-- backend/app/api/routes.py | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/app/api/activities.py b/backend/app/api/activities.py index c68c5ee..bc1f732 100644 --- a/backend/app/api/activities.py +++ b/backend/app/api/activities.py @@ -423,17 +423,21 @@ async def get_route_leaderboard( if not act.named_route_id: return None + # Rank by moving time (pauses excluded), falling back to elapsed for + # activities without it — consistent with avg pace being moving-time based. + effort_s = func.coalesce(Activity.moving_time_s, Activity.duration_s) rows = (await db.execute( select( Activity.id, Activity.name, Activity.start_time, - Activity.duration_s, Activity.distance_m, Activity.avg_heart_rate, + effort_s.label("duration_s"), + Activity.distance_m, Activity.avg_heart_rate, ) .where( Activity.named_route_id == act.named_route_id, Activity.user_id == current_user.id, Activity.duration_s.isnot(None), ) - .order_by(Activity.duration_s) + .order_by(effort_s) )).all() if not rows: return None diff --git a/backend/app/api/routes.py b/backend/app/api/routes.py index aeed46a..121c883 100644 --- a/backend/app/api/routes.py +++ b/backend/app/api/routes.py @@ -195,11 +195,13 @@ async def route_activities( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): + # Rank by moving time (pauses excluded), falling back to elapsed for + # activities without it — consistent with avg pace being moving-time based. result = await db.execute( select(Activity).where( Activity.named_route_id == route_id, Activity.user_id == current_user.id, - ).order_by(Activity.duration_s) + ).order_by(func.coalesce(Activity.moving_time_s, Activity.duration_s)) ) activities = result.scalars().all() return [ @@ -207,7 +209,7 @@ async def route_activities( "id": a.id, "name": a.name, "start_time": a.start_time, - "duration_s": a.duration_s, + "duration_s": a.moving_time_s or a.duration_s, "distance_m": a.distance_m, "avg_heart_rate": a.avg_heart_rate, "avg_speed_ms": a.avg_speed_ms,