diff --git a/scripts/fea_tts_backlog_report.py b/scripts/fea_tts_backlog_report.py old mode 100644 new mode 100755 index e87f885..11495ec --- a/scripts/fea_tts_backlog_report.py +++ b/scripts/fea_tts_backlog_report.py @@ -39,18 +39,21 @@ AUTORES = { 386: ("Arregi", "ArregiFeadulta2026"), } -# Autor y rango que está locutando el cron ahora mismo (los valores por defecto -# de tts_backlog_cron.sh). Solo afecta a qué línea se marca como "en curso". -ACTIVO = (382, 2025, 2026) +# Autor y rango que está locutando el cron ahora mismo. Debe seguir el crontab: +# solo afecta a qué línea se marca como «en curso» en el informe diario. +ACTIVO = (774, 2000, 2026) def php(*args: str) -> str: - """fea_post_io.php dentro del contenedor. stderr fuera: WP escupe warnings.""" + """Llama al helper WP sin convertir un fallo de lectura en una cola vacía.""" r = subprocess.run( ["docker", "exec", CONTAINER, "php", "/tmp/fea_post_io.php", *args], capture_output=True, text=True, ) - return r.stdout if r.returncode == 0 else "" + if r.returncode != 0: + detail = (r.stderr or r.stdout).strip().replace("\n", " ")[:300] + raise RuntimeError(f"consulta WP {' '.join(args)} falló (rc={r.returncode}): {detail}") + return r.stdout def pendientes(autor: int, desde: int, hasta: int, voz: str) -> int: @@ -143,41 +146,62 @@ def _campo_cron(campo: str, valores: range) -> set[int]: def previstas_24h(ahora: datetime | None = None) -> int | None: - """Cuántas ventanas TENÍA que haber corrido el cron en las últimas 24 h. + """Cuántas ventanas tenían que haber corrido en las últimas 24 horas. - 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. + El backlog usa varias líneas de crontab (ritmo normal + sábado + domingo). + Se calcula la unión de sus instantes previstos, sin contar dos veces una + coincidencia entre líneas. """ try: - linea = next( + lineas = [ 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("#")) + 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: + if not lineas: 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 + inicio = ahora - timedelta(hours=24) + previstos: set[datetime] = set() + for linea in lineas: + campos = linea.split(None, 5) + if len(campos) < 5: + continue + try: + 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))} + except (TypeError, ValueError): + continue + 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 inicio < cand <= ahora and cand.hour in horas and (cand.weekday() + 1) % 7 in dows: + previstos.add(cand) + return len(previstos) def main() -> int: - hechos = hechos_24h() + # Nunca informar «0 pendientes» cuando la lectura de WordPress ha fallado. + # El cron no-agent entrega stdout/errores tal cual: un aviso explícito permite + # arreglar Docker/helper sin que parezca que el backlog está terminado. + try: + hechos = hechos_24h() + faltantes = { + aid: (pendientes(aid, 0, 9999, voz), + pendientes(aid, ACTIVO[1], ACTIVO[2], voz) if aid == ACTIVO[0] else None) + for aid, (_nombre, voz) in AUTORES.items() + } + except RuntimeError as exc: + print("⚠️ Fe Adulta — informe TTS inválido: no se pudo leer WordPress.") + print(f" {exc}") + print(" Los ceros no son datos reales; revisar wordpress-web y /tmp/fea_post_io.php.") + return 1 + total = sum(len(v) for v in hechos.values()) corridas, saltadas, fallos = logs_24h() p5, pw = cuota() @@ -194,10 +218,9 @@ def main() -> int: lineas.append("") lineas.append("Pendientes:") for aid, (nombre, voz) in AUTORES.items(): - falta_todo = pendientes(aid, 0, 9999, voz) + falta_todo, falta_lote = faltantes[aid] marca = "" if aid == ACTIVO[0]: - falta_lote = pendientes(aid, ACTIVO[1], ACTIVO[2], voz) marca = f" ← en curso, {falta_lote} del lote {ACTIVO[1]}-{ACTIVO[2]}" lineas.append(f" {nombre}: {falta_todo}{marca}")