diff --git a/BUGS.md b/BUGS.md index 305c6f1..46bd273 100644 --- a/BUGS.md +++ b/BUGS.md @@ -24,6 +24,18 @@ When CLOSED, move the entire entry to Resolved — don't duplicate. fills the cards immediately on load (and every 15 s) via REST. Needs in-car verification. +### BUG-22 — Trip max speed ignores GPS-only stretch (shows OBD max only) +- **Status**: `OPEN` 2026-08-14 +- **Phase**: 29 +- **Symptom**: trip #9 shows "max 111.0 km/h" but the replay scrubber shows "GPS: 135.6 km/h" + at 6:26 p.m. — the top speed reached during the OBD dropout isn't reflected in the trip max. +- **Cause**: `max_speed_kmh` was computed from OBD speeds only (`max(obd_speeds or segment_speeds)` + on the Pi, `max(speeds)` on the server). When a trip has any OBD speed at all, the GPS-only + stretch is ignored for the max. The replay already shows GPS-derived speed for that stretch + (BUG-21 fallback), so the scrubber shows a higher speed than the stored max. +- **Fix**: union OBD + GPS-derived segment speeds when computing the max, on both the Pi + (`build_trip_summaries`) and the server (`compute_trips_for_device`). + --- ## Resolved diff --git a/PLAN.md b/PLAN.md index 32b6ff2..f090fbb 100644 --- a/PLAN.md +++ b/PLAN.md @@ -2286,6 +2286,9 @@ logged as `BUG-XX` entries in [BUGS.md](./BUGS.md) (BUG-16 onward) and fixed + v on 2026-08-13 (22:23:50→22:43:41 UTC). Fixed: server `get_trip_route()` mirrors the Pi's GPS-speed fallback (derives speed from consecutive GPS points when OBD speed is missing), so the portal replay no longer flatlines at 0 km/h. Follow-up: OBD reconnect on stalled reads. +- **BUG-22 (trip max ignores GPS-only stretch) — 🚧 IN PROGRESS**: `max_speed_kmh` used OBD speeds + only, so the top speed during the OBD dropout (e.g. 135 GPS) wasn't reflected. Fix: union OBD + + GPS-derived segment speeds in the max on both Pi + server. Open items from the drive — fill in as reported: - (none clock-related — BUG-17/BUG-18 closed 2026-08-14). diff --git a/modules/app_trip.py b/modules/app_trip.py index f50cd46..c295712 100644 --- a/modules/app_trip.py +++ b/modules/app_trip.py @@ -227,7 +227,7 @@ def build_trip_summaries(raw_rows): distance_km / duration_seconds * 3600, 1 ) if duration_seconds else 0, "max_speed_kmh": round( - max(obd_speeds or segment_speeds or [0]), 1 + max((obd_speeds + segment_speeds) or [0]), 1 ), "start_odometer": next((r["odometer"] for r in trip_rows if r.get("odometer")), None), "end_odometer": next((r["odometer"] for r in reversed(trip_rows) if r.get("odometer")), None), diff --git a/server/modules/db.py b/server/modules/db.py index 9a7c3b8..c2e4ea8 100644 --- a/server/modules/db.py +++ b/server/modules/db.py @@ -450,7 +450,8 @@ def compute_trips_for_device(device_id): seg_rows = rows[start_idx:end_idx + 1] distance = 0 - speeds = [] + speeds = [] # OBD speeds — used for avg + max_speeds = [] # OBD + GPS-derived — used for max for i in range(1, len(seg_rows)): d = haversine_km(seg_rows[i - 1][1], seg_rows[i - 1][2], seg_rows[i][1], seg_rows[i][2]) @@ -458,6 +459,15 @@ def compute_trips_for_device(device_id): s = seg_rows[i][3] if s is not None and s > 0: speeds.append(s) + max_speeds.append(s) + else: + # GPS-derived fallback for max (mirror the Pi): when OBD speed + # is missing, use the segment's GPS speed (>= 8 m, <= 220 km/h). + seconds = (seg_rows[i][0] - seg_rows[i - 1][0]).total_seconds() + if seconds > 0 and d * 1000 >= 8: + seg_speed = d / seconds * 3600 + if seg_speed <= 220: + max_speeds.append(seg_speed) duration_s = int((seg_rows[-1][0] - seg_rows[0][0]).total_seconds()) if duration_s < 30 or distance < 0.05: @@ -469,7 +479,7 @@ def compute_trips_for_device(device_id): "distance_km": round(distance, 3), "duration_s": duration_s, "avg_speed_kmh": round(sum(speeds) / len(speeds), 1) if speeds else None, - "max_speed_kmh": round(max(speeds), 1) if speeds else None, + "max_speed_kmh": round(max(max_speeds), 1) if max_speeds else None, "start_lat": seg_rows[0][1], "start_lon": seg_rows[0][2], "end_lat": seg_rows[-1][1],