""" SLM Status Synchronization Service Syncs SLM device status from SLMM backend to Terra-View's Emitter table. This bridges SLMM's polling data with Terra-View's status snapshot system. SLMM tracks device reachability via background polling. This service fetches that data and creates/updates Emitter records so SLMs appear correctly in the dashboard status snapshot. """ import logging from datetime import datetime, timezone from typing import Dict, Any from backend.database import get_db_session from backend.models import Emitter from backend.services.slmm_client import get_slmm_client, SLMMClientError logger = logging.getLogger(__name__) async def sync_slm_status_to_emitters() -> Dict[str, Any]: """ Fetch SLM status from SLMM and sync to Terra-View's Emitter table. For each device in SLMM's polling status: - If last_success exists, create/update Emitter with that timestamp - If not reachable, update Emitter with last known timestamp (or None) Returns: Dict with synced_count, error_count, errors list """ client = get_slmm_client() synced = 0 errors = [] try: # Get polling status from SLMM status_response = await client.get_polling_status() # Handle nested response structure data = status_response.get("data", status_response) devices = data.get("devices", []) if not devices: logger.debug("No SLM devices in SLMM polling status") return {"synced_count": 0, "error_count": 0, "errors": []} db = get_db_session() try: for device in devices: unit_id = device.get("unit_id") if not unit_id: continue try: # Get or create Emitter record emitter = db.query(Emitter).filter(Emitter.id == unit_id).first() # Determine last_seen from SLMM data last_success_str = device.get("last_success") is_reachable = device.get("is_reachable", False) if last_success_str: # Parse ISO format timestamp last_seen = datetime.fromisoformat( last_success_str.replace("Z", "+00:00") ) # Convert to naive UTC for consistency with existing code if last_seen.tzinfo: last_seen = last_seen.astimezone(timezone.utc).replace(tzinfo=None) else: last_seen = None # Status will be recalculated by snapshot.py based on time thresholds # Just store a provisional status here status = "OK" if is_reachable else "Missing" # Store last error message if available last_error = device.get("last_error") or "" if emitter: # Update existing record emitter.last_seen = last_seen emitter.status = status emitter.unit_type = "slm" emitter.last_file = last_error else: # Create new record emitter = Emitter( id=unit_id, unit_type="slm", last_seen=last_seen, last_file=last_error, status=status ) db.add(emitter) synced += 1 except Exception as e: errors.append(f"{unit_id}: {str(e)}") logger.error(f"Error syncing SLM {unit_id}: {e}") db.commit() finally: db.close() if synced > 0: logger.info(f"Synced {synced} SLM device(s) to Emitter table") except SLMMClientError as e: logger.warning(f"Could not reach SLMM for status sync: {e}") errors.append(f"SLMM unreachable: {str(e)}") except Exception as e: logger.error(f"Error in SLM status sync: {e}", exc_info=True) errors.append(str(e)) return { "synced_count": synced, "error_count": len(errors), "errors": errors }