fix: BUG-22 smooth GPS-derived speeds in trip max (trailing window)

This commit is contained in:
Mathieu Moran
2026-08-14 23:00:53 -04:00
parent d9314969d8
commit a0be96dfaa
2 changed files with 36 additions and 8 deletions
+26 -1
View File
@@ -7,6 +7,7 @@ GPS_MOTION_METERS = 8
MIN_GPS_MOTION_SAMPLES = 3 MIN_GPS_MOTION_SAMPLES = 3
MAX_GPS_SAMPLE_GAP_SECONDS = 30 MAX_GPS_SAMPLE_GAP_SECONDS = 30
MAX_PLAUSIBLE_SPEED_KMH = 220 MAX_PLAUSIBLE_SPEED_KMH = 220
GPS_SPEED_WINDOW = 6 # ~30 s at 5 s logging — smooths GPS position jitter
EARTH_RADIUS_KM = 6371.0088 EARTH_RADIUS_KM = 6371.0088
@@ -180,6 +181,7 @@ def build_trip_summaries(raw_rows):
distance_km = 0 distance_km = 0
segment_speeds = [] segment_speeds = []
obd_speeds = [] obd_speeds = []
gps_speeds = []
has_obd_motion = any(event["obd"] for event in events) has_obd_motion = any(event["obd"] for event in events)
for previous, row in zip(trip_rows, trip_rows[1:]): for previous, row in zip(trip_rows, trip_rows[1:]):
segment_km, segment_speed = _segment(row, previous) segment_km, segment_speed = _segment(row, previous)
@@ -189,6 +191,29 @@ def build_trip_summaries(raw_rows):
segment_speeds.append(segment_speed) segment_speeds.append(segment_speed)
if row["speed"] > 0: if row["speed"] > 0:
obd_speeds.append(row["speed"]) obd_speeds.append(row["speed"])
# Smoothed GPS-derived speeds for the max — a trailing ~30s window
# averages out GPS position jitter (raw 5s segments can spike).
for index in range(1, len(trip_rows)):
if trip_rows[index]["speed"] > 0:
continue # OBD present here — already counted in obd_speeds
window_start = max(0, index - GPS_SPEED_WINDOW)
start_row = trip_rows[window_start]
end_row = trip_rows[index]
if (
start_row["latitude"] is not None
and end_row["latitude"] is not None
):
seconds = (
end_row["datetime"] - start_row["datetime"]
).total_seconds()
window_km = haversine_km(
(start_row["latitude"], start_row["longitude"]),
(end_row["latitude"], end_row["longitude"]),
)
if seconds > 0 and window_km * 1000 >= GPS_MOTION_METERS:
window_speed = window_km / seconds * 3600
if window_speed <= MAX_PLAUSIBLE_SPEED_KMH:
gps_speeds.append(window_speed)
start = trip_rows[0] start = trip_rows[0]
end = trip_rows[-1] end = trip_rows[-1]
@@ -227,7 +252,7 @@ def build_trip_summaries(raw_rows):
distance_km / duration_seconds * 3600, 1 distance_km / duration_seconds * 3600, 1
) if duration_seconds else 0, ) if duration_seconds else 0,
"max_speed_kmh": round( "max_speed_kmh": round(
max((obd_speeds + segment_speeds) or [0]), 1 max((obd_speeds + gps_speeds) or [0]), 1
), ),
"start_odometer": next((r["odometer"] for r in trip_rows if r.get("odometer")), None), "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), "end_odometer": next((r["odometer"] for r in reversed(trip_rows) if r.get("odometer")), None),
+10 -7
View File
@@ -461,13 +461,16 @@ def compute_trips_for_device(device_id):
speeds.append(s) speeds.append(s)
max_speeds.append(s) max_speeds.append(s)
else: else:
# GPS-derived fallback for max (mirror the Pi): when OBD speed # GPS-derived fallback for max (mirror the Pi, but smoothed over
# is missing, use the segment's GPS speed (>= 8 m, <= 220 km/h). # a trailing ~30s window so GPS position jitter can't spike the max).
seconds = (seg_rows[i][0] - seg_rows[i - 1][0]).total_seconds() j = max(0, i - 6)
if seconds > 0 and d * 1000 >= 8: w_dt = (seg_rows[i][0] - seg_rows[j][0]).total_seconds()
seg_speed = d / seconds * 3600 w_d = haversine_km(seg_rows[j][1], seg_rows[j][2],
if seg_speed <= 220: seg_rows[i][1], seg_rows[i][2])
max_speeds.append(seg_speed) if w_dt > 0 and w_d * 1000 >= 8:
w_speed = w_d / w_dt * 3600
if w_speed <= 220:
max_speeds.append(w_speed)
duration_s = int((seg_rows[-1][0] - seg_rows[0][0]).total_seconds()) duration_s = int((seg_rows[-1][0] - seg_rows[0][0]).total_seconds())
if duration_s < 30 or distance < 0.05: if duration_s < 30 or distance < 0.05: