Fix follow-ups: lap bests, segments, charts, dashboard health
- Lap bests: compare against OTHER activities on the route (exclude self), so single-activity routes no longer show every lap as "best" - Segment create: POST to trailing-slash URL (was a 307 that dropped the body); surface errors in the UI - PR splits: scale GPS distance stream to the activity's official distance so over-measured GPS no longer yields bogus split PRs - Speed route colours: red->orange->green->blue->purple (slow->fast) with smooth interpolation + a Slow/Fast gradient key under the map - Health body battery: snap activity highlight to the categorical axis; white tooltip text + % suffix - Health weight: y-min = lowest weight - 20kg; st/lb hover shows total lbs too - Health sleep: move 8h/avg reference labels into the right margin - Dashboard: Health-today pulls latest non-null values (sleep score, VO2 max); body battery tile renders a condensed colour-graded intraday graph Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -213,12 +213,15 @@ 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))
|
||||
.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),
|
||||
)
|
||||
.group_by(ActivityLap.lap_number)
|
||||
|
||||
@@ -380,6 +380,19 @@ def compute_personal_records(activity_id: int, user_id: int, parsed: dict):
|
||||
start_time_str = parsed.get("start_time")
|
||||
start_time = datetime.fromisoformat(start_time_str) if start_time_str else datetime.now(timezone.utc)
|
||||
|
||||
# GPS can over/under-measure relative to the activity's official distance
|
||||
# (e.g. a 5 km run whose GPS track sums to 5.8 km), which would otherwise
|
||||
# produce a bogus "best 5 km" split. Scale the distance stream so its max
|
||||
# matches the recorded total before computing splits.
|
||||
if total_dist > 0 and data_points:
|
||||
gps_max = max((p.get("distance_m") or 0) for p in data_points)
|
||||
if gps_max > 0 and abs(gps_max - total_dist) / total_dist > 0.02:
|
||||
factor = total_dist / gps_max
|
||||
data_points = [
|
||||
{**p, "distance_m": p["distance_m"] * factor} if p.get("distance_m") is not None else p
|
||||
for p in data_points
|
||||
]
|
||||
|
||||
best_splits = compute_best_splits(data_points, total_dist)
|
||||
|
||||
with SyncSessionLocal() as db:
|
||||
|
||||
Reference in New Issue
Block a user