# 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=&server_id=&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.