Document annual ranking snapshot schema plan
This commit is contained in:
104
ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md
Normal file
104
ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
id: TASK-166
|
||||||
|
title: Design annual ranking snapshot schema
|
||||||
|
status: done
|
||||||
|
type: documentation
|
||||||
|
team: Arquitecto de Base de Datos
|
||||||
|
supporting_teams: []
|
||||||
|
roadmap_item: foundation
|
||||||
|
priority: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
# TASK-166 - Design annual ranking snapshot schema
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Define a backend storage plan for annual top-20 ranking snapshots (kills) that avoids
|
||||||
|
full recalculation on each public request.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- TASK-162 implementó el endpoint de búsqueda de jugadores para Stats.
|
||||||
|
- TASK-163 implementó el endpoint de stats personales.
|
||||||
|
- TASK-165 implementó la vista frontend de Stats.
|
||||||
|
|
||||||
|
This task is documentation-only and prepares the schema needed by the next backend
|
||||||
|
endpoint iteration:
|
||||||
|
|
||||||
|
- `GET /api/stats/rankings/annual?year=<year>&server_id=<server-or-all>&metric=kills`
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Revisión de archivos listados en `Files to Read First`.
|
||||||
|
2. Diseñar propuesta de tablas para snapshots anuales y sus items.
|
||||||
|
3. Definir restricciones de unicidad, índices y política de reemplazo.
|
||||||
|
4. Documentar compatibilidad SQLite/Postgres siguiendo el patrón usado en `rcon_historical_leaderboards.py`.
|
||||||
|
5. Especificar API y estados esperados para lectura de snapshot.
|
||||||
|
|
||||||
|
## Files to Read First
|
||||||
|
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `ai/repo-context.md`
|
||||||
|
- `ai/architecture-index.md`
|
||||||
|
- `docs/stats-section-functional-plan.md`
|
||||||
|
- `docs/stats-frontend-integration-plan.md`
|
||||||
|
- `backend/app/rcon_historical_leaderboards.py`
|
||||||
|
- `backend/app/rcon_historical_player_stats.py`
|
||||||
|
- `backend/app/rcon_admin_log_materialization.py`
|
||||||
|
- `backend/app/postgres_rcon_storage.py`
|
||||||
|
- `backend/app/postgres_display_storage.py`
|
||||||
|
- `backend/app/sqlite_to_postgres_migration.py`
|
||||||
|
|
||||||
|
## Expected Files to Modify
|
||||||
|
|
||||||
|
- `ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md`
|
||||||
|
- `docs/annual-ranking-snapshot-schema-plan.md`
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- No migrations in this task.
|
||||||
|
- No backend implementation code changes in this task.
|
||||||
|
- No frontend changes in this task.
|
||||||
|
- No Elo/MMR reactivation.
|
||||||
|
- No Comunidad Hispana #03 reintroduction.
|
||||||
|
- No modification of historical workers.
|
||||||
|
- No touch to:
|
||||||
|
- `frontend/assets/js/partida-actual.js`
|
||||||
|
- `frontend/assets/img/clans/bxb.png`
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Confirm only documentation files were created/updated.
|
||||||
|
- `git diff --name-only` must match expected scope.
|
||||||
|
- Document that no automated tests are applicable for documentation-only task.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- Documento creado:
|
||||||
|
- `docs/annual-ranking-snapshot-schema-plan.md`
|
||||||
|
- Archivos revisados:
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `ai/repo-context.md`
|
||||||
|
- `ai/architecture-index.md`
|
||||||
|
- `docs/stats-section-functional-plan.md`
|
||||||
|
- `docs/stats-frontend-integration-plan.md`
|
||||||
|
- `backend/app/rcon_historical_leaderboards.py`
|
||||||
|
- `backend/app/rcon_historical_player_stats.py`
|
||||||
|
- `backend/app/rcon_admin_log_materialization.py`
|
||||||
|
- `backend/app/postgres_rcon_storage.py`
|
||||||
|
- `backend/app/postgres_display_storage.py`
|
||||||
|
- `backend/app/sqlite_to_postgres_migration.py`
|
||||||
|
- Validación realizada:
|
||||||
|
- Confirmado que la tarea quedó en alcance documental y no se introdujeron cambios de backend/frontend.
|
||||||
|
- Se validó que los únicos archivos esperados para esta tarea estén cubiertos por su documentación.
|
||||||
|
- No se requiere ejecutar pruebas automáticas: es una tarea de documentación.
|
||||||
|
- Limitaciones:
|
||||||
|
- No se tocó código backend ni frontend.
|
||||||
|
- No se ejecutaron migraciones.
|
||||||
|
- Siguiente task recomendada:
|
||||||
|
- Crear migración y estructura de persistencia para snapshots anuales de ranking (`rcon_annual_ranking_*`) y su job de generación.
|
||||||
|
|
||||||
|
## Change Budget
|
||||||
|
|
||||||
|
- Prefer fewer than 5 modified files.
|
||||||
|
- Keep changes concise and reviewable.
|
||||||
210
docs/annual-ranking-snapshot-schema-plan.md
Normal file
210
docs/annual-ranking-snapshot-schema-plan.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# Annual Ranking Snapshot Schema Plan
|
||||||
|
|
||||||
|
Este documento define el diseño de persistencia para snapshots anuales top 20.
|
||||||
|
|
||||||
|
## 1. Objetivo del snapshot anual
|
||||||
|
|
||||||
|
- Evitar recalcular ranking anual completo por request público.
|
||||||
|
- Guardar ranking precomputado por año/servidor/métrica para consumo O(1) en API.
|
||||||
|
- Mantener continuidad con el pipeline materializado RCON ya existente (`rcon_materialized_matches` y `rcon_match_player_stats`).
|
||||||
|
|
||||||
|
## 2. Propuesta de tablas
|
||||||
|
|
||||||
|
Se plantea un modelo de dos tablas:
|
||||||
|
|
||||||
|
- `rcon_annual_ranking_snapshots`
|
||||||
|
- `rcon_annual_ranking_snapshot_items`
|
||||||
|
|
||||||
|
### `rcon_annual_ranking_snapshots`
|
||||||
|
|
||||||
|
Campos esperados:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `year`
|
||||||
|
- `server_key`
|
||||||
|
- `metric`
|
||||||
|
- `limit_size`
|
||||||
|
- `source_basis`
|
||||||
|
- `window_start`
|
||||||
|
- `window_end`
|
||||||
|
- `generated_at`
|
||||||
|
- `status`
|
||||||
|
- `source_matches_count`
|
||||||
|
|
||||||
|
### `rcon_annual_ranking_snapshot_items`
|
||||||
|
|
||||||
|
Campos esperados:
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- `snapshot_id`
|
||||||
|
- `ranking_position`
|
||||||
|
- `player_id`
|
||||||
|
- `player_name`
|
||||||
|
- `metric_value`
|
||||||
|
- `matches_considered`
|
||||||
|
- `kills`
|
||||||
|
- `deaths`
|
||||||
|
- `teamkills`
|
||||||
|
- `kd_ratio`
|
||||||
|
|
||||||
|
## 3. Propuesta de DDL (convencional SQLite/Postgres)
|
||||||
|
|
||||||
|
### SQLite
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS rcon_annual_ranking_snapshots (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
server_key TEXT NOT NULL,
|
||||||
|
metric TEXT NOT NULL,
|
||||||
|
limit_size INTEGER NOT NULL DEFAULT 20,
|
||||||
|
source_basis TEXT NOT NULL DEFAULT 'rcon-admin-log',
|
||||||
|
window_start TEXT,
|
||||||
|
window_end TEXT,
|
||||||
|
generated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
status TEXT NOT NULL DEFAULT 'ready',
|
||||||
|
source_matches_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
source_range_start TEXT,
|
||||||
|
source_range_end TEXT,
|
||||||
|
source_payload_hash TEXT,
|
||||||
|
generation_policy TEXT NOT NULL DEFAULT 'manual',
|
||||||
|
notes TEXT,
|
||||||
|
UNIQUE (year, server_key, metric),
|
||||||
|
CHECK (limit_size > 0),
|
||||||
|
CHECK (metric IN ('kills', 'deaths', 'matches_over_100_kills', 'support'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rcon_annual_snapshots_year
|
||||||
|
ON rcon_annual_ranking_snapshots (year, server_key, metric);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rcon_annual_snapshots_status
|
||||||
|
ON rcon_annual_ranking_snapshots (status);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS rcon_annual_ranking_snapshot_items (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
snapshot_id INTEGER NOT NULL REFERENCES rcon_annual_ranking_snapshots(id) ON DELETE CASCADE,
|
||||||
|
ranking_position INTEGER NOT NULL,
|
||||||
|
player_id TEXT NOT NULL,
|
||||||
|
player_name TEXT NOT NULL,
|
||||||
|
metric_value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
matches_considered INTEGER NOT NULL DEFAULT 0,
|
||||||
|
kills INTEGER NOT NULL DEFAULT 0,
|
||||||
|
deaths INTEGER NOT NULL DEFAULT 0,
|
||||||
|
teamkills INTEGER NOT NULL DEFAULT 0,
|
||||||
|
kd_ratio REAL NOT NULL DEFAULT 0.0,
|
||||||
|
generated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE(snapshot_id, ranking_position),
|
||||||
|
UNIQUE(snapshot_id, player_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rcon_annual_snapshot_items_snapshot
|
||||||
|
ON rcon_annual_ranking_snapshot_items (snapshot_id, ranking_position);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rcon_annual_snapshot_items_player
|
||||||
|
ON rcon_annual_ranking_snapshot_items (snapshot_id, player_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### PostgreSQL (equivalente)
|
||||||
|
|
||||||
|
- `INTEGER` -> `INTEGER`
|
||||||
|
- `TEXT` -> `TEXT`
|
||||||
|
- `REAL` -> `DOUBLE PRECISION`
|
||||||
|
- `AUTOINCREMENT` -> `BIGSERIAL PRIMARY KEY` con secuencia estándar.
|
||||||
|
- `CURRENT_TIMESTAMP` con zona horaria (`TIMESTAMPTZ`) en la capa de compatibilidad.
|
||||||
|
|
||||||
|
## 4. Reglas de unicidad
|
||||||
|
|
||||||
|
1. Snapshot único por combinación:
|
||||||
|
- `UNIQUE(year, server_key, metric)`.
|
||||||
|
2. Posición única por snapshot:
|
||||||
|
- `UNIQUE(snapshot_id, ranking_position)`.
|
||||||
|
3. Jugador único por snapshot:
|
||||||
|
- `UNIQUE(snapshot_id, player_id)`.
|
||||||
|
|
||||||
|
### Comportamiento esperado
|
||||||
|
|
||||||
|
- Si cambia el ranking de un año ya existente, el proceso de generación debe reemplazarlo de forma idempotente.
|
||||||
|
- El reemplazo puede implementarse con:
|
||||||
|
- borrado por `snapshot_id` + inserción nueva, o
|
||||||
|
- `UPSERT`/`ON CONFLICT` sobre `year + server_key + metric`.
|
||||||
|
|
||||||
|
## 5. Índices recomendados
|
||||||
|
|
||||||
|
#### `rcon_annual_ranking_snapshots`
|
||||||
|
|
||||||
|
- `(year, server_key, metric)` para lookup directo por API.
|
||||||
|
- `(server_key, status)` para monitorización y estado de caducidad.
|
||||||
|
- `(status, generated_at)` para refrescos y auditoría.
|
||||||
|
|
||||||
|
#### `rcon_annual_ranking_snapshot_items`
|
||||||
|
|
||||||
|
- `(snapshot_id, ranking_position)` para orden fijo del ranking.
|
||||||
|
- `(snapshot_id, player_id)` para comprobaciones de idempotencia.
|
||||||
|
|
||||||
|
## 6. Compatibilidad SQLite/Postgres
|
||||||
|
|
||||||
|
La implementación futura debe seguir el patrón actual:
|
||||||
|
|
||||||
|
- Consultas materializadas con SQL ANSI/SQLite friendly.
|
||||||
|
- Aislar la conexión con la función de compatibilidad existente:
|
||||||
|
- `use_postgres_rcon_storage` + `connect_postgres_compat()`.
|
||||||
|
- Mantener placeholders por parámetro (`?` en SQL base, transformado por `PostgresCompatConnection`).
|
||||||
|
|
||||||
|
Esto evita duplicar lógica entre SQLite y Postgres y reduce riesgo al soportar ambas rutas.
|
||||||
|
|
||||||
|
### Ajuste de esquema en migración real (futuro)
|
||||||
|
|
||||||
|
- Incluir las tablas en `backend/app/postgres_rcon_storage.py` dentro de `RCON_SCHEMA_SQL` cuando corresponda.
|
||||||
|
- Añadir a `RCON_TABLES` en `backend/app/sqlite_to_postgres_migration.py` solo en fase de migración real.
|
||||||
|
|
||||||
|
## 7. Política de generación
|
||||||
|
|
||||||
|
### Inicial (ahora)
|
||||||
|
- Generación **manual**:
|
||||||
|
- comando interno o mantenimiento administrativo para producir snapshot anual.
|
||||||
|
- Semántica de reemplazo:
|
||||||
|
- `replace_existing` seguro por:
|
||||||
|
- `UNIQUE(year, server_key, metric)` + transacción de borrado e inserción,
|
||||||
|
- o `ON CONFLICT` en `INSERT`.
|
||||||
|
- Registrar:
|
||||||
|
- `generated_at`, `status`, `source_matches_count`, `source_basis`.
|
||||||
|
|
||||||
|
### Evolución
|
||||||
|
- Generación programada futura (cron/job) con ventana anual cerrada.
|
||||||
|
- Mantener snapshots por año histórico para inspección.
|
||||||
|
|
||||||
|
## 8. API futura
|
||||||
|
|
||||||
|
Ruta definida para la siguiente implementación:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/stats/rankings/annual?year=<year>&server_id=<server-or-all>&metric=kills
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parámetros
|
||||||
|
|
||||||
|
- `year` (obligatorio): año calendario (ej. `2026`).
|
||||||
|
- `server_id`:
|
||||||
|
- `all-servers` (default) o slug de servidor.
|
||||||
|
- Debe mapearse al mismo `server_key` usado por RCON materialized.
|
||||||
|
- `metric` (obligatorio en la fase inicial): `kills`.
|
||||||
|
|
||||||
|
### Contrato recomendado
|
||||||
|
|
||||||
|
- `status: ok | pending | stale | not_found`.
|
||||||
|
- `data`:
|
||||||
|
- `year`, `server_key`, `metric`, `generated_at`, `status`, `source_range_start`, `source_range_end`.
|
||||||
|
- `items`: lista de items del ranking con los campos de la tabla de items.
|
||||||
|
- Si no hay snapshot generado:
|
||||||
|
- respuesta `pending` (o `not_found`) con colección vacía.
|
||||||
|
- sin recalcular on-demand (la carga debe dispararse por proceso dedicado).
|
||||||
|
|
||||||
|
## 9. Notas de alcance y migración
|
||||||
|
|
||||||
|
- Esta tarea es de diseño documental únicamente, no incluye migraciones ni cambios de código.
|
||||||
|
- No introduce Elo/MMR.
|
||||||
|
- No se añade lógica de frontend.
|
||||||
|
- No se modifica `frontend/assets/js/partida-actual.js`.
|
||||||
|
- No se toca `frontend/assets/img/clans/bxb.png`.
|
||||||
|
- No se reintroduce Comunidad Hispana #03.
|
||||||
|
|
||||||
Reference in New Issue
Block a user