from __future__ import annotations import hashlib from datetime import datetime from typing import Any from sqlmodel import Session, col, select from app.models import ( TerminalDeviceState, TerminalMessageRaw, get_datetime_utc, ) def insert_raw_message( *, session: Session, device_id: str, topic: str, payload: bytes, qos: int, retain: bool, received_at: datetime | None = None, ) -> TerminalMessageRaw: ts = received_at or get_datetime_utc() row = TerminalMessageRaw( device_id=device_id, topic=topic, received_at=ts, payload_bytes=payload, payload_size=len(payload), payload_sha256=hashlib.sha256(payload).hexdigest(), qos=qos, retain=retain, created_at=ts, ) session.add(row) session.commit() session.refresh(row) return row def fetch_pending_rows_for_update( *, session: Session, batch_size: int ) -> list[TerminalMessageRaw]: statement = ( select(TerminalMessageRaw) .where(col(TerminalMessageRaw.parsed_at).is_(None)) .where(col(TerminalMessageRaw.parse_error).is_(None)) .order_by(col(TerminalMessageRaw.received_at)) .limit(batch_size) ) bind = session.get_bind() if bind is not None and bind.dialect.name != "sqlite": statement = statement.with_for_update(skip_locked=True) rows = session.exec(statement).all() return list(rows) def mark_succeeded(*, session: Session, row: TerminalMessageRaw) -> None: row.parsed_at = get_datetime_utc() row.parse_error = None session.add(row) def mark_failed( *, session: Session, row: TerminalMessageRaw, parse_error: str, ) -> None: row.parsed_at = get_datetime_utc() row.parse_error = parse_error[:1024] session.add(row) def update_device_state_from_events( *, session: Session, row: TerminalMessageRaw, events: list[dict[str, Any]], ) -> None: if not events: state = session.get(TerminalDeviceState, row.device_id) if state is None: state = TerminalDeviceState(device_id=row.device_id) state.last_event_time = row.received_at state.updated_at = get_datetime_utc() session.add(state) return state = session.get(TerminalDeviceState, row.device_id) if state is None: state = TerminalDeviceState(device_id=row.device_id) for event in events: payload = event.get("payload", {}) event_type = str(event.get("event_type", "unknown")) if event_type == "nmea_gga": state.lat = payload.get("lat") state.lon = payload.get("lon") state.alt_m = payload.get("altitude_m") state.satellites_used = payload.get("satellites_used") fix_quality = payload.get("fix_quality") if fix_quality is not None: state.fix_status = str(fix_quality) elif event_type == "nmea_rmc": if payload.get("lat") is not None: state.lat = payload.get("lat") if payload.get("lon") is not None: state.lon = payload.get("lon") status = payload.get("status") if status is not None: state.fix_status = str(status) elif event_type == "nmea_gsv": state.satellite_signal_summary = { "snr_values": payload.get("snr_values", []), "satellites_in_view": payload.get("satellites_in_view"), } elif event_type == "rtcm_summary": state.rtcm_type_summary = payload.get("rtcm_types", {}) state.last_event_time = row.received_at state.updated_at = get_datetime_utc() session.add(state)