Add annual ranking snapshot storage
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: TASK-167
|
||||
title: Add annual ranking snapshot storage
|
||||
status: done
|
||||
type: backend
|
||||
team: Arquitecto de Base de Datos
|
||||
supporting_teams:
|
||||
- Arquitecto Python
|
||||
- Backend Senior
|
||||
roadmap_item: foundation
|
||||
---
|
||||
|
||||
# TASK-167 - Add annual ranking snapshot storage
|
||||
|
||||
## Goal
|
||||
|
||||
Implement storage primitives to persist annual top 20 player ranking snapshots, following the schema plan in `docs/annual-ranking-snapshot-schema-plan.md`, without adding generator logic or annual ranking APIs yet.
|
||||
|
||||
## Context
|
||||
|
||||
`TASK-166-design-annual-ranking-snapshot-schema.md` defined the intended schema and constraints for:
|
||||
|
||||
- `rcon_annual_ranking_snapshots`
|
||||
- `rcon_annual_ranking_snapshot_items`
|
||||
|
||||
This task creates the storage layer so future yearly ranking generators can run on top of materialized RCON data in SQLite and PostgreSQL-compatible paths.
|
||||
|
||||
The implementation must stay backend-only and must not touch frontend assets.
|
||||
|
||||
## Validation Scope
|
||||
|
||||
The work intentionally stayed limited to backend storage schema initialization and migration compatibility.
|
||||
|
||||
## Outcome
|
||||
|
||||
### Archivos modificados
|
||||
|
||||
- backend/app/rcon_admin_log_materialization.py
|
||||
- backend/app/postgres_rcon_storage.py
|
||||
- backend/app/sqlite_to_postgres_migration.py
|
||||
- ai/tasks/done/TASK-167-add-annual-ranking-snapshot-storage.md
|
||||
|
||||
### Tablas nuevas añadidas
|
||||
|
||||
- rcon_annual_ranking_snapshots
|
||||
- rcon_annual_ranking_snapshot_items
|
||||
|
||||
### Compatibilidad SQLite/Postgres
|
||||
|
||||
- SQLite: tablas y restricciones añadidas en `initialize_rcon_materialized_storage()` (backend/app/rcon_admin_log_materialization.py).
|
||||
- PostgreSQL: tablas, índices y restricciones añadidas a `RCON_SCHEMA_SQL` (backend/app/postgres_rcon_storage.py).
|
||||
- Migración: tablas añadidas a `RCON_TABLES` y a `_sync_sequences()` en `backend/app/sqlite_to_postgres_migration.py`.
|
||||
|
||||
### Validaciones realizadas
|
||||
|
||||
- `python -m compileall backend/app`
|
||||
- `scripts/run-integration-tests.ps1`
|
||||
- Validación de inicialización de storage SQLite con base temporal y consulta en `sqlite_master` para ambas tablas.
|
||||
|
||||
### Limitaciones conocidas
|
||||
|
||||
- No se implementó generador anual ni endpoint de ranking anual.
|
||||
- No se tocó frontend.
|
||||
- No hay validación de runtime PostgreSQL porque no había entorno Postgres requerido por el alcance de esta task.
|
||||
|
||||
### Próximo task recomendado
|
||||
|
||||
- TASK-168 (Generador de snapshot anual top 20).
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
id: TASK-167
|
||||
title: Add annual ranking snapshot storage
|
||||
status: pending
|
||||
type: backend
|
||||
team: Arquitecto de Base de Datos
|
||||
supporting_teams:
|
||||
- Arquitecto Python
|
||||
- Backend Senior
|
||||
roadmap_item: foundation
|
||||
---
|
||||
|
||||
# TASK-167 - Add annual ranking snapshot storage
|
||||
|
||||
## Goal
|
||||
|
||||
Implement storage primitives to persist annual top 20 player ranking snapshots, following the schema plan in `docs/annual-ranking-snapshot-schema-plan.md`, without adding generator logic or annual ranking APIs yet.
|
||||
|
||||
## Context
|
||||
|
||||
`TASK-166-design-annual-ranking-snapshot-schema.md` defined the intended schema and constraints for:
|
||||
- `rcon_annual_ranking_snapshots`
|
||||
- `rcon_annual_ranking_snapshot_items`
|
||||
|
||||
This task creates the storage layer so future yearly ranking generators can run on top of materialized RCON data in SQLite and PostgreSQL-compatible paths.
|
||||
|
||||
The implementation must stay backend-only and must not touch frontend assets.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read all files listed below before any edit.
|
||||
2. Add snapshot table creation for both SQLite and PostgreSQL-compatible schema paths if present.
|
||||
3. Include SQLite/Postgres-safe DDL in migration-like storage initializer surfaces used by the project.
|
||||
4. Keep changes minimal and avoid endpoint or generator creation.
|
||||
5. Validate syntax/initialization and keep changed files within expected scope.
|
||||
6. Update task outcome once completed and move to `ai/tasks/done` only after scope checks pass.
|
||||
|
||||
## Files to Read First
|
||||
|
||||
- `AGENTS.md`
|
||||
- `ai/repo-context.md`
|
||||
- `ai/architecture-index.md`
|
||||
- `docs/annual-ranking-snapshot-schema-plan.md`
|
||||
- `backend/app/rcon_admin_log_materialization.py`
|
||||
- `backend/app/postgres_rcon_storage.py`
|
||||
- `backend/app/sqlite_to_postgres_migration.py`
|
||||
- `backend/app/rcon_historical_leaderboards.py`
|
||||
- `backend/app/rcon_historical_player_stats.py`
|
||||
|
||||
## Expected Files to Modify
|
||||
|
||||
- `backend/app/rcon_admin_log_materialization.py` or equivalent materialized-RCON storage init module
|
||||
- `backend/app/postgres_rcon_storage.py` or equivalent PostgreSQL schema initializer
|
||||
- `backend/app/sqlite_to_postgres_migration.py` if migration table lists and sequence sync require alignment
|
||||
- `ai/tasks/done/TASK-167-add-annual-ranking-snapshot-storage.md`
|
||||
|
||||
## Constraints
|
||||
|
||||
- No generador anual de ranking en este scope.
|
||||
- No endpoint anual agregado aún.
|
||||
- No modificaciones de frontend.
|
||||
- No tocar `frontend/assets/js/partida-actual.js`.
|
||||
- No tocar `frontend/assets/img/clans/bxb.png`.
|
||||
- Mantener compatibilidad SQLite/Postgres si la capa existente ya la usa.
|
||||
- No reactivar Elo/MMR.
|
||||
- No reintroducir Comunidad Hispana #03.
|
||||
- No ampliar `historical workers` salvo cambios estrictamente necesarios en inicialización de storage.
|
||||
|
||||
## Validation
|
||||
|
||||
- `python -m compileall backend/app`
|
||||
- `scripts/run-integration-tests.ps1` (si aplica).
|
||||
- Validación de inicialización de storage para SQLite.
|
||||
- Validación de compatibilidad SQL/DDL con Postgres en los módulos de compatibilidad existentes.
|
||||
- `git diff --name-only` debe mostrar sólo el alcance esperado.
|
||||
|
||||
## Outcome
|
||||
|
||||
Documentar en el cierre de task:
|
||||
- Archivos modificados.
|
||||
- Tablas nuevas añadidas (`rcon_annual_ranking_snapshots`, `rcon_annual_ranking_snapshot_items`).
|
||||
- Compatibilidad SQLite/Postgres.
|
||||
- Validaciones ejecutadas.
|
||||
- Limitaciones conocidas.
|
||||
- Siguiente task recomendada: generador de snapshot anual top 20.
|
||||
|
||||
@@ -188,6 +188,39 @@ CREATE TABLE IF NOT EXISTS rcon_match_player_stats (
|
||||
UNIQUE(target_key, match_key, player_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rcon_annual_ranking_snapshots (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
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 TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
status TEXT NOT NULL DEFAULT 'ready',
|
||||
source_matches_count INTEGER NOT NULL DEFAULT 0,
|
||||
CHECK (limit_size > 0),
|
||||
CHECK (metric IN ('kills', 'deaths', 'matches_over_100_kills', 'support')),
|
||||
UNIQUE (year, server_key, metric)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rcon_annual_ranking_snapshot_items (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
snapshot_id BIGINT 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 BIGINT NOT NULL DEFAULT 0,
|
||||
matches_considered INTEGER NOT NULL DEFAULT 0,
|
||||
kills BIGINT NOT NULL DEFAULT 0,
|
||||
deaths BIGINT NOT NULL DEFAULT 0,
|
||||
teamkills BIGINT NOT NULL DEFAULT 0,
|
||||
kd_ratio DOUBLE PRECISION NOT NULL DEFAULT 0.0,
|
||||
UNIQUE(snapshot_id, ranking_position),
|
||||
UNIQUE(snapshot_id, player_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rcon_scoreboard_match_candidates (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
server_slug TEXT NOT NULL,
|
||||
@@ -219,6 +252,14 @@ CREATE INDEX IF NOT EXISTS idx_rcon_materialized_matches_recent
|
||||
ON rcon_materialized_matches(target_key, ended_at DESC, ended_server_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_match_player_stats_match
|
||||
ON rcon_match_player_stats(target_key, match_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_annual_ranking_snapshots_year
|
||||
ON rcon_annual_ranking_snapshots(year, server_key, metric);
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_annual_ranking_snapshots_status
|
||||
ON rcon_annual_ranking_snapshots(status);
|
||||
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);
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_scoreboard_candidates_server_end
|
||||
ON rcon_scoreboard_match_candidates(server_slug, ended_at DESC, started_at DESC);
|
||||
"""
|
||||
|
||||
@@ -83,6 +83,51 @@ def initialize_rcon_materialized_storage(*, db_path: Path | None = None) -> Path
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_match_player_stats_match
|
||||
ON rcon_match_player_stats(target_key, match_key);
|
||||
|
||||
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,
|
||||
CHECK (limit_size > 0),
|
||||
CHECK (metric IN ('kills', 'deaths', 'matches_over_100_kills', 'support')),
|
||||
UNIQUE (year, server_key, metric)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_annual_ranking_snapshots_year
|
||||
ON rcon_annual_ranking_snapshots(year, server_key, metric);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rcon_annual_ranking_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,
|
||||
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);
|
||||
"""
|
||||
)
|
||||
return resolved_path
|
||||
|
||||
@@ -28,6 +28,8 @@ RCON_TABLES = (
|
||||
"rcon_player_profile_snapshots",
|
||||
"rcon_materialized_matches",
|
||||
"rcon_match_player_stats",
|
||||
"rcon_annual_ranking_snapshots",
|
||||
"rcon_annual_ranking_snapshot_items",
|
||||
"rcon_scoreboard_match_candidates",
|
||||
)
|
||||
DISPLAY_TABLES = (
|
||||
@@ -315,6 +317,8 @@ def _sync_sequences() -> None:
|
||||
"rcon_player_profile_snapshots",
|
||||
"rcon_materialized_matches",
|
||||
"rcon_match_player_stats",
|
||||
"rcon_annual_ranking_snapshots",
|
||||
"rcon_annual_ranking_snapshot_items",
|
||||
"rcon_scoreboard_match_candidates",
|
||||
)
|
||||
with connect_display_postgres() as connection:
|
||||
|
||||
Reference in New Issue
Block a user