6.7 KiB
6.7 KiB
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_matchesyrcon_match_player_stats).
2. Propuesta de tablas
Se plantea un modelo de dos tablas:
rcon_annual_ranking_snapshotsrcon_annual_ranking_snapshot_items
rcon_annual_ranking_snapshots
Campos esperados:
idyearserver_keymetriclimit_sizesource_basiswindow_startwindow_endgenerated_atstatussource_matches_count
rcon_annual_ranking_snapshot_items
Campos esperados:
idsnapshot_idranking_positionplayer_idplayer_namemetric_valuematches_consideredkillsdeathsteamkillskd_ratio
3. Propuesta de DDL (convencional SQLite/Postgres)
SQLite
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->INTEGERTEXT->TEXTREAL->DOUBLE PRECISIONAUTOINCREMENT->BIGSERIAL PRIMARY KEYcon secuencia estándar.CURRENT_TIMESTAMPcon zona horaria (TIMESTAMPTZ) en la capa de compatibilidad.
4. Reglas de unicidad
- Snapshot único por combinación:
UNIQUE(year, server_key, metric).
- Posición única por snapshot:
UNIQUE(snapshot_id, ranking_position).
- 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 CONFLICTsobreyear + server_key + metric.
- borrado por
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 porPostgresCompatConnection).
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.pydentro deRCON_SCHEMA_SQLcuando corresponda. - Añadir a
RCON_TABLESenbackend/app/sqlite_to_postgres_migration.pysolo 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_existingseguro por:UNIQUE(year, server_key, metric)+ transacción de borrado e inserción,- o
ON CONFLICTenINSERT.
- 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:
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_keyusado 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(onot_found) con colección vacía. - sin recalcular on-demand (la carga debe dispararse por proceso dedicado).
- respuesta
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.