122 lines
3.7 KiB
Python
122 lines
3.7 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
|
|
|
|
|
|
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()}
|
|
|
|
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"]
|
|
}
|
|
|
|
return {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"units": units,
|
|
"active": active_units,
|
|
"benched": benched_units,
|
|
"retired": retired_units,
|
|
"summary": {
|
|
"total": len(units),
|
|
"active": len(active_units),
|
|
"benched": len(benched_units),
|
|
"retired": len(retired_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()
|