Fix follow-ups: lap bests, segments, charts, dashboard health
Build and push images / validate (push) Successful in 3s
Build and push images / build-backend (push) Successful in 5s
Build and push images / build-worker (push) Successful in 5s
Build and push images / build-frontend (push) Successful in 9s

- 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:
2026-06-08 20:39:26 +01:00
parent bc437cce92
commit 0aa27713ca
6 changed files with 159 additions and 93 deletions
+13
View File
@@ -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: