03-oct: superguardado automático
This commit is contained in:
133
scripts/monitor_estado/ciclo.py
Normal file
133
scripts/monitor_estado/ciclo.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""ciclo.py — Orquestador de un ciclo del monitor (RF-1..RF-18, RF-23, RF-24).
|
||||
|
||||
Ejecuta la recogida de todas las fuentes, el archivado por fuente, el resumen IA,
|
||||
el aviso por voz y el mantenimiento (poda, marca de ultimo ciclo). Aisla los errores
|
||||
por fuente: una fuente caida no aborta el ciclo (RF-6).
|
||||
|
||||
Las dependencias externas (fuentes, IA, aviso) son inyectables para poder testear.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from . import archivo, aviso, config, resumen, vistos
|
||||
from .fuentes import correo as correo_mod
|
||||
from .fuentes import jira as jira_mod
|
||||
from .fuentes import telegram as telegram_mod
|
||||
|
||||
|
||||
def _desde_jira(ahora: datetime) -> datetime:
|
||||
"""Instante desde el que buscar incidencias nuevas: ultimo ciclo o, si no, 24 h."""
|
||||
ultimo = archivo.leer_ultimo_ciclo()
|
||||
inicio = ultimo.get("inicio")
|
||||
if inicio:
|
||||
try:
|
||||
return datetime.fromisoformat(inicio)
|
||||
except Exception:
|
||||
pass
|
||||
return ahora - timedelta(hours=24)
|
||||
|
||||
|
||||
def ejecutar(cfg: dict | None = None, hoy: date | None = None, deps: dict | None = None) -> dict:
|
||||
"""Ejecuta un ciclo completo. Devuelve el resumen estructurado del ciclo."""
|
||||
cfg = cfg or config.cargar()
|
||||
ahora = datetime.now()
|
||||
hoy = hoy or ahora.date()
|
||||
deps = deps or {}
|
||||
|
||||
telegram_recoger = deps.get("telegram_recoger", telegram_mod.recoger_todas)
|
||||
correo_recoger = deps.get("correo_recoger", correo_mod.recoger_recientes)
|
||||
jira_cliente = deps.get("jira_cliente")
|
||||
ia_fn = deps.get("ia")
|
||||
avisar_fn = deps.get("avisar", aviso.avisar)
|
||||
|
||||
novedades: dict[str, list] = {}
|
||||
errores: list[tuple[str, str]] = []
|
||||
|
||||
# --- Telegram ---
|
||||
try:
|
||||
resultado, errs = telegram_recoger(cfg, hoy)
|
||||
for nombre, msgs in resultado.items():
|
||||
archivo.escribe_dia(
|
||||
f"telegram/{nombre}", hoy, msgs,
|
||||
id_fn=lambda m: m["id"], render_fn=telegram_mod.render_mensaje,
|
||||
titulo=f"{nombre} — {hoy.isoformat()}",
|
||||
)
|
||||
nuevos = vistos.filtrar_nuevos(f"telegram_{nombre}", msgs, id_fn=lambda m: m["id"])
|
||||
if nuevos:
|
||||
novedades[f"telegram/{nombre}"] = nuevos
|
||||
errores += [(f"telegram/{n}", e) for n, e in errs]
|
||||
except Exception as e:
|
||||
errores.append(("telegram", str(e)))
|
||||
|
||||
# --- Correo ---
|
||||
try:
|
||||
correos = correo_recoger(horas=cfg.get("correo", {}).get("horas", 24))
|
||||
archivo.escribe_dia("correo", hoy, correos, id_fn=lambda m: m["id"], render_fn=correo_mod.render_correo)
|
||||
nuevos = vistos.filtrar_nuevos("correo", correos, id_fn=lambda m: m["id"])
|
||||
if nuevos:
|
||||
novedades["correo"] = nuevos
|
||||
except Exception as e:
|
||||
errores.append(("correo", str(e)))
|
||||
|
||||
# --- Jira (solo lectura) ---
|
||||
try:
|
||||
cliente = jira_cliente or jira_mod.construir_cliente(cfg)
|
||||
ds_nuevos = jira_mod.ds_nuevos(cliente, _desde_jira(ahora))
|
||||
ds_sin_asignar = jira_mod.ds_sin_asignar(cliente)
|
||||
todos = ds_nuevos + ds_sin_asignar
|
||||
archivo.escribe_dia("jira", hoy, todos, id_fn=lambda m: m["key"], render_fn=jira_mod.render_jira)
|
||||
nuevos = vistos.filtrar_nuevos("jira", todos, id_fn=lambda m: m["key"])
|
||||
if nuevos:
|
||||
novedades["jira"] = nuevos
|
||||
except Exception as e:
|
||||
errores.append(("jira", str(e)))
|
||||
|
||||
# --- Resumen IA + clasificacion ---
|
||||
res = resumen.generar(novedades, errores, cfg=cfg, _ia=ia_fn)
|
||||
archivo.escribe_resumen(res)
|
||||
|
||||
# --- Aviso por voz ---
|
||||
emitido = avisar_fn(res, cfg=cfg, _runner=deps.get("aviso_runner"))
|
||||
res["aviso_emitido"] = emitido
|
||||
|
||||
# --- Mantenimiento ---
|
||||
archivo.poda(cfg.get("retencion_dias", 30), hoy)
|
||||
archivo.escribe_ultimo_ciclo({
|
||||
"inicio": ahora.isoformat(),
|
||||
"fin": datetime.now().isoformat(),
|
||||
"ok": not errores,
|
||||
"fuentes_con_novedad": res.get("fuentes_con_novedad", []),
|
||||
"errores": res.get("errores", []),
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
def texto_estado() -> str:
|
||||
"""Texto legible del ultimo resumen y estado, para el comando `estado` (RF-18)."""
|
||||
r = archivo.leer_ultimo_ciclo()
|
||||
resumen_json = {}
|
||||
ruta = archivo.MONITOR_DIR / "resumen_actual.json"
|
||||
if ruta.exists():
|
||||
import json
|
||||
try:
|
||||
resumen_json = json.loads(ruta.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
resumen_json = {}
|
||||
lineas = [
|
||||
"── MONITOR DE ESTADO ──",
|
||||
f"Datos en : {archivo.MONITOR_DIR}",
|
||||
f"Ultimo ciclo : {r.get('inicio', 'nunca')} (ok={r.get('ok', '-')})",
|
||||
]
|
||||
if r.get("errores"):
|
||||
lineas.append(f"Errores : {len(r['errores'])}")
|
||||
lineas.append("── Ultimo resumen ──")
|
||||
lineas.append(resumen_json.get("resumen_md", "(sin resumen)") or "(sin resumen)")
|
||||
atencion = resumen_json.get("requiere_atencion", [])
|
||||
if atencion:
|
||||
lineas.append("Requiere atencion:")
|
||||
for a in atencion:
|
||||
lineas.append(f" - [{a.get('fuente', '?')}] {a.get('texto', '')} ({a.get('motivo', '')})")
|
||||
return "\n".join(lineas)
|
||||
Reference in New Issue
Block a user