From 3066e1e46075ad2011c8b58b661a72c9ab4dbe2f Mon Sep 17 00:00:00 2001 From: Carlos Lugo Date: Thu, 4 Jun 2026 02:11:59 +0000 Subject: [PATCH] fix: handle clear_lobby event to kick players and reset lobby Co-Authored-By: Claude Sonnet 4.6 --- main.py | 169 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 109 insertions(+), 60 deletions(-) diff --git a/main.py b/main.py index ff22f94..98e2c9d 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse, JSONResponse -import json, time, random, sqlite3, os, asyncio +import json, time, random, sqlite3, os from datetime import datetime DB_PATH = os.environ.get("DB_PATH", "/data/quiz.db") @@ -65,59 +65,114 @@ templates = Jinja2Templates(directory="templates") # ── QUESTIONS ───────────────────────────────────────────────────────────────── QUESTIONS = [ { - "q": "¿Cuál es el enfoque principal del equipo de Network Intelligence?", + "q": "NI construye herramientas propias en vez de depender de licencias comerciales. ¿Por qué?\n\n[EN] NI builds its own tools instead of relying on commercial licenses. Why?", "options": [ - "Vender licencias de software a otras empresas", - "Construir herramientas propias que empoderan a los equipos operacionales", - "Administrar el presupuesto de TI de LCPR", - "Instalar equipos físicos en planta externa" + "Para tener control total, escalar sin costo por usuario y adaptarse a nuestros flujos reales / Full control, scale without per-user cost, adapt to real workflows", + "Para ahorrar en hardware de servidores / To save on server hardware", + "Porque las herramientas comerciales no existen para redes / Commercial tools don't exist for networks", + "Es un requisito del contrato de Liberty / It's a Liberty contract requirement" ], - "correct": 1, - "fun_fact": "¡Exacto! Construimos para quienes operan la red día a día — no solo dashboards bonitos." + "correct": 0, + "fun_fact": "Código propio = sin techo de plataforma. Podemos extender, integrar y mejorar sin pedir permiso ni pagar más. / Our code = no platform ceiling. We extend, integrate, and improve without asking permission or paying more." }, { - "q": "¿Qué problema resolvió el equipo al reemplazar Power BI con frontend propio?", + "q": "¿Para quién construye herramientas el equipo de Network Intelligence?\n\n[EN] Who does the Network Intelligence team build tools for?", "options": [ - "La pantalla se veía más bonita", - "Eliminamos la dependencia de licencias costosas y ganamos control total", - "Fue más fácil de instalar en los servidores", - "Los colores del dashboard coincidían con los de Liberty" - ], - "correct": 1, - "fun_fact": "$0 en licencias de visualización. El código es nuestro y lo extendemos como queremos." - }, - { - "q": "¿Qué es Pathfinder?", - "options": [ - "Una aplicación para rastrear camiones de campo", - "Un sistema de GPS para la red de fibra", - "Una plataforma web que centraliza herramientas operacionales de red en un solo lugar", - "Un reporte mensual de disponibilidad de red" + "Solo para gerencia ejecutiva / Only for executive management", + "Para clientes residenciales de Liberty / For Liberty residential customers", + "Para los equipos de NOC, ingeniería, operaciones y técnicos de campo / For NOC, engineering, operations, and field technician teams", + "Para el equipo de ventas B2B / For the B2B sales team" ], "correct": 2, - "fun_fact": "Pathfinder es el 'panel de control' propio de LCPR — construido exactamente para nuestros flujos de trabajo." + "fun_fact": "El objetivo no es reportar — es que quien opera la red pueda decidir y actuar más rápido. / The goal isn't reporting — it's enabling the people running the network to decide and act faster." }, { - "q": "¿Qué logró el proyecto de Firmware Upgrade Automation?", + "q": "Pathfinder junta monitoreo, rutas, topología y análisis operacional en un solo lugar. ¿Cuál es su propósito principal?\n\n[EN] Pathfinder brings together monitoring, routes, topology, and operational analysis in one place. What is its main purpose?", "options": [ - "Actualizar manualmente 10 modems por semana", - "Contratar más técnicos para visitas a domicilio", - "Automatizar la actualización masiva de modems sin intervención humana", - "Comprar equipos nuevos para reemplazar los modems viejos" + "Gestionar el presupuesto de proyectos de red / Manage the network project budget", + "Reemplazar el sistema de ticketing de la empresa / Replace the company ticketing system", + "Automatizar facturación a clientes B2B / Automate billing for B2B customers", + "Ayudar a ingeniería y NOC a ver el mismo contexto de red para decidir y actuar más rápido / Help engineering and NOC see the same network context to decide and act faster" ], - "correct": 2, - "fun_fact": "400+ modems actualizados en batches automáticos. Antes era trabajo manual uno por uno." + "correct": 3, + "fun_fact": "Pathfinder no es solo un dashboard — es un centro operativo. Monitoreo, rutas, topología, backbone e interfaces en una sola experiencia. / Pathfinder isn't just a dashboard — it's an operations center. Monitoring, routes, topology, backbone, and interfaces in one experience." }, { - "q": "¿Hacia dónde se dirige el equipo en su visión AIOps?", + "q": "MetricFlow es la plataforma KPI del equipo de NI. ¿Qué problema resuelve?\n\n[EN] MetricFlow is NI's KPI platform. What problem does it solve?", "options": [ - "Depender más de herramientas comerciales como ServiceNow", - "Detectar y predecir fallas antes de que el cliente las reporte", - "Reducir el equipo de ingeniería a la mitad", - "Migrar toda la red a la nube pública" + "Reemplaza los emails internos del equipo / Replaces internal team emails", + "Centraliza métricas de desempeño, capacidad y tendencias para que los dominios puedan medir cumplimiento / Centralizes performance, capacity, and trend metrics so domains can measure compliance", + "Administra las cuentas de acceso de los empleados / Manages employee access accounts", + "Genera facturas automáticas para clientes / Generates automatic invoices for customers" ], "correct": 1, - "fun_fact": "La meta: que el técnico salga a campo con el diagnóstico ya hecho — no a diagnosticar." + "fun_fact": "MetricFlow convierte datos dispersos en KPIs por dominio — desempeño, capacidad, tendencia y cumplimiento en un solo lugar. / MetricFlow turns scattered data into domain KPIs — performance, capacity, trend, and compliance in one place." + }, + { + "q": "Fiber Admin organiza el inventario físico de fibra de LCPR. ¿Qué información centraliza?\n\n[EN] Fiber Admin organizes LCPR's physical fiber inventory. What information does it centralize?", + "options": [ + "Contratos de proveedores y facturas de mantenimiento / Vendor contracts and maintenance invoices", + "Horarios del personal de planta externa / External plant staff schedules", + "Hubs, puertos, conexiones y empalmes de fibra para coordinar trabajo entre dominios / Hubs, ports, connections, and fiber splices to coordinate work across domains", + "Configuraciones de routers y switches de core / Core router and switch configurations" + ], + "correct": 2, + "fun_fact": "Fiber Admin convierte inventario físico en información confiable. Sin eso, coordinar cambios entre dominios depende de quién recuerda qué. / Fiber Admin turns physical inventory into reliable information. Without it, coordinating changes across domains depends on who remembers what." + }, + { + "q": "Los Reportes Automatizados con AI transforman datos crudos en algo más útil. ¿Qué produce exactamente?\n\n[EN] Automated AI Reports transform raw data into something more useful. What exactly does it produce?", + "options": [ + "Alertas de red en tiempo real para el NOC / Real-time network alerts for NOC", + "Backups automáticos de bases de datos / Automatic database backups", + "Resúmenes, narrativas y reportes ejecutivos listos para compartir — no solo tablas de números / Summaries, narratives, and executive reports ready to share — not just number tables", + "Configuraciones automáticas de equipos de red / Automatic network equipment configurations" + ], + "correct": 2, + "fun_fact": "Un reporte que solo muestra números no explica qué cambió ni qué hacer. AI convierte métricas en comunicación accionable para cualquier audiencia. / A report that only shows numbers doesn't explain what changed or what to do. AI turns metrics into actionable communication for any audience." + }, + { + "q": "Znuny es la pieza que cierra el ciclo operacional del pipeline de NI. ¿Cuál es su rol clave?\n\n[EN] Znuny is the piece that closes the operational cycle in NI's pipeline. What is its key role?", + "options": [ + "Conectar detección, notificación, tarea, seguimiento y resolución — que cada señal tenga dueño y cierre / Connect detection, notification, task, follow-up, and resolution — every signal has an owner and a close", + "Almacenar backups de configuración de red / Store network configuration backups", + "Procesar pagos de nómina del equipo / Process team payroll payments", + "Monitorear el consumo eléctrico del datacenter / Monitor datacenter power consumption" + ], + "correct": 0, + "fun_fact": "Znuny no es solo un sistema de tickets. Es la pieza que ata el pipeline completo: lo que se detecta termina resuelto, con historial y evidencia. / Znuny isn't just a ticketing system. It's the piece that ties the whole pipeline together: what gets detected ends up resolved, with history and evidence." + }, + { + "q": "El equipo de NI también apoya dominios fuera de la red pura. ¿Cuál de estas áreas es un ejemplo de eso?\n\n[EN] NI also supports domains outside pure network work. Which of these is an example?", + "options": [ + "Administración de torres celulares / Cell tower administration", + "Gestión de inventario de almacén / Warehouse inventory management", + "Soporte técnico a clientes de cable / Technical support for cable customers", + "Dashboards de BI y automatización de flujos para Construcción y PNM en Power BI y Excel / BI dashboards and workflow automation for Construction and PNM in Power BI and Excel" + ], + "correct": 3, + "fun_fact": "NI no es solo red — construimos soluciones analíticas para otros dominios VPTO usando las herramientas correctas para cada caso. / NI isn't just network — we build analytical solutions for other VPTO domains using the right tools for each case." + }, + { + "q": "¿Cuál es la visión del equipo para el próximo capítulo — AIOps?\n\n[EN] What is the team's vision for the next chapter — AIOps?", + "options": [ + "Reemplazar a todos los ingenieros con inteligencia artificial / Replace all engineers with artificial intelligence", + "Comprar una plataforma comercial de AIOps / Purchase a commercial AIOps platform", + "Detectar y predecir fallas de red antes de que el cliente las reporte, con recomendaciones automatizadas / Detect and predict network failures before the customer reports them, with automated recommendations", + "Migrar toda la infraestructura a la nube pública / Migrate all infrastructure to public cloud" + ], + "correct": 2, + "fun_fact": "La meta: el técnico sale a campo con el diagnóstico ya hecho — no a diagnosticar. La red se auto-diagnostica. / The goal: the technician goes to the field with the diagnosis already done — not to diagnose. The network self-diagnoses." + }, + { + "q": "¿Qué diferencia al equipo de NI de un equipo de soporte TI tradicional?\n\n[EN] What sets the NI team apart from a traditional IT support team?", + "options": [ + "NI solo atiende tickets de soporte / NI only handles support tickets", + "NI construye producto interno — herramientas, plataformas y automatización que transforman cómo opera la red / NI builds internal product — tools, platforms, and automation that transform how the network operates", + "NI administra el presupuesto de licencias de software / NI manages the software license budget", + "NI hace instalaciones físicas de equipos en campo / NI does physical equipment installations in the field" + ], + "correct": 1, + "fun_fact": "El backlog de NI no es soporte — es producto interno. Cada tarea construye capacidad operacional real. / The NI backlog isn't support — it's internal product. Every task builds real operational capability." } ] @@ -132,8 +187,12 @@ class GameState: self.current_q = -1 self.q_start_time = 0 self.answers_this_round = [] - self.question_order = list(range(len(QUESTIONS))) - random.shuffle(self.question_order) + # 4 generals (indices 0-4) + 1 tool (indices 5-9), shuffled together + generals = random.sample(range(5), 4) + tool = random.sample(range(5, 10), 1) + combined = generals + tool + random.shuffle(combined) + self.question_order = combined game = GameState() @@ -142,7 +201,6 @@ class ConnectionManager: def __init__(self): self.players: dict = {} self.hosts: list = [] - self._reset_task: asyncio.Task = None async def connect_player(self, ws_id: str, ws: WebSocket): await ws.accept() @@ -151,10 +209,6 @@ class ConnectionManager: async def connect_host(self, ws: WebSocket): await ws.accept() self.hosts.append(ws) - # Cancel pending auto-reset if host reconnected in time - if self._reset_task and not self._reset_task.done(): - self._reset_task.cancel() - self._reset_task = None def disconnect(self, ws_id: str): self.players.pop(ws_id, None) @@ -163,16 +217,6 @@ class ConnectionManager: def disconnect_host(self, ws: WebSocket): if ws in self.hosts: self.hosts.remove(ws) - # If no hosts remain and game is not in lobby, schedule auto-reset - if not self.hosts and game.phase != "lobby": - self._reset_task = asyncio.create_task(self._auto_reset(delay=300)) - - async def _auto_reset(self, delay: int): - """Wait `delay` seconds, then reset the game if no host has reconnected.""" - await asyncio.sleep(delay) - if not self.hosts: - game.reset() - await self.broadcast_players({"type": "phase", "phase": "lobby"}) async def send_to(self, ws_id: str, data: dict): ws = self.players.get(ws_id) @@ -283,7 +327,7 @@ async def player_ws(ws: WebSocket, ws_id: str): if not player or player["answered"]: continue elapsed = time.time() - game.q_start_time - TIME_LIMIT = 20 + TIME_LIMIT = 30 if elapsed > TIME_LIMIT: continue @@ -353,7 +397,7 @@ async def host_ws(ws: WebSocket): elif msg["type"] == "next_question": game.current_q += 1 - if game.current_q >= len(QUESTIONS): + if game.current_q >= len(game.question_order): lb = get_leaderboard() save_session(list(game.players.values())) # ← persist to SQLite game.phase = "podium" @@ -371,11 +415,11 @@ async def host_ws(ws: WebSocket): await mgr.broadcast_all({ "type": "question", "number": game.current_q + 1, - "total": len(QUESTIONS), + "total": len(game.question_order), "q": q["q"], "options": q["options"], - "time_limit": 20, - "correct_idx_hint": -1 # not revealed until show_results + "time_limit": 30, + "correct_idx_hint": q["correct"] }) elif msg["type"] == "show_results": @@ -393,5 +437,10 @@ async def host_ws(ws: WebSocket): game.reset() await mgr.broadcast_all({"type": "phase", "phase": "lobby"}) + elif msg["type"] == "clear_lobby": + await mgr.broadcast_players({"type": "kicked"}) + game.reset() + await mgr.broadcast_hosts({"type": "lobby_update", "players": [], "count": 0}) + except WebSocketDisconnect: mgr.disconnect_host(ws) \ No newline at end of file