Files
terra-view/backend/services/snapshot.py
2025-12-03 00:51:18 +00:00

131 lines
4.1 KiB
Python

from datetime import datetime, timezone
from sqlalchemy.orm import Session
from backend.database import get_db_session
from backend.models import Emitter, RosterUnit, IgnoredUnit
def ensure_utc(dt):
if dt is None:
return None
if dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc)
return dt.astimezone(timezone.utc)
def format_age(last_seen):
if not last_seen:
return "N/A"
last_seen = ensure_utc(last_seen)
now = datetime.now(timezone.utc)
diff = now - last_seen
hours = diff.total_seconds() // 3600
mins = (diff.total_seconds() % 3600) // 60
return f"{int(hours)}h {int(mins)}m"
def emit_status_snapshot():
"""
Merge roster (what we *intend*) with emitter data (what is *actually happening*).
"""
db = get_db_session()
try:
roster = {r.id: r for r in db.query(RosterUnit).all()}
emitters = {e.id: e for e in db.query(Emitter).all()}
ignored = {i.id for i in db.query(IgnoredUnit).all()}
units = {}
# --- Merge roster entries first ---
for unit_id, r in roster.items():
e = emitters.get(unit_id)
if r.retired:
# Retired units get separated later
status = "Retired"
age = "N/A"
last_seen = None
fname = ""
else:
if e:
status = e.status
last_seen = ensure_utc(e.last_seen)
age = format_age(last_seen)
fname = e.last_file
else:
# Rostered but no emitter data
status = "Missing"
last_seen = None
age = "N/A"
fname = ""
units[unit_id] = {
"id": unit_id,
"status": status,
"age": age,
"last": last_seen.isoformat() if last_seen else None,
"fname": fname,
"deployed": r.deployed,
"note": r.note or "",
"retired": r.retired,
}
# --- Add unexpected emitter-only units ---
for unit_id, e in emitters.items():
if unit_id not in roster:
last_seen = ensure_utc(e.last_seen)
units[unit_id] = {
"id": unit_id,
"status": e.status,
"age": format_age(last_seen),
"last": last_seen.isoformat(),
"fname": e.last_file,
"deployed": False, # default
"note": "",
"retired": False,
}
# Separate buckets for UI
active_units = {
uid: u for uid, u in units.items()
if not u["retired"] and u["deployed"]
}
benched_units = {
uid: u for uid, u in units.items()
if not u["retired"] and not u["deployed"]
}
retired_units = {
uid: u for uid, u in units.items()
if u["retired"]
}
# Unknown units - emitters that aren't in the roster and aren't ignored
unknown_units = {
uid: u for uid, u in units.items()
if uid not in roster and uid not in ignored
}
return {
"timestamp": datetime.utcnow().isoformat(),
"units": units,
"active": active_units,
"benched": benched_units,
"retired": retired_units,
"unknown": unknown_units,
"summary": {
"total": len(units),
"active": len(active_units),
"benched": len(benched_units),
"retired": len(retired_units),
"unknown": len(unknown_units),
"ok": sum(1 for u in units.values() if u["status"] == "OK"),
"pending": sum(1 for u in units.values() if u["status"] == "Pending"),
"missing": sum(1 for u in units.values() if u["status"] == "Missing"),
}
}
finally:
db.close()