From 159080f0c83df6a7f0b5f40632dc763d1ccaaa01 Mon Sep 17 00:00:00 2001 From: rafa Date: Mon, 3 Aug 2026 22:10:15 -0400 Subject: [PATCH] TTS backlog: no dar falsa alarma los dias sin cron (#188) El informe avisaba de "ninguna ventana dejo rastro en 24 h" los miercoles y jueves, que son dias de carta y no tienen cron por diseno. Ahora lee el crontab, cuenta cuantas ventanas TENIAN que correr y solo avisa si de verdad tocaba. Si Rafa cambia los dias del cron, el informe le sigue solo. Co-Authored-By: Claude Opus 5 --- scripts/fea_tts_backlog_report.py | 64 ++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/scripts/fea_tts_backlog_report.py b/scripts/fea_tts_backlog_report.py index c844626..e87f885 100644 --- a/scripts/fea_tts_backlog_report.py +++ b/scripts/fea_tts_backlog_report.py @@ -121,6 +121,61 @@ def logs_24h() -> tuple[int, int, list[str]]: return corridas, saltadas, fallos +def _campo_cron(campo: str, valores: range) -> set[int]: + """Expande un campo de crontab ('*', '*/5', '1,5,6,0', '0-4') a un set.""" + if campo == "*": + return set(valores) + out: set[int] = set() + for trozo in campo.split(","): + paso = 1 + if "/" in trozo: + trozo, p = trozo.split("/", 1) + paso = int(p) + if trozo == "*": + base = list(valores) + elif "-" in trozo: + a, b = (int(x) for x in trozo.split("-", 1)) + base = list(range(a, b + 1)) + else: + base = [int(trozo)] + out.update(base[::paso] if paso > 1 else base) + return out + + +def previstas_24h(ahora: datetime | None = None) -> int | None: + """Cuántas ventanas TENÍA que haber corrido el cron en las últimas 24 h. + + Sin esto, los martes/miércoles/jueves (días de carta, sin cron) el informe + daba la alarma de 'ninguna ventana dejó rastro' estando todo correcto. La + verdad está en el crontab, no aquí: si Rafa cambia los días, esto le sigue. + """ + try: + linea = next( + l for l in subprocess.run(["crontab", "-l"], text=True, capture_output=True, + check=True).stdout.splitlines() + if "tts_backlog_cron.sh" in l and not l.lstrip().startswith("#")) + except Exception: # noqa: BLE001 + return None + campos = linea.split(None, 5) + if len(campos) < 5: + return None + minutos = _campo_cron(campos[0], range(60)) + horas = _campo_cron(campos[1], range(24)) + dows = {d % 7 for d in _campo_cron(campos[4], range(7))} # cron: 0 y 7 = domingo + + ahora = ahora or datetime.now() + n = 0 + for h in range(25): + t = (ahora - timedelta(hours=h)).replace(second=0, microsecond=0) + for m in minutos: + cand = t.replace(minute=m) + if not (ahora - timedelta(hours=24) < cand <= ahora): + continue + if cand.hour in horas and (cand.weekday() + 1) % 7 in dows: + n += 1 + return n + + def main() -> int: hechos = hechos_24h() total = sum(len(v) for v in hechos.values()) @@ -151,11 +206,16 @@ def main() -> int: lineas.append("Cuota MiniMax: no se pudo leer") else: lineas.append(f"Cuota MiniMax: 5h {p5:.0f}% · semana {pw:.0f}%") - lineas.append(f"Ventanas 24 h: {corridas} ejecutadas, {saltadas} saltadas por cuota") + previstas = previstas_24h() + de = "" if previstas is None else f" de {previstas} previstas" + lineas.append(f"Ventanas 24 h: {corridas} ejecutadas{de}, {saltadas} saltadas por cuota") + if previstas == 0: + lineas.append(" (sin ventanas previstas: día sin cron, toca carta)") # Una ventana que ni arranca ni se salta es un fallo mudo: el cron no llegó a # correr (bit +x, WSL apagada...). Es exactamente lo que pasó el 2-ago. - if corridas == 0 and saltadas == 0: + # Solo es alarma si de verdad tocaba correr; si no, es martes. + if corridas == 0 and saltadas == 0 and previstas != 0: lineas.append("") lineas.append("⚠️ Ninguna ventana dejó rastro en 24 h. Si tocaba que corriera, " f"comprobar: crontab -l | grep tts_backlog · ls -l {CRON}")