fix: deployment capture coords now reach existing locations

The /deploy classify "Assign to existing location" path dropped the captured
GPS — only "Create new location" applied it — so units assigned to pre-existing
coordless locations left those locations without a pin.

- Classify (promote) now backfills the captured GPS onto an existing location
  that has no coordinates (doesn't clobber operator-set coords).
- Add "Reforward info" button on Assigned deployment cards + endpoint
  POST /pending/{id}/resync-location that re-pushes a capture's GPS onto its
  assigned location (explicit action, overwrites). Fixes already-classified
  locations and guards against this recurring. Logged to unit history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 18:12:56 +00:00
parent ee6062f9fb
commit 93f01be471
2 changed files with 77 additions and 1 deletions
+50
View File
@@ -296,6 +296,12 @@ async def promote_pending(
if not location:
raise HTTPException(status_code=404, detail=f"Location {location_id!r} not found.")
project_id = location.project_id
# Backfill the captured GPS onto the existing location if it doesn't
# have coordinates yet. (Previously the captured coords were dropped on
# the assign-to-existing path, so only create-new locations got a pin.)
# Don't clobber coordinates an operator already set.
if pd.coordinates and not (location.coordinates or "").strip():
location.coordinates = pd.coordinates
else:
# Create-new path. Need a project (existing or new).
project_id = payload.get("project_id")
@@ -456,6 +462,50 @@ async def cancel_pending(
return {"success": True, "cancelled_at": pd.cancelled_at.isoformat()}
@router.post("/pending/{pending_id}/resync-location")
async def resync_location(pending_id: str, db: Session = Depends(get_db)):
"""Re-push a promoted capture's GPS onto its assigned location.
Use when a capture's coordinates didn't land on the location (e.g. it was
assigned to a pre-existing location that had none). Unlike the auto-backfill
on classify, this is an explicit operator action and OVERWRITES the
location's coordinates with the captured GPS.
"""
pd = db.query(PendingDeployment).filter_by(id=pending_id).first()
if not pd:
raise HTTPException(status_code=404, detail="Pending deployment not found.")
if pd.status != "assigned" or not pd.resulting_assignment_id:
raise HTTPException(status_code=400, detail="Only a promoted (assigned) capture can be re-forwarded.")
if not (pd.coordinates or "").strip():
raise HTTPException(status_code=400, detail="This capture has no GPS coordinates to forward.")
asg = db.query(UnitAssignment).filter_by(id=pd.resulting_assignment_id).first()
if not asg:
raise HTTPException(status_code=404, detail="Resulting assignment not found.")
location = db.query(MonitoringLocation).filter_by(id=asg.location_id).first()
if not location:
raise HTTPException(status_code=404, detail="Assigned location not found.")
old = location.coordinates
location.coordinates = pd.coordinates.strip()
_record_history(
db, unit_id=pd.unit_id,
change_type="deployment_coords_reforwarded",
old_value=old,
new_value=location.coordinates,
notes=f"Re-forwarded capture GPS to location '{location.name}'",
)
db.commit()
return {
"success": True,
"location_id": location.id,
"location_name": location.name,
"coordinates": location.coordinates,
}
# ── Helpers ───────────────────────────────────────────────────────────────────
def _to_dict(pd: PendingDeployment, unit: Optional[RosterUnit] = None, detail: bool = False) -> dict: