"""Lo que JARVIS oye y que hace con ello. El combustible de todo lo demas. Sin esto no hay forma de mejorar nada: no se puede arreglar lo que no se mide, y hasta ahora el nucleo no apuntaba absolutamente nada. ## Por que este y no el de antes El diario de la epoca de Newelle guardaba `{"texto": "..."}` y nada mas. Con eso no se puede hacer casi nada: - no se sabe **que paso** con la frase, asi que no se distingue una orden que fallo de una que funciono; - no se sabe **cuando**, asi que no se ve si alguien repitio la misma cosa de tres formas seguidas —que es la señal mas valiosa que hay—; - y solo guardaba los FALLOS, asi que no habia con que comparar. Ademas se llenaba de eco y de alucinaciones de Whisper, y la mejora nocturna acabo generando alias a partir del ruido. De 504 frases, 306 parecian ordenes sin atender y al mirarlas casi todas eran JARVIS oyendose a si mismo. ## Que se guarda Todo lo que llega, con su destino. Una linea por frase, JSON: t cuando oido lo que transcribio Whisper via catalogo | cerebro | ruido | bloqueado | respuesta accion a que accion fue, si fue a alguna arg con que argumento seg lo que tardo en contestar dicho lo que contesto Guardar TAMBIEN los aciertos es lo que permite comparar: sin ellos, un dia malo y un dia bueno tienen el mismo aspecto. ## Lo que NO se guarda Nada de audio. El diario es texto y se puede leer con `less`; si algun dia guardara los WAV, el asistente pasaria a ser una grabadora de la habitacion y eso es otra cosa muy distinta de lo que es. """ import json import os import threading import time from datetime import datetime AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DIARIO = os.path.join(AQUI, "datos", "diario.jsonl") # Un diario que crece sin fin acaba siendo un problema en vez de una ayuda. A # ~150 bytes por linea, 200.000 son unos 30 MB: mas que suficiente para meses de # uso y poco para el disco. TOPE_LINEAS = 200_000 _cerrojo = threading.Lock() def anota(oido: str, via: str, accion: str = "", arg: str = "", seg: float = 0.0, dicho: str = "", ruta: str = DIARIO): """Apunta una frase y su destino. No falla nunca hacia fuera. Si escribir el diario diera error, lo ultimo que debe hacer es tirar al asistente: se pierde una linea y ya. """ if not oido: return linea = { "t": datetime.now().isoformat(timespec="seconds"), "oido": oido[:400], "via": via, } if accion: linea["accion"] = accion if arg: linea["arg"] = arg[:200] if seg: linea["seg"] = round(seg, 2) if dicho: linea["dicho"] = dicho[:300] try: with _cerrojo: os.makedirs(os.path.dirname(ruta), exist_ok=True) with open(ruta, "a", encoding="utf-8") as f: f.write(json.dumps(linea, ensure_ascii=False) + "\n") except OSError: pass def lee(ruta: str = DIARIO, desde=None) -> list: """Las lineas del diario, ya en diccionarios. Salta las corruptas.""" salida = [] try: with open(ruta, encoding="utf-8") as f: for l in f: try: d = json.loads(l) except json.JSONDecodeError: continue if desde and d.get("t", "") < desde: continue salida.append(d) except OSError: pass return salida def recorta(ruta: str = DIARIO, tope: int = TOPE_LINEAS): """Deja solo las ultimas `tope` lineas.""" lineas = [] try: with open(ruta, encoding="utf-8") as f: lineas = f.readlines() except OSError: return if len(lineas) <= tope: return try: with _cerrojo, open(ruta, "w", encoding="utf-8") as f: f.writelines(lineas[-tope:]) except OSError: pass