fix: trip builder handles NULL-GPS speed-only rows (BUG-16 follow-up)
Trips starting/ending on speed-only rows violated the trips.*GPSX NOT NULL constraint. Use the first/last GPS-bearing row for start/end coordinates, and skip a trip that never got any GPS fix (no location to record).
This commit is contained in:
+16
-4
@@ -198,16 +198,28 @@ def build_trip_summaries(raw_rows):
|
|||||||
if not any(event["obd"] for event in events) and duration_seconds < 10:
|
if not any(event["obd"] for event in events) and duration_seconds < 10:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Speed-only rows (logged before the first GPS fix) have NULL coords.
|
||||||
|
# Use the first/last row that actually has a position for start/end GPS,
|
||||||
|
# and skip a trip that never got any fix at all (no location to record).
|
||||||
|
gps_rows = [
|
||||||
|
r for r in trip_rows
|
||||||
|
if r["latitude"] is not None and r["longitude"] is not None
|
||||||
|
]
|
||||||
|
if not gps_rows:
|
||||||
|
continue
|
||||||
|
start_gps = gps_rows[0]
|
||||||
|
end_gps = gps_rows[-1]
|
||||||
|
|
||||||
trips.append({
|
trips.append({
|
||||||
"id": start["id"],
|
"id": start["id"],
|
||||||
"start_tracking_id": start["id"],
|
"start_tracking_id": start["id"],
|
||||||
"end_tracking_id": end["id"],
|
"end_tracking_id": end["id"],
|
||||||
"start_datetime": start["datetime"].isoformat(sep=" "),
|
"start_datetime": start["datetime"].isoformat(sep=" "),
|
||||||
"end_datetime": end["datetime"].isoformat(sep=" "),
|
"end_datetime": end["datetime"].isoformat(sep=" "),
|
||||||
"start_gpsx": start["latitude"],
|
"start_gpsx": start_gps["latitude"],
|
||||||
"start_gpsy": start["longitude"],
|
"start_gpsy": start_gps["longitude"],
|
||||||
"end_gpsx": end["latitude"],
|
"end_gpsx": end_gps["latitude"],
|
||||||
"end_gpsy": end["longitude"],
|
"end_gpsy": end_gps["longitude"],
|
||||||
"distance_km": round(distance_km, 3),
|
"distance_km": round(distance_km, 3),
|
||||||
"duration_seconds": duration_seconds,
|
"duration_seconds": duration_seconds,
|
||||||
"point_count": len(trip_rows),
|
"point_count": len(trip_rows),
|
||||||
|
|||||||
+20
-5
@@ -115,7 +115,7 @@ class TripBuilderTest(unittest.TestCase):
|
|||||||
self.assertEqual(trips[0]["start_tracking_id"], 1)
|
self.assertEqual(trips[0]["start_tracking_id"], 1)
|
||||||
self.assertEqual(trips[0]["end_tracking_id"], 5)
|
self.assertEqual(trips[0]["end_tracking_id"], 5)
|
||||||
|
|
||||||
def test_speed_only_rows_without_gps_build_trip(self):
|
def test_speed_only_rows_without_any_gps_are_skipped(self):
|
||||||
started = datetime(2026, 1, 1, 8)
|
started = datetime(2026, 1, 1, 8)
|
||||||
rows = [
|
rows = [
|
||||||
(index + 1, started + timedelta(seconds=index * 5), None, None, 30)
|
(index + 1, started + timedelta(seconds=index * 5), None, None, 30)
|
||||||
@@ -124,12 +124,27 @@ class TripBuilderTest(unittest.TestCase):
|
|||||||
|
|
||||||
trips = app_trip.build_trip_summaries(rows)
|
trips = app_trip.build_trip_summaries(rows)
|
||||||
|
|
||||||
|
# No GPS fix at all -> no location to record -> no trip (the speed-only
|
||||||
|
# rows remain in the DB but can't form a geolocated trip).
|
||||||
|
self.assertEqual(len(trips), 0)
|
||||||
|
|
||||||
|
def test_speed_only_start_uses_first_gps_fix_for_start_coords(self):
|
||||||
|
started = datetime(2026, 1, 1, 8)
|
||||||
|
rows = [
|
||||||
|
(1, started, None, None, 30),
|
||||||
|
(2, started + timedelta(seconds=5), None, None, 30),
|
||||||
|
(3, started + timedelta(seconds=10), 45.5000, -73.5000, 30),
|
||||||
|
(4, started + timedelta(seconds=15), 45.5001, -73.5000, 30),
|
||||||
|
(5, started + timedelta(seconds=20), 45.5002, -73.5000, 30),
|
||||||
|
]
|
||||||
|
|
||||||
|
trips = app_trip.build_trip_summaries(rows)
|
||||||
|
|
||||||
self.assertEqual(len(trips), 1)
|
self.assertEqual(len(trips), 1)
|
||||||
self.assertEqual(trips[0]["start_tracking_id"], 1)
|
self.assertEqual(trips[0]["start_tracking_id"], 1)
|
||||||
self.assertEqual(trips[0]["end_tracking_id"], 4)
|
self.assertEqual(trips[0]["end_tracking_id"], 5)
|
||||||
self.assertEqual(trips[0]["distance_km"], 0)
|
self.assertEqual(trips[0]["start_gpsx"], 45.5)
|
||||||
self.assertEqual(trips[0]["max_speed_kmh"], 30)
|
self.assertEqual(trips[0]["start_gpsy"], -73.5)
|
||||||
self.assertEqual(trips[0]["point_count"], 4)
|
|
||||||
|
|
||||||
def test_clock_rollback_does_not_merge_motion_episodes(self):
|
def test_clock_rollback_does_not_merge_motion_episodes(self):
|
||||||
first = self.rows_for_route(
|
first = self.rows_for_route(
|
||||||
|
|||||||
Reference in New Issue
Block a user