From 28a986f2a70694c76eef2ee8924a552479581fa4 Mon Sep 17 00:00:00 2001 From: devRaGonSa Date: Mon, 23 Mar 2026 13:46:09 +0100 Subject: [PATCH] Complete TASK-053 snapshot read fast path --- ...istorical-snapshots-read-only-fast-path.md | 74 +++++++++++++++++++ backend/README.md | 15 ++-- backend/app/payloads.py | 24 ++---- 3 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 ai/tasks/done/TASK-053-historical-snapshots-read-only-fast-path.md diff --git a/ai/tasks/done/TASK-053-historical-snapshots-read-only-fast-path.md b/ai/tasks/done/TASK-053-historical-snapshots-read-only-fast-path.md new file mode 100644 index 0000000..959e983 --- /dev/null +++ b/ai/tasks/done/TASK-053-historical-snapshots-read-only-fast-path.md @@ -0,0 +1,74 @@ +# TASK-053-historical-snapshots-read-only-fast-path + +## Goal +Asegurar que los endpoints de snapshots históricos se comportan como una capa de lectura rápida y no intentan recomponer o regenerar snapshots costosos durante la solicitud del usuario. + +## Context +La página histórica debe sentirse rápida. Para conseguirlo, los snapshots deben existir previamente y leerse de forma casi inmediata. Si una petición a la UI termina provocando una recomposición, una regeneración parcial o una ruta de fallback costosa, el tiempo percibido se degrada mucho. La generación debe vivir en la capa de bootstrap/runner/scheduler, no en la interacción del usuario. + +## Steps +1. Revisar la implementación actual de los endpoints de snapshots históricos y la lógica de payload para: + - resumen + - weekly leaderboard + - partidas recientes +2. Identificar si alguna de esas rutas intenta recomponer, regenerar o recalcular snapshots costosos durante la petición. +3. Corregir el comportamiento para que los endpoints de snapshots: + - lean snapshots ya existentes + - respondan rápido + - devuelvan metadatos claros si el snapshot no está listo + - no bloqueen la solicitud por regeneración pesada +4. Mantener el sistema de generación y refresco de snapshots en la capa operativa adecuada: + - bootstrap + - refresh + - runner periódico +5. Asegurar que cuando un snapshot no exista, la respuesta sea rápida y clara, sin dejar al usuario esperando recomposición pesada. +6. Documentar el contrato operativo esperado: lectura rápida en request path, generación fuera del request path. +7. No cambiar la UI en esta task salvo lo estrictamente necesario para reflejar metadatos ya existentes. +8. 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/routes.py +- backend/app/payloads.py +- backend/app/historical_snapshot_storage.py +- backend/app/historical_snapshots.py +- backend/app/historical_runner.py +- backend/app/historical_ingestion.py + +## Expected Files to Modify +- backend/app/routes.py +- backend/app/payloads.py +- backend/app/historical_snapshot_storage.py +- backend/app/historical_snapshots.py +- backend/README.md +- opcionalmente documentación técnica adicional si hace falta dejar la política clara + +## Constraints +- No usar A2S para esta capa. +- No volver a agregados on-demand pesados como estrategia principal. +- No crear páginas nuevas. +- No hacer cambios destructivos. +- Mantener el trabajo centrado en un fast read path para snapshots. + +## Validation +- Los endpoints de snapshots responden como lectura rápida. +- No hay recomposición pesada o regeneración costosa en el request path del usuario. +- Si un snapshot no existe, la respuesta es rápida y clara. +- La documentación deja clara la política operativa. +- Los cambios quedan committeados y se hace push si el entorno lo permite. + +## Change Budget +- Preferir menos de 5 archivos modificados o creados. +- Preferir menos de 220 líneas cambiadas. + +## Outcome +- Los endpoints `/api/historical/snapshots/*` ya no intentan recomponer ni regenerar snapshots durante la petición del usuario. +- Cuando un snapshot no existe, la API responde rápido con metadata explícita de fast path: `snapshot_status`, `missing_reason`, `request_path_policy` y `generation_policy`. +- La política operativa queda documentada en `backend/README.md`: lectura rápida en request path y generación solo en `historical_ingestion` o `historical_runner`. +- Validación local: +- import y ejecución de `build_historical_server_summary_snapshot_payload(server_slug='comunidad-hispana-01')` +- `py_compile` sobre `backend/app/payloads.py`, `backend/app/routes.py` y `backend/app/historical_snapshot_storage.py` diff --git a/backend/README.md b/backend/README.md index 39409df..163e66b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -523,6 +523,10 @@ precalculados bajo `backend/data/snapshots/` y evita recalcular agregados pesados en cada request. Estos endpoints devuelven payloads ligeros listos para frontend con: +- `snapshot_status` +- `missing_reason` +- `request_path_policy` +- `generation_policy` - `generated_at` - `source_range_start` - `source_range_end` @@ -539,11 +543,12 @@ frontend con: - `previous_week_closed_matches` - `sufficient_sample` -Si un servidor ya tiene historico bruto en `historical_*` pero aun no conserva -el archivo precalculado correspondiente en `backend/data/snapshots/`, la API -intenta regenerar automaticamente el lote de snapshots de ese servidor antes de -responder. Esto evita que un servidor quede bloqueado en `found: false` por una -ausencia puntual de persistencia precalculada. +Si un snapshot todavia no existe en `backend/data/snapshots/`, la API responde +rapido con `found: false`, `snapshot_status: "missing"` y +`missing_reason: "snapshot-not-generated"`. La generacion y refresco de esos +artefactos debe ocurrir fuera del request path mediante `historical_ingestion` +o `historical_runner`; la lectura HTTP se mantiene como fast path de solo +lectura. `/api/historical/snapshots/server-summary` devuelve `item` con el resumen del servidor. `/api/historical/snapshots/weekly-leaderboard` devuelve `items` ya diff --git a/backend/app/payloads.py b/backend/app/payloads.py index 71bff6f..7080112 100644 --- a/backend/app/payloads.py +++ b/backend/app/payloads.py @@ -13,7 +13,6 @@ from .historical_snapshots import ( SNAPSHOT_TYPE_RECENT_MATCHES, SNAPSHOT_TYPE_SERVER_SUMMARY, SNAPSHOT_TYPE_WEEKLY_LEADERBOARD, - generate_and_persist_historical_snapshots, ) from .historical_storage import ( ALL_SERVERS_SLUG, @@ -465,21 +464,6 @@ def _get_historical_snapshot_record( ) -> dict[str, object] | None: if not server_key: return None - snapshot = get_historical_snapshot( - server_key=server_key, - snapshot_type=snapshot_type, - metric=metric, - window=window, - ) - if snapshot is not None: - return snapshot - - # Self-heal missing precomputed rows when raw historical data already exists. - try: - generate_and_persist_historical_snapshots(server_key=server_key) - except Exception: - return None - return get_historical_snapshot( server_key=server_key, snapshot_type=snapshot_type, @@ -491,6 +475,10 @@ def _get_historical_snapshot_record( def _build_historical_snapshot_metadata(snapshot: dict[str, object] | None) -> dict[str, object]: if snapshot is None: return { + "snapshot_status": "missing", + "missing_reason": "snapshot-not-generated", + "request_path_policy": "read-only-fast-path", + "generation_policy": "out-of-band-refresh-only", "generated_at": None, "source_range_start": None, "source_range_end": None, @@ -499,6 +487,10 @@ def _build_historical_snapshot_metadata(snapshot: dict[str, object] | None) -> d } is_stale = bool(snapshot.get("is_stale", False)) return { + "snapshot_status": "ready", + "missing_reason": None, + "request_path_policy": "read-only-fast-path", + "generation_policy": "out-of-band-refresh-only", "generated_at": snapshot.get("generated_at"), "source_range_start": snapshot.get("source_range_start"), "source_range_end": snapshot.get("source_range_end"),