from datetime import datetime, timezone from sqlalchemy import delete, insert, select from sqlalchemy.orm import Session from app.models.location import Location from app.schemas.location import LocationRecordRequest def _parse_optional_float_compat(value: str | None) -> float: try: return float(value) except (TypeError, ValueError): return 0.0 def _parse_required_float(value: str, field_name: str) -> float: try: return float(value) except (TypeError, ValueError) as exc: raise ValueError(f"Invalid numeric value for {field_name}") from exc def _utc_now_rfc3339() -> str: now = datetime.now(timezone.utc).replace(microsecond=0) return now.isoformat().replace("+00:00", "Z") def record_location(session: Session, payload: LocationRecordRequest) -> None: stmt = ( insert(Location) .prefix_with("OR IGNORE") .values( person=payload.person, datetime=_utc_now_rfc3339(), latitude=_parse_required_float(payload.latitude, "latitude"), longitude=_parse_required_float(payload.longitude, "longitude"), altitude=_parse_optional_float_compat(payload.altitude), ) ) session.execute(stmt) session.commit() def update_location( session: Session, person: str, datetime_pk: str, *, latitude: float | None, longitude: float | None, altitude: float | None, ) -> Location | None: """Update non-PK fields of a single location row. Returns the updated ORM object, or ``None`` if the PK does not exist. The caller must not pass PK fields — they are immutable. Only fields with a non-``None`` value are written; ``altitude`` being ``None`` in the request means "leave unchanged", not "clear to NULL". """ row = session.execute( select(Location).where( Location.person == person, Location.datetime == datetime_pk, ) ).scalar_one_or_none() if row is None: return None if latitude is not None: row.latitude = latitude if longitude is not None: row.longitude = longitude if altitude is not None: row.altitude = altitude session.commit() session.refresh(row) return row def delete_location(session: Session, person: str, datetime_pk: str) -> bool: """Delete the single location row identified by its full composite PK. Returns ``True`` if exactly one row was deleted, ``False`` if the PK did not exist (caller should raise 404). The DELETE is scoped to the exact PK — no batch/truncate path exists. """ result = session.execute( delete(Location).where( Location.person == person, Location.datetime == datetime_pk, ) ) session.commit() return result.rowcount == 1