#!/usr/bin/env python3 """Que ha aprendido JARVIS del uso, y que conviene cambiar. ./mejora.py que hay en el diario ./mejora.py --propone que acciones o frases faltan ./mejora.py --mide cuanto acierta ahora mismo ## En que se diferencia de la mejora nocturna que se retiro La de antes corria sola de madrugada, se modificaba el codigo y se commiteaba. Nueve horas, 39 rondas, cero commits utiles. Fallo por tres cosas, y las tres estan corregidas aqui: 1. **Minaba ruido.** Su fuente era un registro donde el 60 % era JARVIS oyendose a si mismo y alucinaciones de Whisper. Ahora la guarda del eco y el filtro de ruido limpian antes, y el diario apunta la VIA de cada frase, asi que el ruido esta separado y etiquetado en vez de mezclado. 2. **Su medida tenia mas ruido que su mejora.** Generaba audio nuevo cada ronda: ±1,9 puntos de ruido contra +1,45 de mejora acumulada, o sea que "¿esto mejora o empeora?" se decidia a cara o cruz. Ahora se mide contra `datos/banco.json`, que es fijo y determinista. 3. **Actuaba sola.** Un bucle que se reescribe y se aprueba a si mismo acaba pasando siempre. Esto PROPONE y para: lo que se aplica lo decide una persona mirandolo. Y una cuarta, de fondo: aquella tocaba el codigo. Esta solo mira el catalogo, que es lo unico que se puede validar de verdad con el banco. """ import argparse import collections import os import sys AQUI = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, AQUI) from manos import Manos # noqa: E402 from manos.acciones import normaliza, parecido # noqa: E402 from manos.diario import lee # noqa: E402 def color(t, c): return f"\033[{c}m{t}\033[0m" if sys.stdout.isatty() else t # ------------------------------------------------------------------ mirar def resumen(lineas): if not lineas: print(" el diario esta vacio: usa JARVIS un rato y vuelve") return 1 vias = collections.Counter(l.get("via", "?") for l in lineas) tot = sum(vias.values()) print(f"\n {tot} frases desde {lineas[0]['t'][:10]}\n") print(f" {'via':14s} {'n':>5s} {'%':>7s}") print(" " + "-" * 32) ORDEN = ["catalogo", "cerebro", "respuesta", "ruido", "sin-nombre", "bloqueado", "sin-cerebro"] for v in ORDEN + [k for k in vias if k not in ORDEN]: if v in vias: print(f" {v:14s} {vias[v]:5d} {100*vias[v]/tot:6.1f} %") atendidas = vias["catalogo"] + vias["cerebro"] if atendidas: print(f"\n de lo ATENDIDO ({atendidas}), el catalogo cogio " f"{100*vias['catalogo']/atendidas:.0f} %") segs = [l.get("seg", 0) for l in lineas if l.get("via") == "catalogo"] segc = [l.get("seg", 0) for l in lineas if l.get("via") == "cerebro"] if segs: print(f" catalogo: {sum(segs)/len(segs):.2f} s de media") if segc: print(f" cerebro: {sum(segc)/len(segc):.1f} s de media") ahorro = (sum(segc)/len(segc) - (sum(segs)/len(segs) if segs else 0)) print(f"\n cada accion que se añada al catalogo ahorra ~{ahorro:.0f} s") top = collections.Counter(l["accion"] for l in lineas if l.get("via") == "catalogo" and l.get("accion")) if top: print("\n lo que mas usas:") for a, n in top.most_common(8): print(f" {n:4d} {a}") return 0 # --------------------------------------------------------------- proponer def propone(lineas, manos): """Lo que fue al cerebro y podria ser una accion del catalogo.""" # "sin-cerebro" cuenta igual: es lo mismo —una frase que el catalogo no # cubrio— y solo se diferencia en que no habia modelo conectado para # atenderla. El hueco del catalogo es el mismo. alCerebro = [l for l in lineas if l.get("via") in ("cerebro", "sin-cerebro")] if not alCerebro: print("\n nada ha ido al cerebro todavia: no hay hueco que rellenar") return 0 # Agrupar lo parecido: si has pedido lo mismo de tres formas, es UNA accion # que falta, no tres. Y que lo hayas repetido es justo la señal de que # importa. grupos = [] for l in alCerebro: t = normaliza(l["oido"]) if len(t.split()) < 2: continue for g in grupos: if parecido(t, g["patron"]) > 0.62: g["frases"].append(l["oido"]) break else: grupos.append({"patron": t, "frases": [l["oido"]], "dicho": l.get("dicho", "")}) grupos.sort(key=lambda g: -len(g["frases"])) print(f"\n {len(alCerebro)} frases fueron al cerebro, en {len(grupos)} grupos.") print(" Las que mas se repiten son las acciones que faltan:\n") for g in grupos[:12]: n = len(g["frases"]) marca = color(f"{n:3d} veces", "33" if n > 1 else "90") print(f" {marca} {g['frases'][0][:64]}") for f in g["frases"][1:4]: print(f" {color('tambien: ' + f[:56], '90')}") if g["dicho"]: print(f" {color('contesto: ' + g['dicho'][:56], '90')}") print() repetidas = [g for g in grupos if len(g["frases"]) > 1] print(f" {len(repetidas)} se han pedido mas de una vez: por ahi empezaria.\n") print(color(" Esto NO cambia nada. Para añadir una accion se edita\n" " datos/_generar.py —que es la FUENTE— y se regenera.\n", "90")) return 0 # ------------------------------------------------------------------ medir def mide(manos): """El acierto del emparejador contra el banco fijo.""" import json banco = os.path.join(AQUI, "datos", "banco.json") try: with open(banco, encoding="utf-8") as f: casos = json.load(f)["casos"] except OSError: print(" no hay banco: pruebas/banco.py --crear") return 1 bien = otra = 0 for c in casos: a, _ = manos.busca(c["oido"]) if a is None: continue if a.id == c["id"]: bien += 1 else: otra += 1 print(f"\n acierto {100*bien/len(casos):.1f} % sobre {len(casos)} frases") print(f" ordenes que acaban en la accion equivocada: {otra}") print(color("\n Este numero es comparable entre dias: el banco esta grabado\n" " una vez y no cambia. Era lo que le faltaba a la mejora\n" " nocturna, cuyo ruido de medida era mayor que su mejora.\n", "90")) return 0 def main() -> int: p = argparse.ArgumentParser(description="Que ha aprendido JARVIS del uso") p.add_argument("--propone", action="store_true") p.add_argument("--mide", action="store_true") a = p.parse_args() manos = Manos() lineas = lee() if a.mide: return mide(manos) if a.propone: return propone(lineas, manos) return resumen(lineas) if __name__ == "__main__": sys.exit(main())