""" Delt in-memory WebSocket-kringkasting for sanntidsoppdateringer på den offentlige "Følg live"-siden (ADR-027, oppfølging av ADR-025/026). Egen, RUTEFRI modul -- importerer INGEN router. Grunnen: både `scoring.py`/`tournaments.py` (som endrer matchstatus) og `messaging.py` (som eier selve WS-ruten, `/ws/public/tournaments/{id}/live`) trenger denne, og `registration.py` importerer allerede fra scoring.py/tournaments.py/ matches.py mens `messaging.py` importerer fra `registration.py` -- en kringkastingsfunksjon plassert i en av routerne ville skapt en sirkulær import. Denne modulen ligger BAK alle routere i importgrafen i stedet. In-memory PER PROSESS -- samme kjente begrensning som meldinger sin egen kringkasting (`app/routers/messaging.py`): trygt med dagens ene `teecup_api`-container, deles IKKE på tvers av flere prosesser/containere (se ARCHITECTURE_DECISIONS.md sine "Åpne spørsmål"). Bevisst "noe endret seg, hent på nytt"-signal, IKKE selve dataene -- i motsetning til meldinger (der selve meldingen kringkastes direkte), ville det å bygge/sende hele leaderboard+matchliste+scorekort-formen her duplisert betydelig beregningslogikk. Klienten reagerer ved å kalle de samme REST-endepunktene på nytt, akkurat som ved førstegangslasting. """ from collections import defaultdict from fastapi import WebSocket _tournament_live_sockets: dict[str, set[WebSocket]] = defaultdict(set) def live_sockets_for(tournament_id: str) -> set[WebSocket]: return _tournament_live_sockets[tournament_id] async def broadcast_live_update(tournament_id: str) -> None: sockets = _tournament_live_sockets.get(tournament_id) if not sockets: return dead = [] for ws in list(sockets): try: await ws.send_json({"type": "update"}) except Exception: dead.append(ws) for ws in dead: sockets.discard(ws)