From cabf4078530f02a5322867dde4740c5d8b560a7a Mon Sep 17 00:00:00 2001 From: devRaGonSa Date: Mon, 8 Jun 2026 12:38:05 +0200 Subject: [PATCH] Document annual ranking snapshot schema plan --- ...6-design-annual-ranking-snapshot-schema.md | 104 +++++++++ docs/annual-ranking-snapshot-schema-plan.md | 210 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md create mode 100644 docs/annual-ranking-snapshot-schema-plan.md diff --git a/ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md b/ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md new file mode 100644 index 0000000..f5f45e4 --- /dev/null +++ b/ai/tasks/done/TASK-166-design-annual-ranking-snapshot-schema.md @@ -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=&server_id=&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. diff --git a/docs/annual-ranking-snapshot-schema-plan.md b/docs/annual-ranking-snapshot-schema-plan.md new file mode 100644 index 0000000..f417da4 --- /dev/null +++ b/docs/annual-ranking-snapshot-schema-plan.md @@ -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=&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. +