From 569df34dd1b99c5044b10f0b16ebb347a87ee7ae Mon Sep 17 00:00:00 2001 From: devRaGonSa Date: Mon, 23 Mar 2026 20:14:07 +0100 Subject: [PATCH] Add hourly historical refresh runner workflow --- README.md | 18 +++++ ...64-hourly-historical-refresh-automation.md | 72 +++++++++++++++++++ backend/README.md | 47 ++++++++++++ backend/app/historical_runner.py | 40 ++++++++++- docker-compose.yml | 13 ++++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 ai/tasks/done/TASK-064-hourly-historical-refresh-automation.md diff --git a/README.md b/README.md index 558ed46..d73d093 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,24 @@ Regeneracion puntual de snapshots mediante refresh controlado: docker compose exec backend python -m app.historical_runner --max-runs 1 ``` +Automatizacion horaria recomendada: + +```powershell +docker compose up -d backend historical-runner frontend +``` + +`historical-runner` es un servicio Compose separado que ejecuta +`python -m app.historical_runner --hourly`, refresca el historico de +`comunidad-hispana-01`, `comunidad-hispana-02` y `comunidad-hispana-03`, y +regenera snapshots al terminar cada refresh correcto sin acoplar ese bucle al +proceso HTTP del backend. + +Verificacion minima: + +- `docker compose ps historical-runner` +- `docker compose logs -f historical-runner` +- revisar `generated_at` en `backend/data/snapshots/` + Si se prefiere operar fuera de Docker, el backend sigue pudiendo arrancar localmente con `python -m app.main` desde `backend/`. ## Evolucion prevista diff --git a/ai/tasks/done/TASK-064-hourly-historical-refresh-automation.md b/ai/tasks/done/TASK-064-hourly-historical-refresh-automation.md new file mode 100644 index 0000000..9401944 --- /dev/null +++ b/ai/tasks/done/TASK-064-hourly-historical-refresh-automation.md @@ -0,0 +1,72 @@ +# TASK-064-hourly-historical-refresh-automation + +## Goal +Automatizar la actualización histórica horaria para que el backend ejecute refresh de histórico cada hora y regenere snapshots al finalizar, manteniendo la web histórica al día sin intervención manual. + +## Context +La capa histórica ya dispone de: +- refresh incremental de histórico +- regeneración de snapshots tras una ingesta correcta + +El problema actual es operativo: ese refresh no se está ejecutando de forma periódica, por lo que la UI sigue mostrando partidas antiguas aunque el flujo técnico ya exista. La solución correcta es programar la ejecución horaria del refresh histórico y dejar que la regeneración de snapshots ocurra como parte natural del flujo, sin convertir esta fase en infraestructura final de producción. + +## Steps +1. Revisar la implementación actual de refresh histórico y regeneración de snapshots. +2. Confirmar cómo se ejecuta hoy el flujo para: + - `comunidad-hispana-01` + - `comunidad-hispana-02` + - `comunidad-hispana-03` +3. Diseñar una forma clara de ejecutar el refresh histórico cada hora para los tres servidores. +4. Asegurar que el flujo horario haga: + - refresh histórico + - regeneración de snapshots tras refresh correcto +5. Definir una forma práctica de dejarlo corriendo en: + - entorno local + - entorno Docker / Compose del proyecto +6. Documentar la operativa mínima: + - cómo arrancarlo + - cómo verificar que sigue corriendo + - cómo comprobar que los snapshots se siguen actualizando +7. Mantener fuera del alcance: + - infraestructura final de producción + - TLS + - dominio público + - scheduler externo definitivo +8. Validar que la solución no dependa de regenerar snapshots sin refresh previo. +9. Al completar la implementación: + - dejar el repositorio consistente + - hacer commit + - hacer push al remoto si el entorno lo permite + +## Files to Read First +- AGENTS.md +- backend/README.md +- backend/app/historical_ingestion.py +- backend/app/historical_runner.py +- backend/app/historical_snapshots.py +- docker-compose.yml +- README.md + +## Expected Files to Modify +- backend/README.md +- backend/app/historical_runner.py +- docker-compose.yml +- README.md +- opcionalmente archivos de entorno de ejemplo o documentación técnica adicional si ayudan a dejar clara la automatización horaria + +## Constraints +- No convertir esta task en infraestructura final de producción. +- No romper el flujo local actual del backend. +- No hacer cambios destructivos. +- Mantener el trabajo centrado en refresh histórico horario y snapshots posteriores al refresh. + +## Validation +- Existe una forma clara de ejecutar refresh histórico cada hora. +- El flujo contempla `#01`, `#02` y `#03`. +- La regeneración de snapshots ocurre después del refresh correcto. +- La documentación explica cómo dejarlo corriendo en local o Docker. +- Los cambios quedan committeados y se hace push al remoto si el entorno lo permite. + +## Change Budget +- Preferir menos de 5 archivos modificados o creados. +- Preferir menos de 220 líneas cambiadas. diff --git a/backend/README.md b/backend/README.md index e9adeb0..2031da0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -756,10 +756,27 @@ Flags utiles del runner: - `--server comunidad-hispana-01` para limitar a un servidor - `--interval 900` para fijar la frecuencia recomendada de snapshots +- `--hourly` para fijar directamente un ciclo horario de `3600` segundos - `--retries 1` para reducir reintentos - `--retry-delay 10` para bajar la espera entre fallos - `--max-runs 1` para una validacion puntual sin bucle indefinido +Para dejar automatizado el refresh historico horario de los tres servidores del +proyecto en local, el comando recomendado es: + +```powershell +python -m app.historical_runner --hourly +``` + +Sin `--server`, ese runner refresca: + +- `comunidad-hispana-01` +- `comunidad-hispana-02` +- `comunidad-hispana-03` + +Despues de cada refresh correcto, recompone snapshots para los servidores +afectados y vuelve a alinear el agregado `all-servers`. + Para regenerar snapshots de forma puntual dentro del contenedor sin dejar un bucle permanente, la validacion operativa minima es: @@ -767,6 +784,36 @@ bucle permanente, la validacion operativa minima es: docker compose exec backend python -m app.historical_runner --max-runs 1 ``` +Operativa local minima: + +1. Desde `backend/`, arrancar la API con `python -m app.main`. +2. En otra terminal, dejar corriendo `python -m app.historical_runner --hourly`. +3. Verificar el proceso revisando la salida del runner: al arrancar imprime un + bloque JSON con `event: "historical-refresh-loop-started"`, `server_scope` + y `snapshot_scope`. +4. Confirmar que los snapshots siguen actualizandose revisando `generated_at` + en archivos bajo `backend/data/snapshots/`, por ejemplo: + - `backend/data/snapshots/comunidad-hispana-01/server-summary.json` + - `backend/data/snapshots/comunidad-hispana-02/recent-matches.json` + - `backend/data/snapshots/comunidad-hispana-03/weekly-kills.json` + - `backend/data/snapshots/all-servers/monthly-kills.json` + +Operativa minima con Docker Compose: + +```powershell +docker compose up -d backend historical-runner frontend +``` + +El servicio `historical-runner` usa el mismo volumen persistente `./backend/data` +y ejecuta `python -m app.historical_runner --hourly` como bucle operativo +dedicado, sin mezclar el scheduler con el proceso HTTP principal. + +Comprobaciones utiles con Compose: + +- `docker compose ps historical-runner` +- `docker compose logs -f historical-runner` +- `docker compose exec backend python -m app.historical_runner --max-runs 1` + Variables utiles del runner: - `HLL_HISTORICAL_SNAPSHOT_REFRESH_INTERVAL_SECONDS` diff --git a/backend/app/historical_runner.py b/backend/app/historical_runner.py index d25e215..6f370ff 100644 --- a/backend/app/historical_runner.py +++ b/backend/app/historical_runner.py @@ -20,6 +20,13 @@ from .historical_snapshots import ( generate_and_persist_priority_historical_snapshots, ) +HOURLY_INTERVAL_SECONDS = 3600 +DEFAULT_HISTORICAL_SERVER_SCOPE = ( + "comunidad-hispana-01", + "comunidad-hispana-02", + "comunidad-hispana-03", +) + def run_periodic_historical_refresh( *, @@ -34,8 +41,17 @@ def run_periodic_historical_refresh( """Run periodic historical refreshes and rebuild persisted snapshots.""" completed_runs = 0 print( - "Starting historical refresh loop " - f"(interval={interval_seconds}s, retries={max_retries}, server={server_slug or 'all'})." + json.dumps( + { + "event": "historical-refresh-loop-started", + "interval_seconds": interval_seconds, + "max_retries": max_retries, + "retry_delay_seconds": retry_delay_seconds, + "server_scope": _describe_refresh_scope(server_slug), + "snapshot_scope": _describe_snapshot_scope(server_slug), + }, + indent=2, + ) ) print("Press Ctrl+C to stop.") @@ -129,6 +145,18 @@ def generate_historical_snapshots( } +def _describe_refresh_scope(server_slug: str | None) -> list[str]: + if server_slug: + return [server_slug] + return list(DEFAULT_HISTORICAL_SERVER_SCOPE) + + +def _describe_snapshot_scope(server_slug: str | None) -> list[str]: + if server_slug: + return [server_slug, "all-servers"] + return [*DEFAULT_HISTORICAL_SERVER_SCOPE, "all-servers"] + + def main() -> None: """Allow local scheduled historical refresh execution without external infra.""" parser = argparse.ArgumentParser( @@ -140,6 +168,11 @@ def main() -> None: default=get_historical_refresh_interval_seconds(), help="Seconds to wait between refresh-plus-snapshot runs.", ) + parser.add_argument( + "--hourly", + action="store_true", + help="Shortcut for running the refresh loop every 3600 seconds.", + ) parser.add_argument( "--retries", type=int, @@ -177,6 +210,9 @@ def main() -> None: ) args = parser.parse_args() + if args.hourly: + args.interval = HOURLY_INTERVAL_SECONDS + if args.interval <= 0: raise ValueError("--interval must be a positive integer.") if args.retries < 0: diff --git a/docker-compose.yml b/docker-compose.yml index 0339cd9..aee3860 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,19 @@ services: - ./backend/data:/app/data restart: unless-stopped + historical-runner: + build: + context: ./backend + container_name: hll-vietnam-historical-runner + command: ["python", "-m", "app.historical_runner", "--hourly"] + env_file: + - ./backend/.env.example + depends_on: + - backend + volumes: + - ./backend/data:/app/data + restart: unless-stopped + frontend: build: context: ./frontend