fix(phase26): server trip delete persists across recompute + batch delete with checkboxes

This commit is contained in:
Mathieu Moran
2026-08-11 04:18:58 -04:00
parent e6bc488e84
commit bdc7a9afb7
6 changed files with 133 additions and 5 deletions
+16 -1
View File
@@ -571,10 +571,11 @@ def geocode():
@app.post("/api/v1/portal/recompute-trips")
@require_login
def recompute_trips():
"""Manually trigger trip recomputation. Called after upload batch for freshness."""
"""Recompute trips from scratch, clearing all deletion markers."""
device_id = _portal_device_id()
if device_id is None:
return jsonify(error="device not configured"), 404
db.clear_deleted_trip_markers(device_id)
n = db.compute_trips_for_device(device_id)
return jsonify(status="ok", trips_computed=n)
@@ -591,6 +592,20 @@ def portal_delete_trip(trip_id):
return jsonify(status="ok")
@app.post("/api/v1/portal/trips/delete")
@require_login
def portal_delete_trips_batch():
device_id = _portal_device_id()
if device_id is None:
return jsonify(error="device not configured"), 404
body = request.get_json(silent=True) or {}
ids = body.get("ids")
if not isinstance(ids, list) or not ids:
return jsonify(error="expected {ids: [...]}"), 400
deleted = db.delete_trips_batch(device_id, ids)
return jsonify(status="ok", deleted=deleted)
@app.delete("/api/v1/portal/dashcam/<int:file_id>")
@require_login
def portal_delete_dashcam(file_id):
+1
View File
@@ -13,6 +13,7 @@ services:
- ./sql/migrate_phase24.sql:/docker-entrypoint-initdb.d/migrate_phase24.sql:ro
- ./sql/migrate_phase25_5a.sql:/docker-entrypoint-initdb.d/migrate_phase25_5a.sql:ro
- ./sql/migrate_phase25_5c.sql:/docker-entrypoint-initdb.d/migrate_phase25_5c.sql:ro
- ./sql/migrate_phase26_deleted_trips.sql:/docker-entrypoint-initdb.d/migrate_phase26_deleted_trips.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-yarisobd}"]
interval: 10s
+44 -3
View File
@@ -398,6 +398,15 @@ def compute_trips_for_device(device_id):
"point_count": len(seg_rows),
})
# Skip trips that were manually deleted (recompute won't re-insert them).
with get_cursor() as cur:
cur.execute(
"SELECT start_time FROM deleted_trip_markers WHERE device_id = %s",
(device_id,),
)
deleted_starts = {row[0] for row in cur.fetchall()}
trips = [t for t in trips if t["start_time"] not in deleted_starts]
with get_cursor(commit=True) as cur:
for t in trips:
cur.execute(
@@ -540,13 +549,45 @@ def get_dashcam_file(device_id, file_id):
def delete_trip(device_id, trip_id):
"""Delete a computed trip record. Raw tracking_points are NOT removed."""
"""Delete a computed trip record and mark its time range as deleted
so auto-recompute doesn't re-insert it. Raw tracking_points are kept."""
with get_cursor(commit=True) as cur:
# Record the start_time before deleting so recompute skips this trip.
cur.execute(
"DELETE FROM trips WHERE id = %s AND device_id = %s RETURNING id",
"SELECT start_time FROM trips WHERE id = %s AND device_id = %s",
(trip_id, device_id),
)
return cur.fetchone() is not None
row = cur.fetchone()
if row is None:
return False
cur.execute(
"""INSERT INTO deleted_trip_markers (device_id, start_time)
VALUES (%s, %s) ON CONFLICT DO NOTHING""",
(device_id, row[0]),
)
cur.execute(
"DELETE FROM trips WHERE id = %s AND device_id = %s",
(trip_id, device_id),
)
return True
def delete_trips_batch(device_id, trip_ids):
"""Delete multiple trips in one transaction."""
deleted = 0
for tid in trip_ids:
if delete_trip(device_id, tid):
deleted += 1
return deleted
def clear_deleted_trip_markers(device_id):
"""Remove all deleted trip markers so recompute rebuilds everything."""
with get_cursor(commit=True) as cur:
cur.execute(
"DELETE FROM deleted_trip_markers WHERE device_id = %s",
(device_id,),
)
def delete_dashcam_file(device_id, file_id):
@@ -0,0 +1,10 @@
-- Migration: deleted trip markers to prevent recompute from re-inserting
-- manually deleted trips. Each row records a trip the user explicitly
-- removed. compute_trips_for_device() skips time ranges matching any marker.
-- The "Recompute trips" button clears all markers before rebuilding.
CREATE TABLE IF NOT EXISTS deleted_trip_markers (
device_id INTEGER NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
start_time TIMESTAMPTZ NOT NULL,
deleted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (device_id, start_time)
);
+5 -1
View File
@@ -14,8 +14,11 @@
{# ── Trip list ──────────────────────────────────────────────────── #}
<div class="col-12 col-xl-5">
<div class="card shadow-sm">
<div class="card-header py-2">
<div class="card-header py-2 d-flex justify-content-between align-items-center">
<h6 class="text-primary fw-bold m-0">Recorded trips</h6>
<button id="delete-selected-btn" class="btn btn-sm btn-outline-danger" type="button" hidden title="Delete selected trips">
<i class="fas fa-trash fa-fw"></i> Delete selected
</button>
</div>
<div class="card-body p-0">
<div id="trip-loading" class="text-center text-muted py-4">
@@ -38,6 +41,7 @@
true
) }}
<script>document.getElementById('trip-list').dataset.deleteUrl = '/api/v1/portal/trip/';</script>
<script>document.getElementById('trip-list').dataset.batchDeleteUrl = '/api/v1/portal/trips/delete';</script>
</div>
</div>
</div>