Complete TASK-053 snapshot read fast path
This commit is contained in:
@@ -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`
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user