fix lap-bests distance matching + show PRs on activity page
Build and push images / validate (push) Successful in 5s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 9s

This commit is contained in:
2026-06-16 11:38:12 +01:00
parent 16144d60b4
commit a168f594a7
2 changed files with 83 additions and 6 deletions
+62 -6
View File
@@ -216,20 +216,76 @@ async def get_lap_bests(
if not act.named_route_id:
return {}
# Best per lap number across OTHER activities on the same route, so the
# comparison is meaningful (excluding this activity from its own benchmark).
rows = (await db.execute(
select(ActivityLap.lap_number, func.min(ActivityLap.duration_s))
# This activity's laps, so we know each lap's distance.
this_laps = (await db.execute(
select(ActivityLap.lap_number, ActivityLap.distance_m)
.where(ActivityLap.activity_id == activity_id)
)).all()
this_dist = {ln: d for ln, d in this_laps if d}
# Laps from OTHER activities on the same route, so the comparison excludes
# this activity from its own benchmark.
other_laps = (await db.execute(
select(ActivityLap.lap_number, ActivityLap.distance_m, ActivityLap.duration_s)
.join(Activity, Activity.id == ActivityLap.activity_id)
.where(
Activity.named_route_id == act.named_route_id,
Activity.user_id == current_user.id,
Activity.id != activity_id,
ActivityLap.duration_s.isnot(None),
ActivityLap.distance_m.isnot(None),
)
.group_by(ActivityLap.lap_number)
)).all()
return {str(lap_number): best for lap_number, best in rows}
# Best (fastest) time per lap number, comparing only laps that cover roughly
# the same distance (±5%). Without this, a short/partial lap that happens to
# share a lap number (e.g. a 461 m "lap 5" vs this activity's 1 km lap 5)
# would win as "best" and produce a nonsensical benchmark.
bests: dict[str, float] = {}
for ln, dist, dur in other_laps:
target = this_dist.get(ln)
if target is None or abs(dist - target) > target * 0.05:
continue
key = str(ln)
if key not in bests or dur < bests[key]:
bests[key] = dur
return bests
@router.get("/{activity_id}/records")
async def get_activity_records(
activity_id: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Current personal records that were set in this activity (e.g. a 1 km PR),
so the activity page can flag which records it holds."""
act = (await db.execute(
select(Activity.id).where(
Activity.id == activity_id,
Activity.user_id == current_user.id,
)
)).scalar_one_or_none()
if not act:
raise HTTPException(status_code=404, detail="Activity not found")
rows = (await db.execute(
select(PersonalRecord)
.where(
PersonalRecord.user_id == current_user.id,
PersonalRecord.activity_id == activity_id,
PersonalRecord.is_current_record == True,
)
.order_by(PersonalRecord.distance_m)
)).scalars().all()
return [
{
"distance_label": r.distance_label,
"distance_m": r.distance_m,
"duration_s": r.duration_s,
}
for r in rows
]
@router.get("/{activity_id}/route-leaderboard")