"""Las medidas de eficiencia de JARVIS, sacadas del diario. El diario ya apunta por cada frase: cuanto tardo (`seg`), por donde fue (`via`) y cuando (`t`). De ahi salen las metricas de rendimiento sin instrumentar nada del camino caliente: esto solo LEE, y se calcula fuera del hilo que contesta. ## Que se mide y por que importa cada una latencia p50/p95/max del tiempo de respuesta. La mediana dice como va de normal; el p95 dice como va cuando va mal, que es lo que se nota. reparto que fraccion va al CEREBRO (lento, ~2-3 s) y cual al CATALOGO (instantaneo, salta el modelo). Es la palanca de eficiencia mas grande: cuanto mas catalogo, mas rapido el conjunto. ruido que fraccion de lo que oye es Whisper oyendose a si mismo. No es velocidad, es calidad de la escucha, y envenena todo lo demas. volumen frases en las ultimas 24 h. Contexto para lo anterior: un p95 malo con 4 frases no dice nada. rag hit@5/hit@1 del ultimo benchmark del RAG (informe_rag.txt). Es la unica que no sale del diario: es una medida de test, periodica. Todo sobre una VENTANA reciente (las ultimas MUESTRA frases), no sobre el diario entero: interesa como va AHORA, no la media historica que nunca cambia. """ import os import re from datetime import datetime, timedelta from manos.diario import lee AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) INFORME_RAG = os.path.join(AQUI, "datos", "informe_rag.txt") MUESTRA = 600 # las ultimas N frases: "como va ahora" CEREBRO = "cerebro" def _percentil(valores, q): if not valores: return 0.0 v = sorted(valores) k = (len(v) - 1) * q lo, hi = int(k), min(int(k) + 1, len(v) - 1) return v[lo] + (v[hi] - v[lo]) * (k - lo) def _rag(): """hit@5 y hit@1 del ultimo informe del RAG, si existe.""" try: txt = open(INFORME_RAG, encoding="utf-8").read() except OSError: return None # el bloque CANDIDATO es el que se promociono; si no, el ultimo que haya def pct(clave): # " hit@5 43/45 (95%)" -> 95 ms = re.findall(clave + r"\s+\d+/\d+\s+\((\d+)%\)", txt) return int(ms[-1]) if ms else None h5, h1 = pct("hit@5"), pct("hit@1") if h5 is None: return None return {"hit5": h5, "hit1": h1 or 0} def lee_medidas(): """El dict de medidas, con las unidades ya listas para el HUD.""" diario = lee()[-MUESTRA:] if not diario: return {"n": 0} segs = [d["seg"] for d in diario if isinstance(d.get("seg"), (int, float)) and d["seg"] > 0] seg_cerebro = [d["seg"] for d in diario if d.get("via") == CEREBRO and isinstance(d.get("seg"), (int, float))] seg_catalogo = [d["seg"] for d in diario if d.get("via") == "catalogo" and isinstance(d.get("seg"), (int, float))] vias = {} for d in diario: vias[d.get("via", "?")] = vias.get(d.get("via", "?"), 0) + 1 total = len(diario) def pct_via(v): return round(100 * vias.get(v, 0) / total) if total else 0 # volumen: cuantas en las ultimas 24 h, y por hora las ultimas 12 para la traza ahora = datetime.now() hace24 = ahora - timedelta(hours=24) vol24 = 0 porhora = [0] * 12 for d in diario: try: t = datetime.fromisoformat(d.get("t", "")) except (ValueError, TypeError): continue if t >= hace24: vol24 += 1 h = int((ahora - t).total_seconds() // 3600) if 0 <= h < 12: porhora[11 - h] += 1 medidas = { "n": total, "lat_p50": round(_percentil(segs, 0.50), 2), "lat_p95": round(_percentil(segs, 0.95), 2), "lat_max": round(max(segs), 2) if segs else 0.0, "lat_cerebro": round(sum(seg_cerebro) / len(seg_cerebro), 2) if seg_cerebro else 0.0, "lat_catalogo": round(sum(seg_catalogo) / len(seg_catalogo), 2) if seg_catalogo else 0.0, "pct_cerebro": pct_via("cerebro"), "pct_catalogo": pct_via("catalogo"), "pct_ruido": pct_via("ruido"), "vol24": vol24, "porhora": porhora, } rag = _rag() if rag: medidas["rag"] = rag return medidas