Add hourly historical refresh runner workflow

This commit is contained in:
devRaGonSa
2026-03-23 20:14:07 +01:00
parent 8ae8184f73
commit 569df34dd1
5 changed files with 188 additions and 2 deletions

View File

@@ -133,6 +133,24 @@ Regeneracion puntual de snapshots mediante refresh controlado:
docker compose exec backend python -m app.historical_runner --max-runs 1 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/`. Si se prefiere operar fuera de Docker, el backend sigue pudiendo arrancar localmente con `python -m app.main` desde `backend/`.
## Evolucion prevista ## Evolucion prevista

View File

@@ -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.

View File

@@ -756,10 +756,27 @@ Flags utiles del runner:
- `--server comunidad-hispana-01` para limitar a un servidor - `--server comunidad-hispana-01` para limitar a un servidor
- `--interval 900` para fijar la frecuencia recomendada de snapshots - `--interval 900` para fijar la frecuencia recomendada de snapshots
- `--hourly` para fijar directamente un ciclo horario de `3600` segundos
- `--retries 1` para reducir reintentos - `--retries 1` para reducir reintentos
- `--retry-delay 10` para bajar la espera entre fallos - `--retry-delay 10` para bajar la espera entre fallos
- `--max-runs 1` para una validacion puntual sin bucle indefinido - `--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 Para regenerar snapshots de forma puntual dentro del contenedor sin dejar un
bucle permanente, la validacion operativa minima es: 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 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: Variables utiles del runner:
- `HLL_HISTORICAL_SNAPSHOT_REFRESH_INTERVAL_SECONDS` - `HLL_HISTORICAL_SNAPSHOT_REFRESH_INTERVAL_SECONDS`

View File

@@ -20,6 +20,13 @@ from .historical_snapshots import (
generate_and_persist_priority_historical_snapshots, 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( def run_periodic_historical_refresh(
*, *,
@@ -34,8 +41,17 @@ def run_periodic_historical_refresh(
"""Run periodic historical refreshes and rebuild persisted snapshots.""" """Run periodic historical refreshes and rebuild persisted snapshots."""
completed_runs = 0 completed_runs = 0
print( print(
"Starting historical refresh loop " json.dumps(
f"(interval={interval_seconds}s, retries={max_retries}, server={server_slug or 'all'})." {
"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.") 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: def main() -> None:
"""Allow local scheduled historical refresh execution without external infra.""" """Allow local scheduled historical refresh execution without external infra."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@@ -140,6 +168,11 @@ def main() -> None:
default=get_historical_refresh_interval_seconds(), default=get_historical_refresh_interval_seconds(),
help="Seconds to wait between refresh-plus-snapshot runs.", 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( parser.add_argument(
"--retries", "--retries",
type=int, type=int,
@@ -177,6 +210,9 @@ def main() -> None:
) )
args = parser.parse_args() args = parser.parse_args()
if args.hourly:
args.interval = HOURLY_INTERVAL_SECONDS
if args.interval <= 0: if args.interval <= 0:
raise ValueError("--interval must be a positive integer.") raise ValueError("--interval must be a positive integer.")
if args.retries < 0: if args.retries < 0:

View File

@@ -11,6 +11,19 @@ services:
- ./backend/data:/app/data - ./backend/data:/app/data
restart: unless-stopped 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: frontend:
build: build:
context: ./frontend context: ./frontend