""" Every radio heard on a call, as the edge node reports it (server-26#304). The node sends `radios`: a list of {radio, start, end}, one entry per transmission, offsets in seconds into the uploaded audio file, so a talker's wall-clock time is audio_start_epoch + offset (the same mapping the node uses for the audio itself). It arrives twice — on /upload (as a JSON string form field, because the pipeline starts there) and on the MQTT call_end — and both go through `call_fields` so the call doc gets the same shape either way. `radio_ids` is the distinct IDs in first-heard order, kept alongside so a correlator or query can ask "was radio X on this call" without walking the list. """ import json from typing import Any, Dict, List, Optional from app.internal.logger import logger # A 600 s recording polled every 0.5 s cannot honestly hold more than this. MAX_TRANSMISSIONS = 200 def _num(value: Any) -> Optional[float]: try: number = float(value) except (TypeError, ValueError): return None return number if number >= 0 else None def parse(value: Any) -> List[Dict[str, Any]]: """The node's radio list, validated. Accepts a list or its JSON string; junk entries are dropped.""" if value is None or value == "": return [] if isinstance(value, str): try: value = json.loads(value) except ValueError: logger.warning(f"radios: not JSON, ignored ({value[:80]!r})") return [] if not isinstance(value, list): return [] out: List[Dict[str, Any]] = [] for item in value[:MAX_TRANSMISSIONS]: if not isinstance(item, dict): continue try: radio = int(item.get("radio")) except (TypeError, ValueError): continue start, end = _num(item.get("start")), _num(item.get("end")) if radio <= 0 or start is None or end is None: continue out.append({"radio": radio, "start": start, "end": max(start, end)}) return out def call_fields(value: Any) -> Dict[str, Any]: """Call-doc fields for a radio list; empty when the node sent none (older nodes, analog).""" radios = parse(value) if not radios: return {} return {"radios": radios, "radio_ids": list(dict.fromkeys(r["radio"] for r in radios))}