Files
feadulta/scripts/tts_backlog_cron.sh
T
rafa c5dcbdb997 TTS backlog: arreglar el gate de cuota y anadir el reporte diario (#188)
Gate: `int(v or 100)` trataba `five_h_pct = 0.0` como ausencia de dato y
devolvia 100, o sea que la tanda abortaba justo cuando la ventana de 5h estaba
entera libre. Cazado en la primera ventana real. Ahora se distingue 0.0 de None.

Reporte diario (F2): scripts/fea_tts_backlog_report.py, solo lectura. Cuenta
los audios de las ultimas 24h por autor (atribuidos por fea_audio_voice), lo que
queda por autor, la cuota de MiniMax y las ventanas ejecutadas/saltadas. Avisa
aparte si en 24h no hubo NI ventana ejecutada NI saltada, que es el sintoma de
que el cron no llego a correr (fue lo que paso el 2-ago con el bit +x).

Vive en el repo y ~/.hermes/scripts/ lo ve por symlink: sin copia duplicada que
se desincronice. Job: `hermes cron feadulta-tts-backlog-daily`, 07:30, no-agent,
deliver origin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 18:53:26 -04:00

86 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Cron del backlog de TTS por autor (issue rafa/feadulta#188).
# Corre cada 5 h los lunes, viernes, sábados y domingos, aprovechando la cuota
# ociosa de MiniMax para locutar artículos antiguos. Por ventana:
# 1) Gate de cuota MiniMax: si la semana va alta, o si la ventana de 5 h ya la
# está usando alguien (la carta semanal, el backfill de summaraise...), se
# salta. El trabajo de fondo nunca le come la cuota al trabajo con dueño.
# 2) tts_produce.py --autor ... --max N: la cola sale de la BD, así que esto es
# idempotente por construcción — lo ya locutado no vuelve a salir.
# SOLO LOCAL: no toca producción. Publicar en prod es sync_audio_to_prod.py, que
# está bloqueado hasta después del cutover a Hetzner (#180).
# flock evita solapes si una ventana se alargara. Log por día.
set -uo pipefail
export PATH="/home/rafa/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
export HOME="/home/rafa"
REPO="/home/rafa/joomla-migration"
PY="/home/rafa/tts-local/xtts-venv/bin/python"
QUOTA="/home/rafa/ytsummaries/scripts/quota.py"
WORK="/tmp/fea-tts-backlog"
LOG="$WORK/cron-$(date +%F).log"
LOCK="$WORK/cron.lock"
# Cola: cambiar aquí (o por entorno) para pasar de un autor a otro.
# 382 Fray Marcos · 383 Pagola · 774 Sicre · 386 Arregi
AUTOR="${FEA_TTS_AUTOR:-382}"
DESDE="${FEA_TTS_DESDE:-2025}"
HASTA="${FEA_TTS_HASTA:-2026}"
BATCH="${FEA_TTS_BATCH:-10}"
# Gates de cuota (%). El semanal es el techo real del backlog. El de 5 h es
# "¿cabe esta tanda en lo que queda de ventana?": medido, cada audio gasta ~3-4 %
# de la ventana de 5 h, así que un batch de 10 pide ~40 %. Gate en 55 % deja sitio
# y frena solo cuando la ventana ya la está usando otro (la carta semanal son ~28
# audios de golpe) o cuando la está llenando este mismo cron. Si se sube BATCH,
# subir también este número o se saltarán ventanas.
MAX_SEMANA="${FEA_TTS_MAX_SEMANA:-85}"
MAX_5H="${FEA_TTS_MAX_5H:-55}"
mkdir -p "$WORK"
cd "$REPO" || exit 1
ts() { date +'%F %T'; }
exec 9>"$LOCK"
if ! flock -n 9; then
echo "[$(ts)] otra corrida en curso, salto." >> "$LOG"; exit 0
fi
echo "[$(ts)] === cron TTS backlog start (autor=$AUTOR $DESDE-$HASTA batch=$BATCH) ===" >> "$LOG"
# 1) Gate de cuota MiniMax (ventana de 5 h y semanal)
read -r PCT5 PCTW <<< "$(python3 "$QUOTA" --json --no-local 2>/dev/null | python3 -c '
import json, sys
def pct(v):
# OJO: 0.0 es un valor legítimo (ventana entera libre) y es falsy en Python.
# Un `v or 100` aquí aborta la tanda justo cuando hay toda la cuota disponible.
return int(v) if v is not None else 100
try:
d = json.load(sys.stdin)
m = next(p for p in d["providers"] if p["provider"] == "minimax" and p.get("ok"))
print(pct(m.get("five_h_pct")), pct(m.get("week_pct")))
except Exception:
print(100, 100) # sin lectura fiable de cuota, no se gasta
' 2>/dev/null || echo "100 100")"
echo "[$(ts)] MiniMax 5h=${PCT5}% semana=${PCTW}%" >> "$LOG"
if [ "${PCTW:-100}" -ge "$MAX_SEMANA" ]; then
echo "[$(ts)] ABORT: cuota semanal ${PCTW}% >= ${MAX_SEMANA}%, salto esta ventana." >> "$LOG"
exit 0
fi
if [ "${PCT5:-100}" -ge "$MAX_5H" ]; then
echo "[$(ts)] ABORT: ventana de 5h al ${PCT5}% >= ${MAX_5H}%, no cabe la tanda; salto." >> "$LOG"
exit 0
fi
# 2) Tanda. tts_produce.py ya para solo ante rc 2056/1039 (cuota/rate limit).
echo "[$(ts)] tts_produce --autor $AUTOR --desde $DESDE --hasta $HASTA --max $BATCH ..." >> "$LOG"
"$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \
--max "$BATCH" >> "$LOG" 2>&1
# 3) Cuánto queda tras la tanda (recuento fresco de la BD, barato y sin cuota).
QUEDAN="$("$PY" scripts/tts_produce.py --autor "$AUTOR" --desde "$DESDE" --hasta "$HASTA" \
--dry-run 2>/dev/null | sed -n 's/.*Cola: \([0-9]*\) posts.*/\1/p' | tail -1)"
echo "[$(ts)] === cron TTS backlog done. Pendientes autor $AUTOR $DESDE-$HASTA: ${QUEDAN:-?} ===" >> "$LOG"