Complete TASK-053 snapshot read fast path

This commit is contained in:
devRaGonSa
2026-03-23 13:46:09 +01:00
parent ef1a42fb80
commit 28a986f2a7
3 changed files with 92 additions and 21 deletions

View File

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

View File

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

View File

@@ -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"),