Fix
This commit is contained in:
223
docs/crcon-advanced-metrics-origin-audit.md
Normal file
223
docs/crcon-advanced-metrics-origin-audit.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# CRCON Advanced Metrics Origin Audit
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-24
|
||||
|
||||
## Scope
|
||||
|
||||
Auditoria tecnica del origen probable de metricas avanzadas visibles en
|
||||
ecosistemas tipo CRCON / HLL Records, separando:
|
||||
|
||||
- RCON directo implementado hoy en esta repo
|
||||
- campos historicos ya visibles en la capa publica tipo scoreboard
|
||||
- metricas que solo resultan plausibles con eventos/logs y agregacion propia
|
||||
|
||||
No se implementa captura nueva, tablas nuevas ni cambios de producto.
|
||||
|
||||
## Evidence Reviewed
|
||||
|
||||
- `docs/rcon-data-capability-audit.md`
|
||||
- `docs/monthly-player-ranking-data-audit.md`
|
||||
- `docs/historical-crcon-source-discovery.md`
|
||||
- `backend/README.md`
|
||||
- `backend/app/rcon_client.py`
|
||||
- `backend/app/providers/rcon_provider.py`
|
||||
- `backend/app/data_sources.py`
|
||||
|
||||
## Confirmed Boundary In This Repository
|
||||
|
||||
La evidencia local confirma dos superficies distintas:
|
||||
|
||||
- RCON live directo para estado actual del servidor
|
||||
- historico CRCON / scoreboard publico para partidas cerradas y metricas ricas
|
||||
|
||||
El cliente RCON implementado en `backend/app/rcon_client.py` solo usa:
|
||||
|
||||
- `ServerConnect`
|
||||
- `Login`
|
||||
- `GetServerInformation`
|
||||
|
||||
El proveedor `RconLiveDataSource` solo convierte eso en:
|
||||
|
||||
- nombre del servidor
|
||||
- estado online
|
||||
- jugadores actuales
|
||||
- capacidad maxima
|
||||
- mapa actual
|
||||
- metadata de procedencia del snapshot
|
||||
|
||||
La repo no contiene hoy evidencia de comandos RCON integrados para:
|
||||
|
||||
- killer -> victim
|
||||
- kills por arma
|
||||
- teamkills por evento
|
||||
- duelos jugador contra jugador
|
||||
- ledger tactico de acciones
|
||||
- reconstruccion historica de partidas cerradas
|
||||
|
||||
## What The Historical Source Already Exposes
|
||||
|
||||
La discovery historica local ya documenta que el detalle CRCON / scoreboard
|
||||
publico expone campos avanzados como:
|
||||
|
||||
- `kills_by_type`
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- `weapons`
|
||||
- `death_by_weapons`
|
||||
|
||||
Ademas, `docs/monthly-player-ranking-data-audit.md` confirma que esos campos
|
||||
existen en el origen, aunque la persistencia actual del proyecto todavia no los
|
||||
guarda.
|
||||
|
||||
## Technical Interpretation
|
||||
|
||||
La mejor lectura tecnica basada en la repo es esta:
|
||||
|
||||
- RCON puro hoy solo cubre estado live operativo
|
||||
- metricas como `most_killed` y `death_by` no salen del cliente RCON actual
|
||||
- esas metricas ya existen en una capa historica enriquecida externa al cliente
|
||||
RCON local
|
||||
- para reproducirlas dentro del proyecto haria falta una persistencia propia o
|
||||
una fuente historica equivalente que conserve eventos o agregados avanzados
|
||||
|
||||
Esto no demuestra por si solo el mecanismo interno exacto de CRCON o HLL
|
||||
Records, pero si permite descartar algo importante: en esta repo no hay base
|
||||
para afirmar que esas metricas provengan de RCON directo ya listo para usar.
|
||||
|
||||
## Plausible Origin Paths
|
||||
|
||||
### 1. Direct RCON Commands
|
||||
|
||||
Plausibilidad en esta repo: baja para metricas avanzadas.
|
||||
|
||||
Motivo:
|
||||
|
||||
- no hay comandos RCON avanzados integrados en codigo
|
||||
- no hay provider historico RCON operativo
|
||||
- `RconHistoricalDataSource` es solo un placeholder que falla con
|
||||
`Historical RCON provider is not implemented yet.`
|
||||
|
||||
Conclusion:
|
||||
|
||||
- RCON directo es plausible para live state
|
||||
- no hay evidencia local suficiente para atribuirle `most_killed`,
|
||||
`death_by`, killer/victim o kills por arma
|
||||
|
||||
### 2. Event Stream Or Server Logs
|
||||
|
||||
Plausibilidad en esta repo: alta como origen tecnico necesario si el proyecto
|
||||
quisiera reconstruir esas metricas por cuenta propia.
|
||||
|
||||
Motivo:
|
||||
|
||||
- killer/victim requiere granularidad por evento o al menos por encounter
|
||||
- kills por arma requieren capturar el arma asociada al kill
|
||||
- teamkills por evento requieren distinguir el evento individual
|
||||
- clasificaciones como infantry / tank / artillery requieren una senal por tipo
|
||||
de kill o contexto del evento
|
||||
|
||||
Conclusion:
|
||||
|
||||
- para producir estas metricas dentro de HLL Vietnam, un pipeline de eventos o
|
||||
logs es la hipotesis tecnica mas consistente
|
||||
|
||||
### 3. CRCON Internal Storage / Enriched Aggregation
|
||||
|
||||
Plausibilidad en esta repo: alta para explicar lo que ya se observa en el
|
||||
scoreboard publico.
|
||||
|
||||
Motivo:
|
||||
|
||||
- la fuente publica ya devuelve campos agregados que el proyecto no calcula
|
||||
- esos campos no se derivan del snapshot live RCON implementado hoy
|
||||
- `most_killed` y `death_by` parecen vistas agregadas de encounters, no simples
|
||||
contadores live del servidor
|
||||
|
||||
Conclusion:
|
||||
|
||||
- CRCON / HLL Records probablemente sirve esos campos desde una capa historica
|
||||
propia ya enriquecida y persistida, no desde la llamada live minima que esta
|
||||
repo usa por RCON
|
||||
|
||||
## Origin Matrix By Metric
|
||||
|
||||
| Metrica | RCON directo hoy en esta repo | Requiere eventos/logs para reproducirla | Requiere agregacion/persistencia propia | Origen probable segun evidencia local |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Estado live del servidor | Si | No | No | RCON directo |
|
||||
| Jugadores actuales | Si | No | No | RCON directo |
|
||||
| Mapa actual | Si | No | No | RCON directo |
|
||||
| Scoreboard live basico por jugador | No confirmado | Posiblemente no siempre | Posiblemente no | No confirmado en la repo |
|
||||
| `most_killed` | No | Si o fuente historica equivalente | Si | Capa historica enriquecida |
|
||||
| `death_by` | No | Si o fuente historica equivalente | Si | Capa historica enriquecida |
|
||||
| killer -> victim | No | Si | Si | Eventos/logs + persistencia |
|
||||
| kills por arma | No | Si | Si | Eventos/logs + persistencia |
|
||||
| `kills_by_type` | No | Si | Si | Eventos/logs + persistencia |
|
||||
| `death_by_weapons` | No | Si | Si | Eventos/logs + persistencia |
|
||||
| teamkills por evento | No | Si | Si | Eventos/logs + persistencia |
|
||||
| teamkills agregados historicos | No desde RCON actual | Si | Si | Agregacion historica |
|
||||
| duelos reutilizables | No | Si | Si | Eventos/logs + persistencia |
|
||||
| distincion infantry / tank / artillery | No | Si | Si | Eventos/logs + clasificacion propia |
|
||||
| acciones tacticas finas | No confirmadas | Si | Si | No confirmadas, pero no salen del RCON actual |
|
||||
|
||||
## What RCON Purely Can Plausibly Provide
|
||||
|
||||
Con evidencia local, RCON puro queda limitado a:
|
||||
|
||||
- estado actual del servidor
|
||||
- jugadores presentes
|
||||
- capacidad maxima
|
||||
- mapa actual
|
||||
- metadata live util para un panel operativo
|
||||
|
||||
Eso sirve para monitoreo live, no para un MVP mensual V2 con rivalidades,
|
||||
armas, killers, victims o taxonomias tacticas.
|
||||
|
||||
## What Seems To Require Event Capture Or Logs
|
||||
|
||||
Las metricas siguientes solo son defendibles si el proyecto capta eventos o
|
||||
logs con granularidad suficiente:
|
||||
|
||||
- killer -> victim
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- kills por arma
|
||||
- `kills_by_type`
|
||||
- `death_by_weapons`
|
||||
- teamkills por evento
|
||||
- segmentacion infantry / tank / artillery
|
||||
|
||||
La razon comun es que todas dependen de relaciones o atributos de eventos
|
||||
individuales, no solo de un snapshot agregado del servidor.
|
||||
|
||||
## What Seems To Require Historical Aggregation
|
||||
|
||||
Incluso con eventos capturados, haria falta una capa propia de persistencia y
|
||||
agregacion para exponer de forma estable:
|
||||
|
||||
- rivales mas frecuentes
|
||||
- resumen `most_killed`
|
||||
- resumen `death_by`
|
||||
- perfiles de armas por jugador
|
||||
- acumulados mensuales auditables por servidor
|
||||
|
||||
Sin esa capa, la señal estaria dispersa en eventos crudos y no seria operativa
|
||||
para un ranking MVP V2.
|
||||
|
||||
## Final Conclusion
|
||||
|
||||
La conclusion mas solida que soporta esta repo es:
|
||||
|
||||
- `most_killed`, `death_by`, killer/victim y kills por arma no salen del RCON
|
||||
directo implementado hoy
|
||||
- esas metricas ya son visibles en una fuente historica enriquecida externa al
|
||||
cliente RCON local
|
||||
- para reproducirlas dentro del proyecto haria falta una canalizacion nueva de
|
||||
eventos/logs y una persistencia historica propia con agregados derivados
|
||||
|
||||
## Recommended Follow-Up
|
||||
|
||||
La siguiente task tecnica correcta es disenar el pipeline minimo de eventos de
|
||||
jugador necesario para alimentar una V2 del ranking mensual sin asumir que RCON
|
||||
directo ya entrega esas metricas listas.
|
||||
130
docs/current-hll-data-ingestion-plan.md
Normal file
130
docs/current-hll-data-ingestion-plan.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Current HLL Data Ingestion Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Definir una estrategia tecnica reutilizable para ingerir datos del Hell Let
|
||||
Loose actual como banco de pruebas del futuro ecosistema HLL Vietnam, sin
|
||||
implementar todavia una ingesta productiva completa.
|
||||
|
||||
## Initial Data Scope
|
||||
|
||||
Los primeros campos a capturar deben cubrir el bloque provisional de
|
||||
servidores y preparar historicos minimos:
|
||||
|
||||
- `server_name`
|
||||
- `status`
|
||||
- `players`
|
||||
- `max_players`
|
||||
- `current_map` si la fuente lo permite
|
||||
- `captured_at`
|
||||
- `source`
|
||||
- `external_server_id` o identificador equivalente si la fuente lo ofrece
|
||||
|
||||
Campos como `queue`, `ping`, `rotation` o `notes` quedan como opcionales para
|
||||
fases posteriores y no deben bloquear el bootstrap.
|
||||
|
||||
## Snapshot Concept
|
||||
|
||||
Un snapshot representa el estado observado de un servidor en un momento
|
||||
concreto. No es un perfil estatico del servidor, sino una captura puntual con
|
||||
timestamp.
|
||||
|
||||
Cada snapshot debe permitir:
|
||||
|
||||
- reconstruir una serie temporal simple por servidor
|
||||
- detectar cambios de estado online u offline
|
||||
- medir evolucion basica de jugadores y capacidad
|
||||
- conservar la procedencia de la captura
|
||||
|
||||
El identificador estable del servidor y el `captured_at` deben separar la
|
||||
identidad del servidor de cada observacion historica.
|
||||
|
||||
## Ingestion Source Options
|
||||
|
||||
### Phase-safe controlled payload
|
||||
|
||||
- Fuente recomendada para el inicio.
|
||||
- Permite probar el pipeline con datos mock o manuales servidos por backend.
|
||||
- Fija el contrato de entrada y la normalizacion sin depender de terceros.
|
||||
|
||||
### Public external source
|
||||
|
||||
- Puede ser una API publica o un listado mantenido por terceros.
|
||||
- Acerca el banco de pruebas a datos reales.
|
||||
- Exige validar formato, disponibilidad, limites de uso y estabilidad antes de
|
||||
consolidarlo.
|
||||
|
||||
### Direct server query or intermediary adapter
|
||||
|
||||
- Puede ofrecer datos mas cercanos al estado real del servidor.
|
||||
- Introduce mayor complejidad tecnica, posibles timeouts y dependencia del
|
||||
protocolo soportado.
|
||||
- Debe encapsularse detras de un adaptador backend, no exponerse al frontend.
|
||||
|
||||
## Normalization Baseline
|
||||
|
||||
La captura y la fuente no deben definir el contrato interno final. La
|
||||
arquitectura debe separar:
|
||||
|
||||
1. lectura de datos crudos
|
||||
2. normalizacion a un modelo comun
|
||||
3. produccion de snapshots consistentes
|
||||
|
||||
La normalizacion inicial debe garantizar:
|
||||
|
||||
- naming estable en `snake_case`
|
||||
- `status` reducido a valores controlados como `online`, `offline` o `unknown`
|
||||
- enteros para `players` y `max_players` cuando existan
|
||||
- `captured_at` generado en backend
|
||||
- conservacion del nombre de fuente para trazabilidad
|
||||
|
||||
## Risks And Limits
|
||||
|
||||
- Disponibilidad de terceros: una fuente publica puede dejar de responder sin
|
||||
aviso.
|
||||
- Cambios de formato: scraping o APIs no oficiales pueden romper el adaptador.
|
||||
- Rate limits: las consultas frecuentes pueden exigir cache o polling mas
|
||||
espaciado.
|
||||
- Latencia: una consulta lenta no debe trasladarse directamente al frontend.
|
||||
- CORS: el frontend no debe llamar a fuentes externas para este flujo.
|
||||
- Fiabilidad: diferentes fuentes pueden discrepar en jugadores, mapa o estado.
|
||||
- Dependencia no oficial: una integracion fragil no debe convertirse en pieza
|
||||
critica del producto.
|
||||
|
||||
## Phased Architecture
|
||||
|
||||
### Phase 1: controlled payload and stable structure
|
||||
|
||||
- Mantener un payload controlado como base de `/api/servers`.
|
||||
- Definir el modelo normalizado esperado para servidores y snapshots.
|
||||
- No almacenar historico real todavia.
|
||||
|
||||
### Phase 2: snapshot collector with real or near-real source
|
||||
|
||||
- Introducir un colector backend desacoplado de la fuente concreta.
|
||||
- Permitir ejecucion manual o periodica en entorno de desarrollo.
|
||||
- Generar snapshots consistentes listos para futura persistencia.
|
||||
|
||||
### Phase 3: historical use and basic statistics
|
||||
|
||||
- Persistir snapshots.
|
||||
- Calcular metricas iniciales como actividad por servidor, picos de jugadores o
|
||||
ultima vez visto online.
|
||||
- Mantener el modelo generico para reutilizarlo con HLL Vietnam cuando existan
|
||||
datos mas representativos.
|
||||
|
||||
## Explicitly Out Of Scope Now
|
||||
|
||||
- ingesta real completa en produccion
|
||||
- scraping productivo
|
||||
- base de datos funcional
|
||||
- tareas periodicas operativas
|
||||
- metricas avanzadas o paneles analiticos
|
||||
- cambios visibles en frontend
|
||||
|
||||
## Handoff To Following Tasks
|
||||
|
||||
- `TASK-019` debe convertir este plan en una base de esquema para persistir
|
||||
servidores y snapshots.
|
||||
- `TASK-020` debe preparar un bootstrap pequeno del colector en Python con
|
||||
separacion entre fuente, normalizacion y snapshot.
|
||||
130
docs/current-hll-servers-source-plan.md
Normal file
130
docs/current-hll-servers-source-plan.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Current HLL Servers Source Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Definir como mostrar en la web de HLL Vietnam un bloque provisional con
|
||||
servidores actuales de Hell Let Loose sin presentarlos como si fueran datos de
|
||||
HLL Vietnam ni depender todavia de una integracion real externa.
|
||||
|
||||
## Product Framing
|
||||
|
||||
- El bloque debe presentarse como referencia provisional para la comunidad.
|
||||
- El copy debe mencionar de forma explicita "servidores actuales de Hell Let
|
||||
Loose" y evitar formulas ambiguas como "servidores HLL Vietnam".
|
||||
- La UI debe dejar claro que el bloque sirve mientras no existan datos propios o
|
||||
mas cercanos al contexto final de HLL Vietnam.
|
||||
- Si no hay datos disponibles, el estado vacio debe ser neutral y honesto, sin
|
||||
simular actividad inexistente.
|
||||
|
||||
## Recommended Fields For This Phase
|
||||
|
||||
Campos utiles para un bloque pequeno y entendible:
|
||||
|
||||
- `server_name`
|
||||
- `status`
|
||||
- `players`
|
||||
- `max_players`
|
||||
- `current_map`
|
||||
- `region`
|
||||
|
||||
Campos opcionales solo si una fuente futura los ofrece de forma estable:
|
||||
|
||||
- `queue`
|
||||
- `ping`
|
||||
- `notes`
|
||||
- `last_updated`
|
||||
|
||||
## Source Options
|
||||
|
||||
### Public external source
|
||||
|
||||
- Puede ser una API publica especializada, un listado publico o una consulta de
|
||||
servidor compatible con el juego actual.
|
||||
- Ventaja: acerca la web a datos mas reales.
|
||||
- Riesgo: cambios de formato, limites de uso, CORS, disponibilidad y dependencia
|
||||
de terceros.
|
||||
|
||||
### Controlled placeholder data
|
||||
|
||||
- Fuente recomendada para la primera implementacion.
|
||||
- El backend expone un payload manual con forma realista y semantica estable.
|
||||
- Permite validar UI, contrato y estados de error sin acoplar la web a una
|
||||
fuente externa todavia no validada.
|
||||
|
||||
### Stronger future integration
|
||||
|
||||
- Un adaptador backend dedicado podra sustituir el placeholder cuando exista una
|
||||
fuente fiable o un dataset controlado mantenido por la comunidad.
|
||||
- La sustitucion debe preservar el contrato JSON para no romper al frontend.
|
||||
|
||||
## Risks And Restrictions
|
||||
|
||||
- Disponibilidad: una fuente externa puede caer o degradarse sin aviso.
|
||||
- CORS: el frontend no debe depender de llamadas directas a terceros.
|
||||
- Rate limits: una API publica puede limitar frecuencia o volumen.
|
||||
- Formato: scraping o endpoints no oficiales pueden cambiar sin contrato.
|
||||
- Mantenimiento: una integracion fragil crearia coste operativo prematuro.
|
||||
- Identidad: el bloque no puede inducir a pensar que HLL Vietnam ya dispone de
|
||||
servidores propios o datos oficiales.
|
||||
|
||||
## Phased Strategy
|
||||
|
||||
### Phase 1: controlled mock
|
||||
|
||||
- `GET /api/servers` devuelve datos manuales con estructura estable.
|
||||
- El payload debe incluir una marca de contexto provisional para indicar que los
|
||||
datos pertenecen al HLL actual.
|
||||
- La landing puede consumir el endpoint con fallback local si el backend no esta
|
||||
disponible.
|
||||
|
||||
### Phase 2: backend adapter
|
||||
|
||||
- Sustituir el mock por un adaptador backend desacoplado de la fuente concreta.
|
||||
- Mantener el mismo contrato principal de `items`.
|
||||
- Introducir validacion basica de campos y fallback controlado si falla la
|
||||
fuente.
|
||||
|
||||
### Phase 3: replacement toward HLL Vietnam
|
||||
|
||||
- Reemplazar o mezclar progresivamente el bloque cuando existan datos mas
|
||||
representativos del contexto HLL Vietnam.
|
||||
- Revisar naming, copy y campos para no arrastrar supuestos del juego actual.
|
||||
|
||||
## Explicitly Out Of Scope Now
|
||||
|
||||
- Integrar una fuente externa real.
|
||||
- Hacer scraping.
|
||||
- Consultar servidores reales desde el frontend.
|
||||
- Anadir base de datos, cache o panel administrativo.
|
||||
- Presentar el bloque como caracteristica definitiva del producto.
|
||||
|
||||
## Recommended Contract Shape
|
||||
|
||||
Ejemplo minimo de respuesta provisional:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Servidores actuales de Hell Let Loose",
|
||||
"context": "current-hll-reference",
|
||||
"source": "controlled-placeholder",
|
||||
"items": [
|
||||
{
|
||||
"server_name": "HLL ESP Tactical Rotation",
|
||||
"status": "online",
|
||||
"players": 74,
|
||||
"max_players": 100,
|
||||
"current_map": "Sainte-Marie-du-Mont",
|
||||
"region": "EU"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Handoff To Following Tasks
|
||||
|
||||
- Backend task: preparar el adaptador placeholder estable sobre este contrato.
|
||||
- Frontend task: anadir un panel visual sobrio con etiqueta provisional y
|
||||
fallback seguro si el endpoint falla o no devuelve items.
|
||||
307
docs/database-maintenance.md
Normal file
307
docs/database-maintenance.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# Database Maintenance
|
||||
|
||||
## Overview
|
||||
|
||||
HLL Vietnam keeps database cleanup at the application level.
|
||||
|
||||
The current maintenance scope is intentionally narrow:
|
||||
|
||||
- old `server_snapshots`;
|
||||
- old non-critical `rcon_admin_log_events`;
|
||||
- old critical `rcon_admin_log_events` only after retention and protected-match checks;
|
||||
- old non-protected `rcon_materialized_matches`;
|
||||
- dependent `rcon_match_player_stats` for deleted matches.
|
||||
|
||||
The first maintenance pass does not routinely delete:
|
||||
|
||||
- `displayed_historical_snapshots`;
|
||||
- file-based snapshots under `backend/data/snapshots/`;
|
||||
- public-scoreboard `historical_*` fallback tables;
|
||||
- `player_event_raw_ledger` and its worker metadata;
|
||||
- Elo/MMR tables;
|
||||
- Comunidad Hispana #03 data reactivation or targets.
|
||||
|
||||
## Why Application-Level And Not `pg_cron`
|
||||
|
||||
Cleanup is versioned in backend code instead of delegated to `pg_cron`, host cron, or a separate container because the retention logic depends on product rules:
|
||||
|
||||
- keep the latest 100 closed materialized matches;
|
||||
- keep the current month;
|
||||
- keep the previous month during the first 7 days of a new month;
|
||||
- keep the current week;
|
||||
- keep the previous week when weekly fallback may still need it;
|
||||
- keep child stats for protected matches;
|
||||
- avoid breaking current/live pages that still read recent AdminLog data.
|
||||
|
||||
Those rules belong with the application’s read and write model, not inside database-only scheduling.
|
||||
|
||||
## Scheduled Cleanup Inside `historical-runner`
|
||||
|
||||
Database maintenance is scheduled inside `app.historical_runner`.
|
||||
|
||||
Behavior:
|
||||
|
||||
- disabled by default;
|
||||
- no extra Docker service is added for maintenance;
|
||||
- the runner checks whether maintenance is due;
|
||||
- when enabled and due, the runner invokes `python -m app.database_maintenance cleanup --apply` behavior through the shared Python function;
|
||||
- failures are logged and do not crash the historical runner loop;
|
||||
- cleanup runs under the same writer-lock coordination used by the historical writer flows.
|
||||
|
||||
Relevant structured log events:
|
||||
|
||||
- `database-maintenance-scheduler-skipped-disabled`
|
||||
- `database-maintenance-scheduler-skipped-not-due`
|
||||
- `database-maintenance-scheduler-started`
|
||||
- `database-maintenance-scheduler-completed`
|
||||
- `database-maintenance-scheduler-failed`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Required maintenance-related variables:
|
||||
|
||||
```text
|
||||
HLL_DB_MAINTENANCE_ENABLED=false
|
||||
HLL_DB_MAINTENANCE_INTERVAL_SECONDS=43200
|
||||
HLL_RECENT_MATCHES_KEEP=100
|
||||
HLL_ADMIN_LOG_NONCRITICAL_RETENTION_DAYS=30
|
||||
HLL_ADMIN_LOG_CRITICAL_RETENTION_DAYS=90
|
||||
HLL_SERVER_SNAPSHOT_RETENTION_DAYS=14
|
||||
HLL_DB_MAINTENANCE_BATCH_SIZE=5000
|
||||
```
|
||||
|
||||
Meaning:
|
||||
|
||||
- `HLL_DB_MAINTENANCE_ENABLED`
|
||||
Enables scheduled apply mode inside `historical-runner`.
|
||||
- `HLL_DB_MAINTENANCE_INTERVAL_SECONDS`
|
||||
Default scheduler interval. `43200` means every 12 hours.
|
||||
- `HLL_RECENT_MATCHES_KEEP`
|
||||
Number of latest closed materialized matches that must always be protected.
|
||||
- `HLL_ADMIN_LOG_NONCRITICAL_RETENTION_DAYS`
|
||||
Retention for non-critical AdminLog events such as chat/connect/disconnect.
|
||||
- `HLL_ADMIN_LOG_CRITICAL_RETENTION_DAYS`
|
||||
Retention for critical AdminLog events such as `kill`, `match_start`, `match_end`.
|
||||
- `HLL_SERVER_SNAPSHOT_RETENTION_DAYS`
|
||||
Retention for live server snapshots.
|
||||
- `HLL_DB_MAINTENANCE_BATCH_SIZE`
|
||||
Delete batch size for apply mode.
|
||||
|
||||
## Protected Data
|
||||
|
||||
The cleanup command protects:
|
||||
|
||||
- latest 100 closed materialized matches by default;
|
||||
- current month materialized matches;
|
||||
- previous month materialized matches when the current day is `1` through `7`;
|
||||
- current week materialized matches;
|
||||
- previous week materialized matches when weekly fallback may still need them;
|
||||
- `rcon_match_player_stats` belonging to protected matches;
|
||||
- current/live AdminLog data required for visible current-match surfaces;
|
||||
- `displayed_historical_snapshots`;
|
||||
- file snapshots in `backend/data/snapshots/`.
|
||||
|
||||
If a match timestamp cannot be interpreted safely, that match is skipped and protected instead of deleted.
|
||||
|
||||
## Deleted Data
|
||||
|
||||
Apply mode is currently allowed to delete:
|
||||
|
||||
- `server_snapshots` older than retention;
|
||||
- non-critical `rcon_admin_log_events` older than retention;
|
||||
- critical `rcon_admin_log_events` older than retention only when they are not required by protected materialized match ranges;
|
||||
- non-protected `rcon_materialized_matches`;
|
||||
- dependent `rcon_match_player_stats` for deleted matches.
|
||||
|
||||
Current critical AdminLog event types:
|
||||
|
||||
- `kill`
|
||||
- `match_start`
|
||||
- `match_end`
|
||||
|
||||
## Dry-Run Command
|
||||
|
||||
From `backend/`:
|
||||
|
||||
```powershell
|
||||
python -m app.database_maintenance cleanup --dry-run
|
||||
```
|
||||
|
||||
From the repository root with the backend package on `PYTHONPATH`:
|
||||
|
||||
```powershell
|
||||
$env:PYTHONPATH='backend'
|
||||
python -m app.database_maintenance cleanup --dry-run
|
||||
```
|
||||
|
||||
Inside Docker Compose:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.database_maintenance cleanup --dry-run
|
||||
```
|
||||
|
||||
Useful dry-run options:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.database_maintenance cleanup --dry-run `
|
||||
--recent-matches-keep 100 `
|
||||
--admin-log-noncritical-retention-days 30 `
|
||||
--admin-log-critical-retention-days 90 `
|
||||
--server-snapshot-retention-days 14 `
|
||||
--batch-size 5000
|
||||
```
|
||||
|
||||
Dry-run is the safe preview path and should be reviewed before any production apply.
|
||||
|
||||
## Apply Command
|
||||
|
||||
Local module execution:
|
||||
|
||||
```powershell
|
||||
python -m app.database_maintenance cleanup --apply
|
||||
```
|
||||
|
||||
Docker Compose:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.database_maintenance cleanup --apply
|
||||
```
|
||||
|
||||
One-off local validation with a fixed time anchor:
|
||||
|
||||
```powershell
|
||||
python -m app.database_maintenance cleanup --apply --now 2026-06-20T12:00:00Z
|
||||
```
|
||||
|
||||
Optional maintenance vacuum/analyze:
|
||||
|
||||
```powershell
|
||||
python -m app.database_maintenance cleanup --apply --vacuum-analyze
|
||||
```
|
||||
|
||||
## Table-Size Audit SQL
|
||||
|
||||
```sql
|
||||
select
|
||||
schemaname,
|
||||
relname as table_name,
|
||||
pg_size_pretty(pg_total_relation_size(relid)) as total_size,
|
||||
pg_size_pretty(pg_relation_size(relid)) as table_size,
|
||||
pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) as indexes_size,
|
||||
n_live_tup as estimated_rows,
|
||||
n_dead_tup as estimated_dead_rows
|
||||
from pg_stat_user_tables
|
||||
order by pg_total_relation_size(relid) desc;
|
||||
```
|
||||
|
||||
## Row-Count And Age Audit SQL
|
||||
|
||||
### AdminLog events by type/date
|
||||
|
||||
```sql
|
||||
select
|
||||
event_type,
|
||||
count(*) as row_count,
|
||||
min(event_timestamp) as first_event_timestamp,
|
||||
max(event_timestamp) as last_event_timestamp,
|
||||
min(server_time) as first_server_time,
|
||||
max(server_time) as last_server_time
|
||||
from rcon_admin_log_events
|
||||
group by event_type
|
||||
order by row_count desc, event_type asc;
|
||||
```
|
||||
|
||||
### Materialized matches by server/date
|
||||
|
||||
```sql
|
||||
select
|
||||
target_key,
|
||||
source_basis,
|
||||
count(*) as matches,
|
||||
min(coalesce(ended_at, started_at)) as first_closed_at,
|
||||
max(coalesce(ended_at, started_at)) as last_closed_at
|
||||
from rcon_materialized_matches
|
||||
group by target_key, source_basis
|
||||
order by target_key asc, source_basis asc;
|
||||
```
|
||||
|
||||
### Server snapshots by date
|
||||
|
||||
```sql
|
||||
select
|
||||
server_id,
|
||||
min(captured_at) as first_captured_at,
|
||||
max(captured_at) as last_captured_at,
|
||||
count(*) as snapshot_rows
|
||||
from server_snapshots
|
||||
group by server_id
|
||||
order by last_captured_at desc;
|
||||
```
|
||||
|
||||
### Displayed snapshots count
|
||||
|
||||
```sql
|
||||
select
|
||||
snapshot_type,
|
||||
metric,
|
||||
snapshot_window,
|
||||
count(*) as snapshot_rows,
|
||||
min(generated_at) as first_generated_at,
|
||||
max(generated_at) as last_generated_at
|
||||
from displayed_historical_snapshots
|
||||
group by snapshot_type, metric, snapshot_window
|
||||
order by snapshot_type asc, metric asc, snapshot_window asc;
|
||||
```
|
||||
|
||||
## Logs To Inspect
|
||||
|
||||
The cleanup command emits JSON logs. Minimum events to look for:
|
||||
|
||||
- `database-maintenance-started`
|
||||
- `database-maintenance-plan`
|
||||
- `database-maintenance-table-skipped`
|
||||
- `database-maintenance-delete-batch`
|
||||
- `database-maintenance-completed`
|
||||
- `database-maintenance-error`
|
||||
|
||||
Examples:
|
||||
|
||||
```powershell
|
||||
docker compose logs --tail=200 backend
|
||||
docker compose logs --tail=200 historical-runner
|
||||
```
|
||||
|
||||
If scheduled cleanup is enabled:
|
||||
|
||||
```powershell
|
||||
docker compose logs --tail=200 historical-runner
|
||||
```
|
||||
|
||||
## Docker And Portainer Warnings
|
||||
|
||||
- Never use `docker compose down -v` unless you intentionally want to delete PostgreSQL and mounted volume data.
|
||||
- Always review dry-run output before enabling apply in production.
|
||||
- Do not manually delete protected match or player-stat rows from PostgreSQL.
|
||||
- Keep backups before changing retention settings.
|
||||
- Do not add Comunidad Hispana #03 back into RCON targets in this task.
|
||||
- Do not add a separate maintenance container, host cron, or `pg_cron` job for this feature.
|
||||
|
||||
For Portainer-style operations the same warning applies:
|
||||
|
||||
- deleting volumes is destructive;
|
||||
- maintenance should run through the application command, not through manual table purges.
|
||||
|
||||
## Rollback And Restore Considerations
|
||||
|
||||
- Retention changes are destructive when apply mode runs.
|
||||
- Keep a PostgreSQL backup before enabling scheduled apply in production.
|
||||
- If cleanup removes too much data, recovery is restore-based, not “undo last delete.”
|
||||
- Favor dry-run, smaller batch sizes, and reviewed retention values before long-running scheduled apply.
|
||||
|
||||
## Safe Operator Flow
|
||||
|
||||
1. Audit table size and row ages with the SQL above.
|
||||
2. Run dry-run locally or in Compose.
|
||||
3. Review protected counts and candidate counts in JSON output.
|
||||
4. Enable `HLL_DB_MAINTENANCE_ENABLED=true` only after dry-run review.
|
||||
5. Monitor `historical-runner` logs for scheduler events and cleanup completion.
|
||||
242
docs/decisions.md
Normal file
242
docs/decisions.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Technical Decisions
|
||||
|
||||
## Decision 001: frontend simple HTML/CSS/JS
|
||||
|
||||
Se adopta una base estatica con HTML, CSS y JavaScript puro para priorizar simplicidad, velocidad de arranque y compatibilidad total al abrir el frontend directamente en navegador.
|
||||
|
||||
## Decision 002: backend previsto en Python
|
||||
|
||||
La estructura del repositorio reserva desde el inicio una carpeta de backend porque la implementacion futura se realizara en Python.
|
||||
|
||||
## Decision 003: estructura preparada para orquestacion por agentes
|
||||
|
||||
Se incluye una carpeta `ai/` y un documento `AGENTS.md` para facilitar una futura organizacion del trabajo por roles, tareas y orquestacion.
|
||||
|
||||
## Decision 004: branding militar Vietnam
|
||||
|
||||
La direccion visual inicial se alinea con una estetica sobria, tactica y militar inspirada en el contexto Vietnam para mantener coherencia tematica desde la primera iteracion.
|
||||
|
||||
## Decision 005: AI Development Platform integrada de forma adaptada
|
||||
|
||||
Se integra una capa de orquestacion por tasks inspirada en la plantilla de AI Development Platform, pero adaptada al contexto real de HLL Vietnam y sin arrastrar supuestos genericos de otros stacks. La plataforma se usa como soporte operativo del repositorio, no como funcionalidad del producto.
|
||||
|
||||
## Decision 006: contrato API pequeno antes de integraciones reales
|
||||
|
||||
Antes de implementar endpoints de comunidad o integraciones externas, se fija un contrato JSON minimo entre frontend y backend para evitar que la landing y el backend evolucionen con supuestos incompatibles.
|
||||
|
||||
La unica ruta implementada hoy es `GET /health`. Las rutas `/api/community`, `/api/trailer`, `/api/discord` y `/api/servers` quedan definidas como contrato previsto o placeholder en `docs/frontend-backend-contract.md`, manteniendo el backend en Python y sin introducir todavia Discord real, servidores reales ni base de datos.
|
||||
|
||||
## Decision 007: estrategia por fases para Discord y servidores
|
||||
|
||||
Los datos de Discord y de servidores de juego se incorporaran por fases para evitar dependencias prematuras de credenciales, APIs externas o consultas de red todavia no validadas.
|
||||
|
||||
La fase inicial debe usar datos manuales o placeholder controlados por el backend para mantener estable el contrato del frontend. Una fase intermedia podra anadir una integracion limitada con fuentes publicas o consultas tecnicas de bajo riesgo. Solo una fase posterior evaluara integraciones mas reales, siempre que queden claras las restricciones de seguridad, disponibilidad, latencia y mantenimiento.
|
||||
|
||||
La estrategia detallada de bloques de datos, fuentes posibles, riesgos y orden recomendado de implementacion queda documentada en `docs/discord-and-server-data-plan.md`.
|
||||
|
||||
## Decision 008: consumo frontend progresivo con fallback estatico
|
||||
|
||||
El frontend no debe depender de datos dinamicos para renderizar la landing base mientras el proyecto siga en fase fundacional.
|
||||
|
||||
Cuando se incorporen endpoints del backend, el consumo debe hacerse con `fetch` y JavaScript simple, priorizando bloques independientes y manteniendo contenido estatico o placeholders visuales si falla una llamada. `GET /health` queda reservado para comprobaciones tecnicas y no debe bloquear el render principal.
|
||||
|
||||
La estrategia detallada de prioridades de endpoints, estados de carga, errores y orden de migracion queda en `docs/frontend-data-consumption-plan.md`.
|
||||
|
||||
## Decision 009: servidores actuales de HLL como referencia provisional
|
||||
|
||||
Mientras no existan datos reales o representativos de HLL Vietnam, la web puede
|
||||
mostrar un bloque provisional con servidores actuales de Hell Let Loose siempre
|
||||
que quede claramente etiquetado como referencia temporal.
|
||||
|
||||
La primera version de ese bloque debe salir de un payload controlado del backend
|
||||
Python, no de una integracion directa desde frontend ni de scraping prematuro.
|
||||
Esto permite fijar campos utiles, preservar el tono del producto y evitar que la
|
||||
landing dependa de una fuente externa aun no validada.
|
||||
|
||||
La estrategia de campos, riesgos, fases y sustitucion futura queda documentada
|
||||
en `docs/current-hll-servers-source-plan.md`.
|
||||
|
||||
## Decision 010: ingesta por snapshots y adaptadores desacoplados
|
||||
|
||||
La evolucion desde payloads placeholder hacia datos mas realistas debe hacerse
|
||||
con una arquitectura de snapshots de servidor, no conectando el frontend a una
|
||||
fuente externa ni acoplando el backend a una integracion unica desde el inicio.
|
||||
|
||||
La unidad tecnica base sera un snapshot con `captured_at` y campos normalizados
|
||||
como estado, jugadores, capacidad y mapa actual cuando exista. La lectura de
|
||||
fuente, la normalizacion y la produccion del snapshot deben quedar separadas
|
||||
para poder sustituir mocks por una fuente publica o consulta tecnica posterior
|
||||
sin romper el contrato interno.
|
||||
|
||||
La estrategia detallada de fuentes, riesgos, fases y limites queda documentada
|
||||
en `docs/current-hll-data-ingestion-plan.md`.
|
||||
|
||||
## Decision 011: modelo de almacenamiento logico antes de fijar tecnologia
|
||||
|
||||
Antes de introducir una base de datos concreta, el proyecto debe fijar un
|
||||
modelo logico minimo para identidad de servidores y snapshots historicos.
|
||||
|
||||
La base inicial se apoya en entidades genericas como `game_sources`, `servers`
|
||||
y `server_snapshots`. Las metricas iniciales deben derivarse primero de esos
|
||||
snapshots en vez de materializar agregados prematuros. Esto mantiene el diseno
|
||||
reutilizable para HLL actual y para futuras fuentes mas cercanas a HLL Vietnam.
|
||||
|
||||
El modelo base y las preguntas abiertas quedan documentados en
|
||||
`docs/stats-database-schema-foundation.md`.
|
||||
|
||||
## Decision 012: historico de partidas desde CRCON scoreboard JSON
|
||||
|
||||
El historico reutilizable para estadisticas por partida y por jugador debe
|
||||
salir de la capa JSON publica expuesta por los scoreboards CRCON de la
|
||||
comunidad, no de A2S ni del HTML renderizado de `/games`.
|
||||
|
||||
La discovery tecnica confirma que ambos scoreboards sirven una SPA cuya fuente
|
||||
real de datos usa `baseURL: "/api"` y endpoints como
|
||||
`/get_scoreboard_maps` y `/get_map_scoreboard`. Esa capa permite obtener listas
|
||||
de partidas, detalle por `map_id` y metricas por jugador suficientes para una
|
||||
futura agregacion semanal por servidor.
|
||||
|
||||
A2S se mantiene como fuente de estado actual de servidores. El historico de
|
||||
partidas y rankings debe construirse en una linea separada basada en CRCON. La
|
||||
discovery detallada queda en `docs/historical-crcon-source-discovery.md`.
|
||||
|
||||
## Decision 013: persistencia historica local separada del flujo live
|
||||
|
||||
El backend mantiene el estado live de servidores y el historico CRCON en el
|
||||
mismo SQLite local de desarrollo para no introducir infraestructura prematura,
|
||||
pero ambas lineas quedan separadas por tablas y contratos distintos.
|
||||
|
||||
El flujo live sigue usando `server_snapshots` via A2S. El flujo historico usa
|
||||
tablas `historical_*` para:
|
||||
|
||||
- servidores historicos configurados
|
||||
- partidas
|
||||
- mapas
|
||||
- jugadores
|
||||
- estadisticas por jugador y partida
|
||||
- ejecuciones de ingesta
|
||||
|
||||
Las claves estables son:
|
||||
|
||||
- servidor: `historical_servers.slug`
|
||||
- partida: `(historical_server_id, external_match_id)`
|
||||
- jugador: `stable_player_key`
|
||||
- estadistica por partida: `(historical_match_id, historical_player_id)`
|
||||
|
||||
Esto permite bootstrap, refresco incremental e idempotencia sin mezclar
|
||||
semanticas de estado actual con historico persistido. El modelo detallado queda
|
||||
en `docs/historical-domain-model.md`.
|
||||
|
||||
## Decision 014: despliegue normal simplificado sin servidor #03
|
||||
|
||||
El despliegue operativo normal vuelve a quedar reducido a `backend` +
|
||||
`frontend`. Los servicios `historical-runner` y `rcon-historical-worker` se
|
||||
mantienen disponibles solo para uso avanzado y explicito mediante el perfil
|
||||
Compose `advanced`.
|
||||
|
||||
Comunidad Hispana #03 deja de formar parte de los targets RCON por defecto
|
||||
porque ya no es una fuente operativa vigente. El codigo historico, los datos
|
||||
persistidos, las migraciones y las piezas Elo/MMR no se eliminan; quedan
|
||||
pausadas operativamente para esta fase y pueden reintroducirse mediante una
|
||||
task futura si se valida de nuevo la fuente y el coste de mantenimiento.
|
||||
|
||||
## Decision 015: historico RCON-first con fallback publico
|
||||
|
||||
La politica por defecto para historico vuelve a ser RCON-first:
|
||||
`HLL_BACKEND_HISTORICAL_DATA_SOURCE=rcon`. El scoreboard publico de CRCON se
|
||||
mantiene como fallback controlado cuando RCON falla, no tiene cobertura util o
|
||||
no soporta todavia una operacion competitiva concreta.
|
||||
|
||||
La arquitectura historica RCON-first se compone de captura de sesiones RCON,
|
||||
ingesta de AdminLog, parser de eventos, almacenamiento de eventos/snapshots y
|
||||
materializacion de partidas y estadisticas por jugador. Los snapshots de perfil
|
||||
procedentes de `MESSAGE` enriquecen lecturas de jugador, pero no sustituyen los
|
||||
hechos de partida derivados de eventos RCON.
|
||||
|
||||
Comandos operativos manuales:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.rcon_admin_log_ingestion --minutes 1440
|
||||
docker compose exec backend python -m app.rcon_historical_worker capture
|
||||
```
|
||||
|
||||
Esta decision no reactiva Elo/MMR dentro del arranque normal del backend. Las
|
||||
piezas Elo/MMR, migraciones, datos persistidos y modulos historicos se
|
||||
conservan, pero su operativa compleja sigue pausada y desacoplada salvo task
|
||||
explicita.
|
||||
|
||||
## Decision 016: catalogo confiable de scoreboards publicos activos
|
||||
|
||||
Los origenes publicos de scoreboard que el backend puede exponer o validar se
|
||||
centralizan en un catalogo explicito de servidores activos. En esta fase solo
|
||||
son confiables `comunidad-hispana-01`, con origen
|
||||
`https://scoreboard.comunidadhll.es`, y `comunidad-hispana-02`, con origen
|
||||
`https://scoreboard.comunidadhll.es:5443`.
|
||||
|
||||
`comunidad-hispana-03` no forma parte de ese catalogo ni de los seeds por
|
||||
defecto nuevos. Los datos historicos ya persistidos no se eliminan, pero las
|
||||
URLs publicas de partidas solo se aceptan si el `raw_payload_ref` usa HTTP(S),
|
||||
apunta al origen confiable del servidor activo y mantiene una ruta `/games/`.
|
||||
|
||||
## Decision 017: PostgreSQL phase 1 for RCON historical persistence
|
||||
|
||||
La primera migracion de persistencia a PostgreSQL cubre el camino que sufria
|
||||
contencion SQLite entre `backend`, `historical-runner` y
|
||||
`rcon-historical-worker`:
|
||||
|
||||
- captura prospectiva RCON, muestras y ventanas competitivas
|
||||
- eventos AdminLog deduplicados y snapshots de perfil derivados
|
||||
- partidas RCON materializadas y estadisticas por jugador
|
||||
- candidatos confiables de URL de scoreboard que puedan poblarse para
|
||||
correlacion de detalle
|
||||
|
||||
Docker Compose configura `HLL_BACKEND_DATABASE_URL` y usa PostgreSQL como
|
||||
backend autoritativo para esas tablas. La ejecucion local sin esa variable sigue
|
||||
usando SQLite como fallback temporal para preservar comandos y tests locales.
|
||||
|
||||
Quedan SQLite-backed en esta fase porque no forman parte del lock-prone writer
|
||||
path migrado y siguen cubriendo fallback publico o caches locales:
|
||||
|
||||
- snapshots live y cache de `/api/servers`
|
||||
- tablas `historical_*` de scoreboard publico, rankings y correlacion legacy
|
||||
- snapshots historicos precalculados, ledger player-event y Elo/MMR pausado
|
||||
|
||||
La correlacion de URL publica en detalle usa primero candidatos PostgreSQL
|
||||
confiables cuando existan y puede seguir leyendo filas `historical_*`
|
||||
persistidas en SQLite durante la transicion. El diagnostico operativo se expone
|
||||
con `python -m app.storage_diagnostics`.
|
||||
|
||||
## Decision 018: PostgreSQL phase 2 for displayed historical data
|
||||
|
||||
PostgreSQL pasa a ser la fuente de lectura para los datos historicos visibles:
|
||||
|
||||
- fallback publico `historical_*` de partidas, detalle y rankings
|
||||
- snapshots historicos precalculados que consume `historico.html`
|
||||
- cache live de servidores que consume `/api/servers`
|
||||
- ledger player-event usado para reconstruir snapshots visibles
|
||||
- tablas RCON de AdminLog, perfiles, ventanas, partidas materializadas,
|
||||
estadisticas y candidatos seguros ya migradas en phase 1
|
||||
|
||||
La migracion se ejecuta de forma idempotente con:
|
||||
|
||||
```powershell
|
||||
cd backend
|
||||
python -m app.sqlite_to_postgres_migration
|
||||
python -m app.storage_diagnostics
|
||||
```
|
||||
|
||||
El comando conserva IDs y `external_match_id` del scoreboard publico, claves
|
||||
`match_key` materializadas y URLs seguras existentes. Copia SQLite y los JSON
|
||||
historicos de `backend/data/snapshots` como fuentes legacy; no los vuelve a
|
||||
usar como read model visible cuando `HLL_BACKEND_DATABASE_URL` esta definido.
|
||||
Las filas legacy de `comunidad-hispana-03` se omiten en el read model visible
|
||||
de esta migracion para no reactivar ese target.
|
||||
|
||||
Permanecen fuera de phase 2:
|
||||
|
||||
- checkpoints y runs operativos del import publico que no aparecen en frontend
|
||||
- Elo/MMR pausado y oculto en la UI actual
|
||||
|
||||
`app.storage_diagnostics` muestra conteos PostgreSQL, ultimas partidas
|
||||
materializadas, ultimos `match_end`, dominios restantes y un resumen de paridad
|
||||
para verificar la migracion antes de retirar fuentes legacy.
|
||||
130
docs/deployment/nas-portainer.md
Normal file
130
docs/deployment/nas-portainer.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# NAS / Portainer deployment
|
||||
|
||||
This deployment path is for the Proxmox NAS Docker/Portainer environment. It keeps the development `docker-compose.yml` unchanged and adds a production compose file under `deploy/portainer/`.
|
||||
|
||||
## Files
|
||||
|
||||
- `deploy/portainer/docker-compose.nas.yml`: production compose for Portainer.
|
||||
- `deploy/portainer/stack.env.example`: safe environment template. Copy values into Portainer and replace placeholders.
|
||||
- `deploy/portainer/Caddyfile.example`: Caddy reverse proxy block for `comunidadhll.devzamode.es`.
|
||||
|
||||
## Portainer stack
|
||||
|
||||
1. In Portainer, create a new Stack from the cloned repository.
|
||||
2. Use compose file path:
|
||||
|
||||
```text
|
||||
deploy/portainer/docker-compose.nas.yml
|
||||
```
|
||||
|
||||
3. Paste variables from `deploy/portainer/stack.env.example` into the stack environment editor.
|
||||
4. Replace all placeholders, especially:
|
||||
- `POSTGRES_PASSWORD`
|
||||
- `HLL_BACKEND_DATABASE_URL`
|
||||
- `HLL_BACKEND_RCON_TARGETS`
|
||||
|
||||
The production compose does not publish host ports. Caddy is the only public entrypoint. Backend and frontend are attached to the external Docker network configured by `CADDY_NETWORK`, defaulting to `stack-caddy`.
|
||||
|
||||
## External Caddy network
|
||||
|
||||
Make sure the Caddy network exists:
|
||||
|
||||
```bash
|
||||
docker network ls | grep stack-caddy
|
||||
```
|
||||
|
||||
If the network does not exist, create it from the Caddy stack or manually:
|
||||
|
||||
```bash
|
||||
docker network create stack-caddy
|
||||
```
|
||||
|
||||
## Caddy configuration
|
||||
|
||||
Add this block to `/mnt/data8tb/NAS/stack-caddy/Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
comunidadhll.devzamode.es {
|
||||
encode zstd gzip
|
||||
|
||||
reverse_proxy /health hll-vietnam-backend-1:8000
|
||||
reverse_proxy /api/* hll-vietnam-backend-1:8000
|
||||
|
||||
reverse_proxy hll-vietnam-frontend-1:8080
|
||||
}
|
||||
```
|
||||
|
||||
Then format and reload Caddy:
|
||||
|
||||
```bash
|
||||
docker exec caddy caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
From the NAS or another machine:
|
||||
|
||||
```bash
|
||||
curl -I https://comunidadhll.devzamode.es
|
||||
curl https://comunidadhll.devzamode.es/health
|
||||
curl https://comunidadhll.devzamode.es/api/servers
|
||||
```
|
||||
|
||||
In Portainer, check logs for:
|
||||
|
||||
- backend
|
||||
- frontend
|
||||
- postgres
|
||||
|
||||
With Docker CLI:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml ps
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml logs --tail=100 backend
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml logs --tail=100 frontend
|
||||
```
|
||||
|
||||
## Updating after git pull
|
||||
|
||||
From the repository directory on the NAS:
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml build
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml up -d
|
||||
```
|
||||
|
||||
Or redeploy the stack from Portainer.
|
||||
|
||||
## Advanced historical workers
|
||||
|
||||
Normal production startup includes only:
|
||||
|
||||
- postgres
|
||||
- backend
|
||||
- frontend
|
||||
|
||||
Historical workers are opt-in through the `advanced` profile:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml --profile advanced up -d historical-runner rcon-historical-worker
|
||||
```
|
||||
|
||||
Stop them before running manual backfills or other long writer jobs:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml --profile advanced stop historical-runner rcon-historical-worker
|
||||
```
|
||||
|
||||
## Local validation commands
|
||||
|
||||
Run from repository root:
|
||||
|
||||
```bash
|
||||
docker compose config
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml config
|
||||
docker compose -f deploy/portainer/docker-compose.nas.yml build
|
||||
```
|
||||
|
||||
The development compose still exposes local ports for `http://localhost:8080` and `http://localhost:8000`. The NAS compose intentionally exposes no host ports.
|
||||
119
docs/discord-and-server-data-plan.md
Normal file
119
docs/discord-and-server-data-plan.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Discord And Server Data Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Definir una base tecnica para exponer en la web datos de Discord y de futuros servidores de juego sin implementar todavia integraciones reales ni depender de servicios externos en esta fase.
|
||||
|
||||
## Discord Data Candidates
|
||||
|
||||
Bloques con sentido para la web:
|
||||
|
||||
- `invite_url`: enlace principal para entrar en la comunidad.
|
||||
- `community_name`: nombre visible de la comunidad o del servidor.
|
||||
- `cta_label`: texto de llamada a la accion para el boton de acceso.
|
||||
- `approx_presence`: presencia aproximada o estado publico solo si existe una fuente publica fiable.
|
||||
- `public_summary`: breve descripcion publica, reglas resumidas o mensaje de bienvenida.
|
||||
|
||||
## Game Server Data Candidates
|
||||
|
||||
Bloques con sentido para la web:
|
||||
|
||||
- `server_name`: nombre visible del servidor.
|
||||
- `status`: online u offline.
|
||||
- `current_map`: mapa actual si la fuente lo permite.
|
||||
- `rotation`: rotacion o proximo mapa si la fuente es estable.
|
||||
- `players`: jugadores conectados.
|
||||
- `max_players`: capacidad maxima.
|
||||
- `ping`: latencia aproximada si la consulta la devuelve.
|
||||
- `region` o `notes`: metadatos operativos simples para la comunidad.
|
||||
|
||||
## Possible Discord Sources
|
||||
|
||||
### Public widget
|
||||
|
||||
- Util para obtener datos publicos basicos si el servidor lo tiene habilitado.
|
||||
- Bueno para presencia aproximada o nombre visible.
|
||||
- Limitado por la configuracion del propio servidor y por el alcance real del widget.
|
||||
|
||||
### External API or third-party integration
|
||||
|
||||
- Puede simplificar algunas lecturas, pero introduce dependencia de terceros, cambios de servicio y posibles limites de uso.
|
||||
- Debe considerarse solo si aporta estabilidad y evita exponer credenciales en frontend.
|
||||
|
||||
### Own bot
|
||||
|
||||
- Da mas control a largo plazo.
|
||||
- Exige credenciales, despliegue, permisos y operacion continua.
|
||||
- No encaja en la fase actual del repositorio.
|
||||
|
||||
### Manual configured data
|
||||
|
||||
- Fuente mas segura para la primera fase.
|
||||
- Sirve para `invite_url`, nombre de comunidad y textos publicos.
|
||||
- Permite validar el contrato API y el consumo frontend sin depender de Discord real.
|
||||
|
||||
## Possible Game Server Sources
|
||||
|
||||
### Direct server queries
|
||||
|
||||
- Pueden dar estado, jugadores, mapa o ping segun el protocolo disponible.
|
||||
- Exigen validar compatibilidad real con el juego, frecuencia de consulta y tolerancia a timeouts.
|
||||
|
||||
### External API
|
||||
|
||||
- Puede simplificar el acceso si existe una fuente especializada.
|
||||
- Introduce dependencia externa, disponibilidad ajena y posible coste o rate limit.
|
||||
|
||||
### Mock or placeholder data
|
||||
|
||||
- Opcion recomendada para la primera fase.
|
||||
- Permite fijar formato JSON, estados y experiencia de frontend sin acoplarse a infraestructura real.
|
||||
|
||||
### Manual updates
|
||||
|
||||
- Util para mostrar estado controlado o informacion operativa minima mientras no exista integracion tecnica fiable.
|
||||
- Reduce riesgo en una etapa donde el backend aun es preparatorio.
|
||||
|
||||
## Risks And Restrictions
|
||||
|
||||
- Credenciales: bots o APIs privadas requieren secretos y una estrategia de almacenamiento segura.
|
||||
- Rate limits: Discord o terceros pueden limitar frecuencia de consulta.
|
||||
- Availability: widgets, APIs o consultas de servidor pueden fallar o cambiar sin previo aviso.
|
||||
- Security: nunca debe exponerse en frontend una credencial ni una ruta administrativa.
|
||||
- CORS: el frontend no deberia depender de llamadas directas a servicios externos si eso obliga a resolver CORS en cliente.
|
||||
- Latency: consultas en tiempo real pueden degradar la web si no se amortiguan en backend.
|
||||
- External dependency: cada integracion nueva aumenta coste operativo y puntos de fallo.
|
||||
|
||||
## Phased Strategy
|
||||
|
||||
### Phase 1: controlled placeholders
|
||||
|
||||
- Backend Python devuelve datos manuales o mock para `/api/discord` y `/api/servers`.
|
||||
- La web usa esos datos solo cuando futuras tasks lo indiquen.
|
||||
- No hay consultas reales a Discord ni a servidores.
|
||||
|
||||
### Phase 2: limited technical integration
|
||||
|
||||
- Evaluar una unica fuente publica o consulta sencilla por dominio.
|
||||
- Mantener fallback manual si la fuente falla.
|
||||
- Introducir observabilidad minima antes de ampliar alcance.
|
||||
|
||||
### Phase 3: real integration if justified
|
||||
|
||||
- Considerar bot propio, polling controlado o una integracion mas rica solo si aporta valor real a la comunidad.
|
||||
- Revisar seguridad, operacion, cache y mantenimiento antes de consolidarlo.
|
||||
|
||||
## What Is Explicitly Out Of Scope Now
|
||||
|
||||
- Integrar Discord real.
|
||||
- Consultar servidores reales de juego.
|
||||
- Anadir base de datos.
|
||||
- Implementar autenticacion o panel administrativo.
|
||||
- Hacer llamadas directas desde el frontend a servicios externos.
|
||||
|
||||
## Recommended Implementation Order
|
||||
|
||||
1. Consolidar placeholders backend para `community`, `discord`, `trailer` y `servers`.
|
||||
2. Definir consumo frontend con fallbacks visuales y orden de prioridad.
|
||||
3. Validar una fuente publica o consulta tecnica pequena para Discord o servidores.
|
||||
4. Decidir si merece la pena ampliar integraciones reales.
|
||||
214
docs/elo-mmr-monthly-ranking-design.md
Normal file
214
docs/elo-mmr-monthly-ranking-design.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Elo/MMR Monthly Ranking Design
|
||||
|
||||
## Scope
|
||||
|
||||
This repository now exposes a first operational Elo/MMR-like system inspired by
|
||||
`sistema_elo_mensual_hll.pdf`, but constrained to signals that are really
|
||||
available today.
|
||||
|
||||
The implementation keeps the same conceptual split:
|
||||
|
||||
- persistent `MMR`
|
||||
- monthly `MonthlyRankScore`
|
||||
|
||||
It does **not** claim full parity with the PDF. Every major signal is labeled as:
|
||||
|
||||
- `exact`
|
||||
- `approximate`
|
||||
- `not_available`
|
||||
|
||||
## Real Inputs Available Today
|
||||
|
||||
Exact today from persisted historical CRCON/public-scoreboard data:
|
||||
|
||||
- closed match identity
|
||||
- server scope
|
||||
- player identity
|
||||
- team side
|
||||
- kills
|
||||
- deaths
|
||||
- support
|
||||
- teamkills
|
||||
- combat score
|
||||
- offense score
|
||||
- defense score
|
||||
- match timestamps when present
|
||||
- final allied/axis score
|
||||
|
||||
Exact today from current product state but not required by the core engine:
|
||||
|
||||
- player-event V2 summaries for duels, most-killed, death-by and weapon summaries
|
||||
|
||||
Approximate only:
|
||||
|
||||
- `role_bucket`
|
||||
- inferred from the dominant scoreboard axis among `combat`, `offense`,
|
||||
`defense` and `support`
|
||||
- `ObjectiveIndex`
|
||||
- proxied with `offense + defense` because there is no tactical event feed
|
||||
- `StrengthOfSchedule`
|
||||
- proxied with match quality and lobby density because there is no opponent MMR
|
||||
model yet
|
||||
|
||||
Not available today:
|
||||
|
||||
- explicit squad role / commander / SL role
|
||||
- garrisons and OPs destroyed
|
||||
- revives
|
||||
- AFK and leave events
|
||||
- precise leadership telemetry
|
||||
- exact tactical objective event stream
|
||||
- exact opponent-strength graph by roster
|
||||
|
||||
## Current Capability Contract
|
||||
|
||||
### Match validity
|
||||
|
||||
Current rule:
|
||||
|
||||
- match must be closed
|
||||
- match duration must be at least `15` minutes
|
||||
- match must have at least `20` persisted player rows
|
||||
|
||||
Duration source:
|
||||
|
||||
- `exact` if `started_at` and `ended_at` exist
|
||||
- `approximate` if we must fall back to max player `time_seconds`
|
||||
|
||||
### Quality factor Q
|
||||
|
||||
Current `Q` is a bounded mix of:
|
||||
|
||||
- player density
|
||||
- match duration
|
||||
- score completeness
|
||||
|
||||
This is an operational approximation of the PDF quality factor and is labelled:
|
||||
|
||||
- `exact` for the density and score-completeness inputs
|
||||
- `exact` or `approximate` for duration depending on timestamp availability
|
||||
|
||||
### Buckets
|
||||
|
||||
Implemented:
|
||||
|
||||
- duration bucket
|
||||
- mode retention through `game_mode`
|
||||
- approximate `role_bucket`
|
||||
|
||||
Not implemented yet:
|
||||
|
||||
- literal class role bucket
|
||||
|
||||
### Subindices
|
||||
|
||||
Implemented now:
|
||||
|
||||
- `OutcomeScore`: `exact`
|
||||
- `CombatIndex`: `exact`
|
||||
- `ObjectiveIndex`: `approximate`
|
||||
- `UtilityIndex`: `exact`
|
||||
- `LeadershipIndex`: `not_available`
|
||||
- `DisciplineIndex`: `exact` for teamkills only
|
||||
|
||||
### ImpactScore
|
||||
|
||||
Implemented with role-inspired weights, but the role itself is approximate, so
|
||||
the final `ImpactScore` is operationally `approximate`.
|
||||
|
||||
### DeltaMMR
|
||||
|
||||
Implemented from:
|
||||
|
||||
- `OutcomeScore`
|
||||
- `ImpactScore`
|
||||
- quality factor `Q`
|
||||
|
||||
The resulting `DeltaMMR` is real and persisted, but inherits the mixed
|
||||
availability of the inputs above.
|
||||
|
||||
## Storage Model
|
||||
|
||||
Tables added in backend SQLite:
|
||||
|
||||
- `elo_mmr_player_ratings`
|
||||
- `elo_mmr_match_results`
|
||||
- `elo_mmr_monthly_rankings`
|
||||
- `elo_mmr_monthly_checkpoints`
|
||||
|
||||
Meaning:
|
||||
|
||||
- `elo_mmr_player_ratings`
|
||||
- current persistent rating per player and scope
|
||||
- `elo_mmr_match_results`
|
||||
- per-match scoring trace used to explain rating movement
|
||||
- `elo_mmr_monthly_rankings`
|
||||
- monthly ranking rows ready for product/API
|
||||
- `elo_mmr_monthly_checkpoints`
|
||||
- generated-at metadata plus source policy and capability summary
|
||||
|
||||
Scopes persisted:
|
||||
|
||||
- per historical server
|
||||
- `all-servers`
|
||||
|
||||
## Runtime Source Policy
|
||||
|
||||
The Elo/MMR engine follows the same historical policy as the rest of backend:
|
||||
|
||||
- primary intent: `rcon`
|
||||
- current competitive calculation fallback: `public-scoreboard`
|
||||
|
||||
Why fallback still exists here:
|
||||
|
||||
- the current RCON historical read model only supports coverage and recent
|
||||
activity
|
||||
- it does not yet expose enough competitive match detail to support this Elo/MMR
|
||||
engine directly
|
||||
|
||||
That fallback is exposed in API metadata through:
|
||||
|
||||
- `primary_source`
|
||||
- `selected_source`
|
||||
- `fallback_used`
|
||||
- `fallback_reason`
|
||||
- `source_attempts`
|
||||
|
||||
## Product Read Model
|
||||
|
||||
Current API surfaces:
|
||||
|
||||
- `/api/historical/elo-mmr/leaderboard`
|
||||
- `/api/historical/elo-mmr/player`
|
||||
|
||||
These payloads expose:
|
||||
|
||||
- persistent rating
|
||||
- monthly ranking score
|
||||
- eligibility
|
||||
- component breakdown
|
||||
- exact/approximate/partial capability metadata
|
||||
|
||||
## Important Limitations
|
||||
|
||||
This first version should be treated as:
|
||||
|
||||
- operational
|
||||
- honest about accuracy
|
||||
- compatible with future expansion
|
||||
|
||||
It should **not** be described as:
|
||||
|
||||
- a perfect Elo system
|
||||
- full parity with the PDF
|
||||
- a complete tactical rating model
|
||||
|
||||
## Planned Expansion Path
|
||||
|
||||
The current design is compatible with future upgrades once real telemetry exists:
|
||||
|
||||
- replace approximate `ObjectiveIndex` with event-driven tactical signals
|
||||
- add `LeadershipIndex` when squad/command telemetry exists
|
||||
- replace approximate `StrengthOfSchedule` with opponent MMR graph logic
|
||||
- feed V2 duels and weapon signals into richer combat weighting when their
|
||||
coverage is sufficient
|
||||
263
docs/frontend-backend-contract.md
Normal file
263
docs/frontend-backend-contract.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Frontend Backend Contract
|
||||
|
||||
## Objetivo
|
||||
|
||||
Definir un contrato inicial y pequeno entre la landing actual y el futuro backend Python sin implementar todavia integraciones reales ni comprometer detalles de infraestructura antes de tiempo.
|
||||
|
||||
## Estado actual
|
||||
|
||||
- Frontend: landing estatica sin consumo de API
|
||||
- Backend: bootstrap Python con `GET /health`
|
||||
- Integraciones reales: no implementadas
|
||||
|
||||
## Convenciones generales
|
||||
|
||||
- Todas las respuestas usan JSON.
|
||||
- Los nombres de campos usan `snake_case`.
|
||||
- `status` es obligatorio en todas las respuestas.
|
||||
- Las respuestas exitosas usan `status: "ok"`.
|
||||
- Las respuestas de error usan `status: "error"` y un campo `message`.
|
||||
- Cuando un endpoint sea solo placeholder o aun no tenga datos reales, puede responder datos controlados o quedar documentado como previsto hasta una task posterior.
|
||||
|
||||
## Estructura base de respuesta
|
||||
|
||||
Respuesta correcta:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
Respuesta de error minima:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Route not found"
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `GET /health`
|
||||
|
||||
- Proposito: comprobar que el backend bootstrap esta levantado.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/health`
|
||||
- Estado actual: implementado
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"service": "hll-vietnam-backend",
|
||||
"phase": "bootstrap"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/community`
|
||||
|
||||
- Proposito: devolver contenido resumido de presentacion de la comunidad para bloques de texto o estadisticas futuras.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/community`
|
||||
- Estado actual: previsto
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Comunidad Hispana HLL Vietnam",
|
||||
"summary": "Punto de encuentro para jugadores, escuadras y comunidad.",
|
||||
"discord_invite_url": "https://discord.com/invite/PedEqZ2Xsa"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/trailer`
|
||||
|
||||
- Proposito: exponer la informacion del trailer que hoy esta fija en la landing.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/trailer`
|
||||
- Estado actual: previsto
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"video_url": "https://www.youtube.com/embed/JzYzYNVWZ_A",
|
||||
"title": "Trailer HLL Vietnam",
|
||||
"provider": "youtube"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/discord`
|
||||
|
||||
- Proposito: centralizar la informacion publica del acceso a Discord sin integrar todavia datos reales del servidor.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/discord`
|
||||
- Estado actual: placeholder
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"invite_url": "https://discord.com/invite/PedEqZ2Xsa",
|
||||
"label": "Unirse al Discord",
|
||||
"availability": "manual"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/servers`
|
||||
|
||||
- Proposito: exponer el estado actual de los 2 servidores reales de la comunidad desde backend, usando el ultimo snapshot valido y forzando refresco real cuando el cache local supere el objetivo de 120 segundos.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/servers`
|
||||
- Estado actual: implementado con refresco A2S bajo demanda y fallback a snapshot persistido stale
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Estado actual de servidores",
|
||||
"context": "current-hll-status",
|
||||
"source": "real-time-a2s-refresh",
|
||||
"last_snapshot_at": "2026-03-20T18:37:58.628122Z",
|
||||
"snapshot_age_seconds": 0,
|
||||
"snapshot_age_minutes": 0,
|
||||
"max_snapshot_age_seconds": 120,
|
||||
"is_stale": false,
|
||||
"freshness": "fresh",
|
||||
"refresh_attempted": true,
|
||||
"refresh_status": "success",
|
||||
"refresh_errors": [],
|
||||
"items": [
|
||||
{
|
||||
"external_server_id": "comunidad-hispana-01",
|
||||
"server_name": "Comunidad Hispana #01",
|
||||
"status": "online",
|
||||
"players": 74,
|
||||
"max_players": 100,
|
||||
"current_map": "Sainte-Marie-du-Mont",
|
||||
"region": "ES",
|
||||
"snapshot_origin": "real-a2s",
|
||||
"captured_at": "2026-03-20T18:37:58.628122Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notas del comportamiento actual:
|
||||
|
||||
- Si el snapshot persistido tiene `120` segundos o menos, puede reutilizarse sin refresco inmediato.
|
||||
- Si el snapshot supera ese umbral, backend intenta una consulta A2S real antes de responder.
|
||||
- Si la consulta real falla, backend devuelve el ultimo snapshot valido con `is_stale: true`.
|
||||
- Si no existe ningun snapshot valido, backend responde `items: []` y no inventa servidores de referencia.
|
||||
|
||||
### `GET /api/servers/latest`
|
||||
|
||||
- Proposito: devolver el ultimo snapshot conocido por servidor desde la persistencia local.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/servers/latest`
|
||||
- Estado actual: implementado para validacion tecnica
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Ultimo estado conocido de servidores",
|
||||
"context": "current-hll-history",
|
||||
"source": "local-snapshot-storage",
|
||||
"items": [
|
||||
{
|
||||
"server_id": 1,
|
||||
"external_server_id": "hll-esp-tactical-rotation",
|
||||
"server_name": "HLL ESP Tactical Rotation",
|
||||
"region": "EU",
|
||||
"captured_at": "2026-03-20T08:45:20.802006Z",
|
||||
"status": "online",
|
||||
"players": 74,
|
||||
"max_players": 100,
|
||||
"current_map": "Sainte-Marie-du-Mont"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/servers/history`
|
||||
|
||||
- Proposito: devolver una ventana simple de snapshots recientes desde la persistencia local.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/servers/history`
|
||||
- Parametros opcionales: `limit` entre `1` y `100`
|
||||
- Estado actual: implementado para validacion tecnica
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Historial reciente de servidores",
|
||||
"context": "current-hll-history",
|
||||
"source": "local-snapshot-storage",
|
||||
"limit": 20,
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/servers/{id}/history`
|
||||
|
||||
- Proposito: devolver una historia basica de snapshots para un servidor concreto.
|
||||
- Metodo HTTP: `GET`
|
||||
- Ruta: `/api/servers/{id}/history`
|
||||
- Parametros opcionales: `limit` entre `1` y `100`
|
||||
- Identificadores aceptados: `server_id` numerico interno o `external_server_id`
|
||||
- Estado actual: implementado para validacion tecnica
|
||||
|
||||
Ejemplo JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Historial por servidor",
|
||||
"context": "current-hll-history",
|
||||
"source": "local-snapshot-storage",
|
||||
"server_id": "hll-esp-tactical-rotation",
|
||||
"limit": 20,
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Consumo previsto desde frontend
|
||||
|
||||
- El frontend deberia llamar primero a `GET /health` solo para comprobaciones tecnicas o entornos de desarrollo, no para condicionar el render basico de la landing.
|
||||
- Los endpoints de contenido (`/api/community`, `/api/trailer`, `/api/discord`, `/api/servers`) deberian consumirse con `fetch`.
|
||||
- Si una llamada falla, la landing debe conservar un fallback estatico mientras exista contenido fijo en `index.html`.
|
||||
- La futura migracion debe reemplazar valores hardcoded de forma incremental, endpoint por endpoint.
|
||||
|
||||
## Notas de alcance
|
||||
|
||||
- Este contrato no introduce autenticacion.
|
||||
- Este contrato no define base de datos.
|
||||
- Este contrato no integra Discord ni servidores reales.
|
||||
- La implementacion de estos endpoints queda para tasks posteriores.
|
||||
73
docs/frontend-data-consumption-plan.md
Normal file
73
docs/frontend-data-consumption-plan.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Frontend Data Consumption Plan
|
||||
|
||||
## Objective
|
||||
|
||||
Definir como evolucionara la landing de HLL Vietnam desde contenido estatico hacia bloques alimentados por el backend sin romper simplicidad, branding ni compatibilidad al abrir `frontend/index.html` directamente.
|
||||
|
||||
## Current Frontend Blocks With Future Dynamic Potential
|
||||
|
||||
- Hero principal: titulo, resumen y CTA de Discord podran leer `community` y `discord`.
|
||||
- Bloque de trailer: podra leer `trailer` para desacoplar video y titulo del HTML.
|
||||
- Estado de servidores: queda reservado para una futura seccion y no debe forzarse en la landing actual.
|
||||
|
||||
## Recommended Consumption Strategy
|
||||
|
||||
- Usar `fetch` nativo cuando una task habilite consumo real.
|
||||
- Mantener JavaScript simple en `frontend/assets/js/main.js` o dividir en modulos ligeros solo si el numero de bloques dinamicos ya lo justifica.
|
||||
- Centralizar la URL base del backend en una configuracion minima si el frontend deja de ser puramente estatico en un entorno concreto.
|
||||
- No llamar a servicios externos desde el navegador; el frontend debe hablar con el backend Python.
|
||||
|
||||
## UI State Rules
|
||||
|
||||
### Loading
|
||||
|
||||
- No bloquear el render inicial de la landing.
|
||||
- Mostrar skeletons o placeholders ligeros solo en bloques futuros que ya dependan del backend.
|
||||
|
||||
### Error
|
||||
|
||||
- Si falla una llamada, conservar el contenido estatico existente o un mensaje tactico breve y no intrusivo.
|
||||
- Registrar el error en consola durante desarrollo sin degradar toda la pagina.
|
||||
|
||||
### Empty state
|
||||
|
||||
- Si `servers.items` llega vacio, mostrar un estado neutral de "informacion disponible mas adelante".
|
||||
- Si un bloque opcional no tiene datos, ocultarlo o dejar un placeholder discreto en lugar de mostrar errores tecnicos.
|
||||
|
||||
### Fallback
|
||||
|
||||
- Mantener el Discord CTA hardcoded hasta que `/api/discord` sea estable.
|
||||
- Mantener el iframe del trailer fijo hasta validar `/api/trailer`.
|
||||
- No hacer depender el hero de `/health`.
|
||||
|
||||
## Endpoint Priority
|
||||
|
||||
1. `/api/community`
|
||||
2. `/api/trailer`
|
||||
3. `/api/discord`
|
||||
4. `/api/servers`
|
||||
5. `/health` solo para checks tecnicos o diagnostico en desarrollo
|
||||
|
||||
## Progressive Migration Path
|
||||
|
||||
### Step 1
|
||||
|
||||
- Introducir una capa minima de lectura para `community` y `trailer`.
|
||||
- Reutilizar el HTML actual como fallback.
|
||||
|
||||
### Step 2
|
||||
|
||||
- Sustituir el CTA de Discord por datos de `/api/discord` cuando el placeholder backend sea estable.
|
||||
- Mantener la URL actual como respaldo local.
|
||||
|
||||
### Step 3
|
||||
|
||||
- Anadir una seccion de servidores solo cuando exista diseno, contrato y placeholder suficientemente claros.
|
||||
- Evitar reservar complejidad en la landing antes de que ese bloque aporte valor real.
|
||||
|
||||
## Explicitly Out Of Scope Now
|
||||
|
||||
- Implementar `fetch` real.
|
||||
- Cambiar el comportamiento visible de la landing.
|
||||
- Introducir librerias de estado o frameworks frontend.
|
||||
- Conectar el navegador directamente con Discord o con APIs de servidores.
|
||||
120
docs/historical-coverage-report.md
Normal file
120
docs/historical-coverage-report.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Historical Coverage Report
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-21
|
||||
- 2026-03-23
|
||||
|
||||
## Scope
|
||||
|
||||
Estado real de la cobertura historica persistida localmente en
|
||||
`backend/data/hll_vietnam_dev.sqlite3` tras ejecutar el bootstrap CRCON con el
|
||||
flujo reforzado de `backend/app/historical_ingestion.py`.
|
||||
|
||||
## Commands Used
|
||||
|
||||
Desde `backend/`:
|
||||
|
||||
```powershell
|
||||
python -m app.historical_ingestion bootstrap --max-pages 3 --detail-workers 16
|
||||
```
|
||||
|
||||
Bootstrap acotado y reanudable para `comunidad-hispana-03`:
|
||||
|
||||
```powershell
|
||||
python -m app.historical_ingestion bootstrap --server comunidad-hispana-03 --page-size 10 --max-pages 1 --detail-workers 8
|
||||
```
|
||||
|
||||
Verificacion puntual previa de idempotencia sobre la primera pagina ya
|
||||
importada:
|
||||
|
||||
```powershell
|
||||
python -m app.historical_ingestion bootstrap --max-pages 1 --detail-workers 8
|
||||
```
|
||||
|
||||
Esa reejecucion devolvio `matches_inserted: 0` y solo `matches_updated` para
|
||||
los matches ya persistidos, confirmando el comportamiento idempotente en el
|
||||
tramo reimportado.
|
||||
|
||||
## Source Depth Discovered
|
||||
|
||||
La propia API CRCON reporto en pagina 1:
|
||||
|
||||
- `comunidad-hispana-01`: `23029` matches historicos disponibles
|
||||
- `comunidad-hispana-02`: `18221` matches historicos disponibles
|
||||
|
||||
Esto confirma que la fuente publica tiene un archivo mucho mas profundo que la
|
||||
semana movil usada por la UI y que un bootstrap completo real es una operacion
|
||||
larga incluso con paralelismo.
|
||||
|
||||
## Persisted Coverage After Bootstrap Validation
|
||||
|
||||
### comunidad-hispana-01
|
||||
|
||||
- matches importados: `150`
|
||||
- jugadores unicos: `3986`
|
||||
- filas de estadisticas por jugador: `12650`
|
||||
- primera partida persistida: `2026-03-04T22:11:18Z`
|
||||
- ultima partida persistida: `2026-03-20T21:41:18Z`
|
||||
- rango cubierto: `15.98` dias
|
||||
|
||||
### comunidad-hispana-02
|
||||
|
||||
- matches importados: `150`
|
||||
- jugadores unicos: `4468`
|
||||
- filas de estadisticas por jugador: `12665`
|
||||
- primera partida persistida: `2026-03-01T16:59:10Z`
|
||||
- ultima partida persistida: `2026-03-20T21:14:21Z`
|
||||
- rango cubierto: `19.18` dias
|
||||
|
||||
### comunidad-hispana-03
|
||||
|
||||
- matches importados: `33`
|
||||
- jugadores unicos: `1161`
|
||||
- filas de estadisticas por jugador: `2547`
|
||||
- primera partida persistida: `2026-02-24T18:16:11Z`
|
||||
- ultima partida persistida: `2026-03-08T18:11:52Z`
|
||||
- rango cubierto: `12.0` dias
|
||||
- total descubierto en la fuente publica: `11652` matches
|
||||
- checkpoint actual de bootstrap: `next_page = 2`, `last_completed_page = 1`
|
||||
|
||||
## Interpretation
|
||||
|
||||
- La base persistida ya supera claramente la ventana semanal en ambos
|
||||
servidores, por lo que la UI historica ya puede distinguir entre "ranking de
|
||||
ultimos 7 dias" y "cobertura total importada" sin fingir que ambos conceptos
|
||||
son lo mismo.
|
||||
- `comunidad-hispana-03` ya no esta vacio: existe historico real persistido,
|
||||
snapshots de resumen y partidas recientes, y un checkpoint reanudable para
|
||||
seguir ampliando cobertura sin repetir desde cero.
|
||||
- El historico local sigue siendo parcial respecto al total reportado por la
|
||||
fuente. Lo importado hoy es suficiente para seguir con semantica y revisiones
|
||||
de UI, pero no representa aun el archivo completo disponible en CRCON.
|
||||
|
||||
## Source Limits Observed
|
||||
|
||||
- Bajo replays repetidos del mismo bootstrap, la fuente CRCON devolvio errores
|
||||
`502 Bad Gateway` intermitentes en `get_public_info` y `get_map_scoreboard`.
|
||||
- Con `--detail-workers 16` la carga validada fue estable para `3` paginas por
|
||||
servidor. Con concurrencia mas alta se observaron payloads no validos con mas
|
||||
frecuencia.
|
||||
|
||||
## Operational Conclusion
|
||||
|
||||
- El bootstrap queda reanudable por checkpoint persistido en
|
||||
`historical_backfill_progress`; si no se pasa `--start-page`, una nueva
|
||||
sesion continua desde `next_page`.
|
||||
- Cada pagina completada actualiza por servidor:
|
||||
- `last_completed_page`
|
||||
- `next_page`
|
||||
- `discovered_total_matches`
|
||||
- `discovered_total_pages`
|
||||
- `last_run`
|
||||
- La estrategia operativa razonable para completar todo el archivo es ejecutar
|
||||
varias sesiones consecutivas con el mismo comando hasta que
|
||||
`archive_exhausted` pase a `true`.
|
||||
- `--start-page` se conserva solo como override manual cuando haga falta
|
||||
reprocesar un tramo concreto.
|
||||
- Mientras no se complete todo el archivo, cualquier UI o API debe mostrar la
|
||||
cobertura importada como cobertura real disponible y no como historico total
|
||||
del servidor.
|
||||
243
docs/historical-crcon-source-discovery.md
Normal file
243
docs/historical-crcon-source-discovery.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Historical CRCON Source Discovery
|
||||
|
||||
## Objective
|
||||
|
||||
Documentar la fuente historica real y mas estable para los 2 servidores de la comunidad a partir de sus scoreboards publicos basados en CRCON, dejando claro que el historico reutilizable debe venir de esa capa y no de A2S ni de una implementacion previa ya descartada.
|
||||
|
||||
## Discovery Date
|
||||
|
||||
- Verificado el 2026-03-20 contra:
|
||||
- `https://scoreboard.comunidadhll.es/games`
|
||||
- `https://scoreboard.comunidadhll.es:5443/games`
|
||||
|
||||
## Main Finding
|
||||
|
||||
La fuente historica reutilizable mas estable disponible hoy es una API JSON publica expuesta por cada scoreboard, no el HTML renderizado de `/games`.
|
||||
|
||||
Las dos URLs de historial cargan una SPA con el mismo bundle frontend. Ese bundle usa `axios` con `baseURL: "/api"` y consulta endpoints JSON concretos:
|
||||
|
||||
- `GET /api/get_public_info`
|
||||
- `GET /api/get_live_scoreboard`
|
||||
- `GET /api/get_live_game_stats`
|
||||
- `GET /api/get_scoreboard_maps?page={page}&limit={limit}`
|
||||
- `GET /api/get_map_scoreboard?map_id={map_id}`
|
||||
|
||||
Por tanto, la estrategia recomendada no es parsear HTML de `/games`, sino consumir la capa JSON que alimenta ese frontend.
|
||||
|
||||
## Server Mapping
|
||||
|
||||
Cada scoreboard representa un servidor distinto:
|
||||
|
||||
- `https://scoreboard.comunidadhll.es`
|
||||
- `GET /api/get_public_info` identifica `#01 [ESP] Comunidad Hispana - discord.comunidadhll.es - Spa Onl`
|
||||
- `public_stats_port`: `7010`
|
||||
- `public_stats_port_https`: `7011`
|
||||
- `https://scoreboard.comunidadhll.es:5443`
|
||||
- `GET /api/get_public_info` identifica `#02 [ESP] Comunidad Hispana - discord.comunidadhll.es - Spa Onl`
|
||||
- `public_stats_port`: `7012`
|
||||
- `public_stats_port_https`: `7013`
|
||||
- `https://scoreboard.comunidadhll.es:3443`
|
||||
- tercer scoreboard comunitario reservado para la identidad estable `comunidad-hispana-03`
|
||||
- la capa de backend ya debe tratarlo como otra fuente CRCON independiente de las de `#01` y `#02`
|
||||
|
||||
## How Historical Data Is Loaded
|
||||
|
||||
### 1. History list
|
||||
|
||||
`GET /api/get_scoreboard_maps?page=1&limit=5`
|
||||
|
||||
Devuelve una lista paginada de partidas finalizadas con estructura JSON. Campos verificados:
|
||||
|
||||
- `page`
|
||||
- `page_size`
|
||||
- `total`
|
||||
- `maps[]`
|
||||
|
||||
Cada item de `maps[]` incluye al menos:
|
||||
|
||||
- `id`
|
||||
- `creation_time`
|
||||
- `start`
|
||||
- `end`
|
||||
- `server_number`
|
||||
- `map.id`
|
||||
- `map.pretty_name`
|
||||
- `map.image_name`
|
||||
- `map.game_mode`
|
||||
- `result.axis`
|
||||
- `result.allied`
|
||||
|
||||
Observacion importante:
|
||||
|
||||
- `player_stats` aparece vacio en la lista. Para metricas de jugadores hay que ir al endpoint de detalle.
|
||||
|
||||
### 2. Match detail
|
||||
|
||||
`GET /api/get_map_scoreboard?map_id={map_id}`
|
||||
|
||||
Devuelve el detalle historico de una partida concreta. Ejemplos verificados:
|
||||
|
||||
- servidor `#01`: `map_id=1561077`
|
||||
- servidor `#02`: `map_id=1561076`
|
||||
|
||||
Campos verificados a nivel de partida:
|
||||
|
||||
- `id`
|
||||
- `creation_time`
|
||||
- `start`
|
||||
- `end`
|
||||
- `server_number`
|
||||
- `map_name`
|
||||
- `map.pretty_name`
|
||||
- `result.axis`
|
||||
- `result.allied`
|
||||
- `player_stats[]`
|
||||
|
||||
Campos verificados a nivel de jugador dentro de `player_stats[]`:
|
||||
|
||||
- `id`
|
||||
- `player_id`
|
||||
- `player`
|
||||
- `steaminfo.id`
|
||||
- `steaminfo.profile.steamid` cuando existe
|
||||
- `map_id`
|
||||
- `kills`
|
||||
- `kills_by_type`
|
||||
- `kills_streak`
|
||||
- `deaths`
|
||||
- `deaths_by_type`
|
||||
- `teamkills`
|
||||
- `time_seconds`
|
||||
- `kills_per_minute`
|
||||
- `deaths_per_minute`
|
||||
- `kill_death_ratio`
|
||||
- `longest_life_secs`
|
||||
- `shortest_life_secs`
|
||||
- `combat`
|
||||
- `offense`
|
||||
- `defense`
|
||||
- `support`
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- `weapons`
|
||||
- `death_by_weapons`
|
||||
- `team.side`
|
||||
- `level`
|
||||
|
||||
Esto confirma que el scoreboard ya expone la base necesaria para rankings semanales por servidor como "top kills", junto con otras metricas reutilizables.
|
||||
|
||||
## Detail URLs And IDs
|
||||
|
||||
- La UI publica usa rutas tipo `/games/{id}`.
|
||||
- `GET https://scoreboard.comunidadhll.es/games/1561077` responde `200`.
|
||||
- `GET https://scoreboard.comunidadhll.es:5443/games/1561076` responde `200`.
|
||||
|
||||
Inferencia razonable:
|
||||
|
||||
- `/games/{id}` es la URL publica de detalle de partida.
|
||||
- el dato real se resuelve desde frontend llamando a `GET /api/get_map_scoreboard?map_id={id}`.
|
||||
|
||||
## Stable Historical Data Actually Available
|
||||
|
||||
A dia 2026-03-20, la capa JSON permite obtener de forma estable:
|
||||
|
||||
- servidor
|
||||
- por host del scoreboard
|
||||
- por `server_number`
|
||||
- por `get_public_info.name`
|
||||
- partida
|
||||
- `id`
|
||||
- `start`
|
||||
- `end`
|
||||
- `creation_time`
|
||||
- mapa
|
||||
- `map.id`
|
||||
- `map.pretty_name`
|
||||
- `game_mode`
|
||||
- `environment`
|
||||
- jugador
|
||||
- `player_id`
|
||||
- `player`
|
||||
- `steaminfo` parcial cuando existe
|
||||
- metricas
|
||||
- `kills`
|
||||
- `deaths`
|
||||
- `teamkills`
|
||||
- `kills_per_minute`
|
||||
- `kill_death_ratio`
|
||||
- `combat`
|
||||
- `offense`
|
||||
- `defense`
|
||||
- `support`
|
||||
- desglose por armas y tipos cuando aparece
|
||||
|
||||
## Pagination And Historical Depth
|
||||
|
||||
- La lista historica es paginada mediante `page` y `limit`.
|
||||
- El bundle observado usa por defecto `page=1` y `limit=50`.
|
||||
- En la verificacion:
|
||||
- servidor `#01` reporto `total: 23027`
|
||||
- servidor `#02` reporto `total: 18219`
|
||||
|
||||
Esto sugiere una profundidad historica amplia y apta para ingesta incremental paginada.
|
||||
|
||||
## Risks And Limits
|
||||
|
||||
- La fuente es publica, pero no hay contrato formal versionado publicado; sigue siendo una API no documentada externamente.
|
||||
- El frontend depende de rutas `/api/...` observadas en el bundle actual `v11.9.0`; una actualizacion futura podria renombrarlas.
|
||||
- `player_id` no parece homogeneo al 100%:
|
||||
- a veces coincide con SteamID
|
||||
- a veces aparece como hash o identificador alternativo
|
||||
- `steaminfo` puede venir completo, parcial o `null`; no debe asumirse como obligatorio.
|
||||
- Existen valores de calidad irregular en algunas partidas:
|
||||
- `shortest_life_secs` negativos
|
||||
- jugadores con tiempos atipicos
|
||||
- campos vacios o `unknown`
|
||||
- El HTML de `/games` no debe tomarse como base tecnica porque solo sirve la SPA shell y es mas fragil que consumir el JSON directo.
|
||||
- A2S sigue siendo util para estado actual, no para reconstruir historico de partidas ni ranking semanal retroactivo.
|
||||
|
||||
## Recommended Strategy For Following Tasks
|
||||
|
||||
### Ideal historical source
|
||||
|
||||
Usar directamente la API JSON publica de cada scoreboard CRCON:
|
||||
|
||||
- listar partidas con `GET /api/get_scoreboard_maps`
|
||||
- obtener detalle por partida con `GET /api/get_map_scoreboard`
|
||||
|
||||
### Realistic initial operating plan
|
||||
|
||||
1. Mantener separados los 2 orígenes:
|
||||
- `https://scoreboard.comunidadhll.es/api`
|
||||
- `https://scoreboard.comunidadhll.es:5443/api`
|
||||
2. Registrar por servidor:
|
||||
- host base del scoreboard
|
||||
- nombre publico devuelto por `get_public_info`
|
||||
- `server_number`
|
||||
3. Ingerir paginas historicas de forma incremental.
|
||||
4. Persistir una entidad de partida externa con `match_id = id`.
|
||||
5. Persistir filas de estadistica por jugador asociadas a `match_id` y servidor.
|
||||
6. Calcular agregados semanales desde esos datos persistidos, no consultando el scoreboard en cada request de frontend.
|
||||
|
||||
### Fallback if the JSON layer changes
|
||||
|
||||
- primer fallback: revalidar el bundle SPA para localizar las nuevas rutas `/api`
|
||||
- segundo fallback: parsear HTML solo como ultimo recurso y solo si el JSON deja de ser accesible
|
||||
|
||||
## Explicitly Not Recommended
|
||||
|
||||
- No basar el historico en A2S.
|
||||
- No reutilizar como base de arquitectura una implementacion historica previa ya descartada.
|
||||
- No tomar el HTML de `/games` como fuente principal.
|
||||
- No disenar todavia la UI historica final.
|
||||
|
||||
## Repository Impact
|
||||
|
||||
El repositorio ya tenia una pista correcta en la landing al enlazar ambos scoreboards, pero no existia documentacion tecnica del origen real de historico.
|
||||
|
||||
Tambien se detecto un rastro de implementacion previa no reutilizable:
|
||||
|
||||
- `backend/app/payloads.py` importa `.historical_storage` para un flujo de `weekly_top_kills`
|
||||
- el archivo `backend/app/historical_storage.py` no existe
|
||||
|
||||
Ese estado confirma que cualquier intento previo de ranking historico no debe considerarse base valida para la siguiente fase. La nueva fase debe reconstruirse desde la fuente CRCON JSON documentada aqui.
|
||||
66
docs/historical-data-quality-notes.md
Normal file
66
docs/historical-data-quality-notes.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Historical Data Quality Notes
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-20
|
||||
|
||||
## Scope
|
||||
|
||||
Validacion local del historico CRCON persistido en `backend/data/hll_vietnam_dev.sqlite3`
|
||||
para los servidores:
|
||||
|
||||
- `comunidad-hispana-01`
|
||||
- `comunidad-hispana-02`
|
||||
|
||||
## Findings Before Correction
|
||||
|
||||
- habia jugadores fragmentados entre claves `steam:*`, `steaminfo:*`,
|
||||
`crcon-player:*` e incluso claves legacy sin prefijo
|
||||
- algunas filas usaban `steaminfo.id` corto como si fuera `steam_id`, lo que no
|
||||
representaba un SteamID real
|
||||
- existian partidas duplicadas por sesion cuando una partida en curso quedaba
|
||||
persistida con id sintetico y luego aparecia cerrada con id CRCON numerico
|
||||
- el ranking semanal podia contar esas partidas transitorias porque aceptaba
|
||||
filas sin `ended_at`
|
||||
|
||||
## Corrections Applied
|
||||
|
||||
- la identidad de jugador ahora prioriza:
|
||||
- `steaminfo.profile.steamid`
|
||||
- `player_id` cuando ya parece un SteamID real
|
||||
- `player_id` como `crcon-player:*`
|
||||
- `steaminfo.id` solo como ultimo fallback
|
||||
- la inicializacion del storage fusiona jugadores duplicados y reasigna sus
|
||||
estadisticas por partida
|
||||
- la inicializacion del storage fusiona partidas duplicadas por
|
||||
`(servidor, started_at, mapa)` cuando la fila mas completa ya representa la
|
||||
partida final cerrada
|
||||
- `weekly-top-kills` filtra solo partidas cerradas con `ended_at`
|
||||
|
||||
## Final Local Snapshot After Correction
|
||||
|
||||
- partidas historicas: `12`
|
||||
- jugadores historicos: `510`
|
||||
- filas `historical_player_match_stats`: `914`
|
||||
- distribucion:
|
||||
- `comunidad-hispana-01`: `7` partidas, `487` jugadores unicos, `859` filas
|
||||
- `comunidad-hispana-02`: `5` partidas, `44` jugadores unicos, `55` filas
|
||||
|
||||
## Checks Performed
|
||||
|
||||
- sin duplicados por `steam_id`
|
||||
- sin duplicados por `source_player_id`
|
||||
- sin duplicados de nombre normalizado en el dataset local actual
|
||||
- sin partidas abiertas restantes (`ended_at IS NULL`)
|
||||
- sin duplicados por misma combinacion de servidor, `started_at` y mapa
|
||||
- el ranking semanal devuelve resultados separados por servidor y basados solo
|
||||
en partidas cerradas dentro de la ventana movil de 7 dias
|
||||
|
||||
## Notes
|
||||
|
||||
- el volumen actual sigue siendo pequeno y claramente parcial; la calidad
|
||||
estructural queda validada, pero no sustituye un bootstrap historico mas
|
||||
profundo cuando se quiera construir UI historica propia
|
||||
- siguen existiendo partidas con muy pocos jugadores en el dataset local
|
||||
actual; por ahora se conservan porque no son un problema de integridad, sino
|
||||
una caracteristica del muestreo ingerido hasta hoy
|
||||
194
docs/historical-domain-model.md
Normal file
194
docs/historical-domain-model.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Historical Domain Model
|
||||
|
||||
## Objective
|
||||
|
||||
Definir la base minima de dominio y persistencia para historico de partidas y
|
||||
metricas por jugador obtenidas desde la capa JSON publica de los scoreboards
|
||||
CRCON de Comunidad Hispana.
|
||||
|
||||
## Scope
|
||||
|
||||
Esta capa cubre solo historico persistido en backend:
|
||||
|
||||
- identidad estable de los 3 servidores historicos
|
||||
- partidas cerradas o actualizadas desde CRCON
|
||||
- mapas asociados a esas partidas
|
||||
- identidad reutilizable de jugadores
|
||||
- estadisticas de jugador por partida
|
||||
- trazabilidad de ejecuciones de ingesta
|
||||
- snapshots precalculados para lectura rapida
|
||||
|
||||
No sustituye ni modifica el flujo actual de snapshots live via A2S.
|
||||
|
||||
## Stable Identities
|
||||
|
||||
### Server
|
||||
|
||||
- tabla: `historical_servers`
|
||||
- clave estable: `slug`
|
||||
- ejemplos:
|
||||
- `comunidad-hispana-01`
|
||||
- `comunidad-hispana-02`
|
||||
- `comunidad-hispana-03`
|
||||
- atributos de soporte:
|
||||
- `scoreboard_base_url`
|
||||
- `server_number`
|
||||
- `source_kind`
|
||||
|
||||
La capa de lectura y snapshots admite además una clave lógica adicional:
|
||||
|
||||
- `all-servers`
|
||||
|
||||
Esta clave no representa una fila física extra en `historical_servers`; es una
|
||||
vista agregada sobre los tres servidores históricos reales para rankings y
|
||||
resúmenes globales.
|
||||
|
||||
### Match
|
||||
|
||||
- tabla: `historical_matches`
|
||||
- clave estable: `(historical_server_id, external_match_id)`
|
||||
- `external_match_id` corresponde al `id` devuelto por CRCON para cada partida
|
||||
- razon:
|
||||
- el `id` de partida es estable dentro de cada scoreboard
|
||||
- se conserva separado por servidor para evitar asumir unicidad global sin
|
||||
contrato formal
|
||||
|
||||
### Player
|
||||
|
||||
- tabla: `historical_players`
|
||||
- clave estable: `stable_player_key`
|
||||
- estrategia de identidad:
|
||||
1. `steam:{steamid}` cuando existe `steaminfo.profile.steamid`
|
||||
2. `steaminfo:{id}` cuando existe `steaminfo.id`
|
||||
3. `crcon-player:{player_id}` cuando existe `player_id`
|
||||
4. `name:{normalized-name}` como ultimo fallback
|
||||
|
||||
La prioridad evita perder continuidad cuando CRCON expone SteamID. Los
|
||||
fallbacks quedan documentados porque la calidad del origen no es totalmente
|
||||
uniforme.
|
||||
|
||||
### Player Stats Per Match
|
||||
|
||||
- tabla: `historical_player_match_stats`
|
||||
- clave estable: `(historical_match_id, historical_player_id)`
|
||||
- efecto:
|
||||
- la misma partida puede reingestarse sin duplicar filas
|
||||
- si una partida cambia despues, la fila se actualiza por `UPSERT`
|
||||
|
||||
### Ingestion Run
|
||||
|
||||
- tabla: `historical_ingestion_runs`
|
||||
- registra:
|
||||
- tipo de ejecucion (`bootstrap` o `incremental`)
|
||||
- inicio y fin
|
||||
- estado
|
||||
- paginas procesadas
|
||||
- matches vistos
|
||||
- inserts y updates
|
||||
|
||||
### Precomputed Snapshot
|
||||
|
||||
- directorio: `backend/data/snapshots/<server_key>/`
|
||||
- identidad estable:
|
||||
- `server_key`
|
||||
- `snapshot_type`
|
||||
- `metric`
|
||||
- `window`
|
||||
- razon:
|
||||
- permite exponer resumen, rankings y partidas recientes sin recalcular
|
||||
agregados pesados en cada request
|
||||
- mantiene metadatos operativos sobre frescura y rango fuente como artefactos
|
||||
JSON inspeccionables
|
||||
|
||||
## Data Model
|
||||
|
||||
### `historical_servers`
|
||||
|
||||
Fuente historica por scoreboard CRCON.
|
||||
|
||||
### `historical_maps`
|
||||
|
||||
Catalogo reutilizable de mapas usando `map.id` cuando existe.
|
||||
|
||||
### `historical_matches`
|
||||
|
||||
Partida historica persistida con:
|
||||
|
||||
- servidor
|
||||
- identidad externa
|
||||
- tiempos (`creation_time`, `start`, `end`)
|
||||
- mapa y metadatos visibles
|
||||
- resultado axis/allied
|
||||
- referencia de procedencia
|
||||
|
||||
### `historical_players`
|
||||
|
||||
Identidad reutilizable del jugador entre partidas y servidores.
|
||||
|
||||
### `historical_player_match_stats`
|
||||
|
||||
Metricas por jugador y partida con al menos:
|
||||
|
||||
- kills
|
||||
- deaths
|
||||
- teamkills
|
||||
- time_seconds
|
||||
- kills_per_minute
|
||||
- deaths_per_minute
|
||||
- kill_death_ratio
|
||||
- combat
|
||||
- offense
|
||||
- defense
|
||||
- support
|
||||
|
||||
### `historical_ingestion_runs`
|
||||
|
||||
Trazabilidad operativa para bootstrap y refresh incremental.
|
||||
|
||||
### `backend/data/snapshots/<server_key>/*.json`
|
||||
|
||||
Payloads JSON precalculados listos para lectura rapida desde API/UI con:
|
||||
|
||||
- `server_key`
|
||||
- `snapshot_type`
|
||||
- `metric`
|
||||
- `window`
|
||||
- `payload`
|
||||
- `generated_at`
|
||||
- `source_range_start`
|
||||
- `source_range_end`
|
||||
- `is_stale`
|
||||
|
||||
## Idempotency Strategy
|
||||
|
||||
- servidores sembrados de forma declarativa y actualizables por `slug`
|
||||
- partidas persistidas con `UPSERT` por `(historical_server_id, external_match_id)`
|
||||
- jugadores persistidos con `UPSERT` por `stable_player_key`
|
||||
- estadisticas por jugador actualizadas con `UPSERT` por
|
||||
`(historical_match_id, historical_player_id)`
|
||||
- el refresco incremental usa una ventana de solape temporal para volver a leer
|
||||
partidas recientes y absorber cambios tardios sin rehacer todo el historico
|
||||
- los snapshots precalculados usan reemplazo por identidad logica de archivo
|
||||
para refrescar el payload sin crear duplicados
|
||||
|
||||
## Query Readiness
|
||||
|
||||
La estructura soporta ya consultas futuras como:
|
||||
|
||||
- top kills de la ultima semana por servidor
|
||||
- top muertes, soporte y partidas de 100+ kills desde una capa cacheada
|
||||
- partidas recientes por servidor
|
||||
- rankings y resumenes globales con la clave logica `all-servers`
|
||||
- mapas jugados y frecuencia
|
||||
- agregados por jugador sobre ventanas temporales
|
||||
|
||||
## Separation From Live State
|
||||
|
||||
- live state actual: `server_snapshots` via A2S
|
||||
- historico persistido: `historical_*` via CRCON scoreboard JSON
|
||||
- snapshots precalculados: archivos JSON bajo `backend/data/snapshots/`
|
||||
generados desde el mismo historico persistido
|
||||
|
||||
Ambas lineas siguen compartiendo el mismo SQLite local para el estado live y el
|
||||
historico bruto, pero la capa de snapshots UI queda desacoplada como archivos
|
||||
en disco para simplificar inspeccion, servicio y depuracion.
|
||||
57
docs/historical-rcon-backfill.md
Normal file
57
docs/historical-rcon-backfill.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Historical RCON AdminLog Backfill
|
||||
|
||||
The RCON/AdminLog backfill is an explicit operator command. It does not run on
|
||||
backend startup or on web requests.
|
||||
|
||||
Run it through the advanced worker image:
|
||||
|
||||
```powershell
|
||||
docker compose run --rm rcon-historical-worker python -m app.rcon_historical_backfill --ensure-recent-matches 100 --servers comunidad-hispana-01,comunidad-hispana-02 --dry-run
|
||||
```
|
||||
|
||||
Before a real manual backfill, stop the writer services to avoid waiting on the
|
||||
shared writer lock:
|
||||
|
||||
```powershell
|
||||
docker compose --profile advanced stop historical-runner rcon-historical-worker
|
||||
```
|
||||
|
||||
Restart them afterwards:
|
||||
|
||||
```powershell
|
||||
docker compose --profile advanced up -d historical-runner rcon-historical-worker
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```powershell
|
||||
docker compose run --rm rcon-historical-worker python -m app.rcon_historical_backfill --ensure-recent-matches 100 --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
docker compose run --rm rcon-historical-worker python -m app.rcon_historical_backfill --ensure-current-month --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
docker compose run --rm rcon-historical-worker python -m app.rcon_historical_backfill --ensure-leaderboard-windows --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
docker compose run --rm rcon-historical-worker python -m app.rcon_historical_backfill --ensure-recent-matches 100 --servers comunidad-hispana-01,comunidad-hispana-02 --chunk-hours 6 --sleep-seconds 1 --max-days-back 45 --regenerate-snapshots
|
||||
```
|
||||
|
||||
Direct module examples:
|
||||
|
||||
```powershell
|
||||
python -m app.rcon_historical_backfill --from 2026-05-01 --to now --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
python -m app.rcon_historical_backfill --ensure-recent-matches 100 --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
python -m app.rcon_historical_backfill --ensure-current-month --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
python -m app.rcon_historical_backfill --ensure-leaderboard-windows --servers comunidad-hispana-01,comunidad-hispana-02
|
||||
```
|
||||
|
||||
Useful configuration:
|
||||
|
||||
- `HLL_RCON_BACKFILL_CHUNK_HOURS`, default `6`
|
||||
- `HLL_RCON_BACKFILL_SLEEP_SECONDS`, default `1`
|
||||
- `HLL_RCON_BACKFILL_MAX_DAYS_BACK`, default `45`
|
||||
- `HLL_BACKEND_RCON_ADMIN_LOG_LOOKBACK_MINUTES`, for normal prospective worker capture only
|
||||
|
||||
The command only selects `comunidad-hispana-01` and `comunidad-hispana-02` by
|
||||
default. `comunidad-hispana-03` is not included unless it is configured in
|
||||
`HLL_BACKEND_RCON_TARGETS` and explicitly passed with `--servers`.
|
||||
|
||||
Monthly RCON leaderboards use the previous calendar month on days 1 through 7.
|
||||
From day 8 onward they use the current calendar month. Weekly RCON leaderboards
|
||||
use the current week only when the current week has enough closed materialized
|
||||
matches; otherwise they fall back to the previous week.
|
||||
197
docs/monthly-mvp-ranking-scoring-design.md
Normal file
197
docs/monthly-mvp-ranking-scoring-design.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Monthly MVP Ranking Scoring Design
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-24
|
||||
|
||||
## Objective
|
||||
|
||||
Definir una formula V1 precisa y auditable para un ranking mensual de mejores
|
||||
jugadores usando solo metricas ya persistidas y suficientemente fiables en el
|
||||
repositorio.
|
||||
|
||||
## Evidence Base
|
||||
|
||||
This proposal is based on:
|
||||
|
||||
- `docs/monthly-player-ranking-data-audit.md`
|
||||
- `docs/historical-domain-model.md`
|
||||
- `docs/historical-data-quality-notes.md`
|
||||
- `backend/app/historical_models.py`
|
||||
- `backend/app/historical_storage.py`
|
||||
- `backend/app/payloads.py`
|
||||
|
||||
The design assumes the existing monthly window already used by the backend:
|
||||
|
||||
- UTC calendar month
|
||||
- closed matches only
|
||||
- fallback to the previous closed month only when the current month has no
|
||||
closed matches at all
|
||||
|
||||
## V1 Meaning Of "Best Player Of The Month"
|
||||
|
||||
V1 should not mean "highest raw kills only" and should not pretend to measure
|
||||
full tactical impact that the project does not persist yet.
|
||||
|
||||
For this project, "monthly MVP" in V1 means:
|
||||
|
||||
- sustained offensive contribution across the month
|
||||
- meaningful team contribution through support
|
||||
- good efficiency without rewarding one or two short outlier matches
|
||||
- enough participation to make the result credible
|
||||
|
||||
This is therefore a balanced MVP model with a light offensive bias.
|
||||
|
||||
## Metrics Included In V1
|
||||
|
||||
Included metrics:
|
||||
|
||||
- total kills
|
||||
- total support
|
||||
- total time played
|
||||
- KPM derived from monthly totals
|
||||
- KDA derived from monthly totals
|
||||
- optional teamkill penalty
|
||||
- matches played as an eligibility guard
|
||||
|
||||
Derived metrics must be recomputed from monthly totals, not from the average of
|
||||
per-match ratios:
|
||||
|
||||
- `kpm = total_kills / max(total_time_minutes, 1)`
|
||||
- `kda = total_kills / max(total_deaths, 1)`
|
||||
|
||||
## Metrics Explicitly Out Of Scope For V1
|
||||
|
||||
Do not include in V1:
|
||||
|
||||
- combat
|
||||
- offense
|
||||
- defense
|
||||
- matches over 100 kills
|
||||
- win/loss context
|
||||
- weapons profile
|
||||
- kill streaks or life-span fields
|
||||
- duels, `most_killed`, `death_by`
|
||||
- garrisons, OPs or tactical events not confirmed as persisted
|
||||
|
||||
Reason:
|
||||
|
||||
- some are useful but would complicate the first release without improving
|
||||
reliability enough
|
||||
- others are not persisted today or are not confirmed with stable semantics
|
||||
|
||||
## Eligibility Rules
|
||||
|
||||
A player is eligible for the monthly MVP ranking only if all conditions hold:
|
||||
|
||||
- played at least `6` closed matches in the selected month and scope
|
||||
- accumulated at least `21600` seconds (`6` hours) of play time in that month
|
||||
- has non-null persisted stats for kills, deaths, support and time
|
||||
|
||||
These gates are intentionally dual:
|
||||
|
||||
- match count blocks one-match outliers
|
||||
- time played blocks short-session inflation
|
||||
|
||||
## Scope Recommendation
|
||||
|
||||
V1 should be computed in both scopes from the same formula:
|
||||
|
||||
- per server
|
||||
- global aggregate using `all-servers`
|
||||
|
||||
Publication recommendation:
|
||||
|
||||
- default visible ranking: per server
|
||||
- secondary comparable view: global aggregate
|
||||
|
||||
Why:
|
||||
|
||||
- per-server ranking is easier to interpret and fairer for each community shard
|
||||
- the repository already supports the logical aggregate `all-servers`
|
||||
- using one formula for both scopes avoids redesign later
|
||||
|
||||
## Normalized Component Scores
|
||||
|
||||
For each month and scope, first aggregate one row per eligible player.
|
||||
|
||||
Then calculate these normalized component scores on a `0..100` scale:
|
||||
|
||||
- `kills_score = 100 * ln(1 + total_kills) / ln(1 + max_total_kills_eligible)`
|
||||
- `support_score = 100 * ln(1 + total_support) / ln(1 + max_total_support_eligible)`
|
||||
- `kpm_score = 100 * ln(1 + kpm) / ln(1 + max_kpm_eligible)`
|
||||
- `kda_score = 100 * ln(1 + kda) / ln(1 + max_kda_eligible)`
|
||||
- `participation_score = 100 * min(1, total_time_seconds / 28800)`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- `ln(1 + x)` dampens extreme leaders without hiding real advantage
|
||||
- participation reaches full score at `8` hours
|
||||
- all `max_*_eligible` references are calculated inside the same month and scope
|
||||
|
||||
## V1 Scoring Formula
|
||||
|
||||
Recommended V1 monthly MVP score:
|
||||
|
||||
`mvp_score = 0.35 * kills_score + 0.20 * support_score + 0.20 * kpm_score + 0.15 * kda_score + 0.10 * participation_score - teamkill_penalty`
|
||||
|
||||
Weight rationale:
|
||||
|
||||
- `35%` kills: offensive impact should matter most in a first public ranking
|
||||
- `20%` support: keeps the model closer to MVP than to a pure frag ranking
|
||||
- `20%` KPM: rewards productive time, not only volume
|
||||
- `15%` KDA: rewards cleaner performance but keeps it below kills volume
|
||||
- `10%` participation: favors sustained monthly presence without turning the
|
||||
ranking into a pure grind chart
|
||||
|
||||
## Teamkill Penalty
|
||||
|
||||
Use a small optional penalty in V1:
|
||||
|
||||
- `teamkill_penalty = min(6, total_teamkills * 0.5)`
|
||||
|
||||
Effect:
|
||||
|
||||
- `1` teamkill subtracts `0.5`
|
||||
- `4` teamkills subtract `2`
|
||||
- penalty caps at `6`
|
||||
|
||||
This keeps the penalty visible without letting it dominate the ranking.
|
||||
|
||||
## Tie-Break Rules
|
||||
|
||||
If two players have the same `mvp_score`, resolve ties in this order:
|
||||
|
||||
1. higher `participation_score`
|
||||
2. higher `kills_score`
|
||||
3. higher `support_score`
|
||||
4. lower `total_teamkills`
|
||||
5. alphabetical `display_name`
|
||||
6. stable player key as final deterministic fallback
|
||||
|
||||
## Why This V1 Is Reasonable
|
||||
|
||||
This design is defendable for a first release because it:
|
||||
|
||||
- uses only metrics already persisted with strong coverage
|
||||
- recomputes efficiency from totals instead of averaging noisy per-match ratios
|
||||
- blocks absurd winners from tiny samples with explicit eligibility gates
|
||||
- stays interpretable enough to explain in product copy
|
||||
- can be implemented from current monthly aggregates without new ingestion or
|
||||
schema work
|
||||
|
||||
## V2 Expansion Path
|
||||
|
||||
V2 can extend the same structure without redesigning the whole ranking:
|
||||
|
||||
- add combat, offense and defense as extra weighted components
|
||||
- add win/loss context only where team scores are present and validated
|
||||
- review whether teamkill penalty should become rate-based instead of absolute
|
||||
- later add tactical metrics only after deliberate persistence work
|
||||
|
||||
The important constraint for V2 is to preserve the same shape:
|
||||
|
||||
- explicit eligibility
|
||||
- normalized component scores
|
||||
- weighted sum
|
||||
- deterministic tie-breaks
|
||||
243
docs/monthly-mvp-v2-scoring-design.md
Normal file
243
docs/monthly-mvp-v2-scoring-design.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Monthly MVP V2 Scoring Design
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-24
|
||||
|
||||
## Objective
|
||||
|
||||
Definir una formula V2 precisa, explicable e implementable para el MVP mensual
|
||||
usando la base V1 ya aprobada y solo las senales avanzadas V2 que hoy tienen
|
||||
soporte real en la repo.
|
||||
|
||||
## Evidence Base
|
||||
|
||||
This proposal is based on:
|
||||
|
||||
- `docs/monthly-mvp-ranking-scoring-design.md`
|
||||
- `docs/monthly-player-ranking-data-audit.md`
|
||||
- `docs/player-event-pipeline-v2-design.md`
|
||||
- `backend/app/monthly_mvp.py`
|
||||
- `backend/app/player_event_aggregates.py`
|
||||
- `backend/app/historical_snapshots.py`
|
||||
- `backend/app/payloads.py`
|
||||
|
||||
## Design Position
|
||||
|
||||
V2 should not replace the V1 logic with a radically different opaque model.
|
||||
|
||||
The correct direction is:
|
||||
|
||||
- keep V1 as the stable baseline
|
||||
- preserve the same monthly UTC window and closed-match policy
|
||||
- add a small set of advanced event-derived signals with limited weight
|
||||
- avoid weapon-type or kill-type complexity until the source is richer
|
||||
|
||||
## Meaning Of MVP In V2
|
||||
|
||||
V2 still means "best monthly player", not "best fragger only".
|
||||
|
||||
Compared with V1, V2 should reward:
|
||||
|
||||
- sustained offensive output
|
||||
- team contribution
|
||||
- efficiency over the month
|
||||
- cleaner player-vs-player control in repeated encounters
|
||||
- better discipline through a stricter teamkill penalty
|
||||
|
||||
## Signals Included In V2
|
||||
|
||||
V2 keeps these V1 signals:
|
||||
|
||||
- total kills
|
||||
- total support
|
||||
- KPM recomputed from monthly totals
|
||||
- KDA recomputed from monthly totals
|
||||
- participation based on monthly time played
|
||||
- monthly teamkills as penalty input
|
||||
|
||||
V2 adds these advanced signals:
|
||||
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- net duel summaries
|
||||
|
||||
These signals are used only as modest scoring components, not as the core of
|
||||
the ranking.
|
||||
|
||||
## Signals Explicitly Excluded From V2 Formula
|
||||
|
||||
Do not score these yet:
|
||||
|
||||
- weapon-type weighting
|
||||
- kill-category weighting
|
||||
- weapon variety bonus
|
||||
- `death_by_weapons`
|
||||
- combat, offense and defense
|
||||
- win/loss context
|
||||
|
||||
Reason:
|
||||
|
||||
- the current CRCON-derived V2 layer is partial and summary-based
|
||||
- weapon and type semantics are not robust enough for a serious weighted score
|
||||
- adding too many low-confidence knobs would make V2 harder to defend than V1
|
||||
|
||||
Weapon kills remain useful for product readouts and future analysis, but not as
|
||||
a weighted scoring factor in this phase.
|
||||
|
||||
## Eligibility Rules
|
||||
|
||||
Player eligibility for V2 should remain identical to V1:
|
||||
|
||||
- at least `6` closed matches in the selected month and scope
|
||||
- at least `21600` seconds (`6` hours) played in the selected month and scope
|
||||
- non-null monthly totals for kills, deaths, support and time
|
||||
|
||||
Additional publication gate for the ranking itself:
|
||||
|
||||
- publish V2 only when the selected month and scope have matching player-event
|
||||
coverage for that same `month_key`
|
||||
|
||||
This avoids ranking a month with V1 totals but missing V2 event coverage.
|
||||
|
||||
## Derived Advanced Metrics
|
||||
|
||||
For each eligible player-month, derive:
|
||||
|
||||
- `most_killed_count`
|
||||
- kills against the player most often killed by this player in the month
|
||||
- `death_by_count`
|
||||
- deaths suffered from the player that killed this player most often in the
|
||||
month
|
||||
- `rivalry_edge_raw = max(0, most_killed_count - death_by_count)`
|
||||
- `duel_control_raw`
|
||||
- sum of positive `net_duel_value` across the player's top `3` duel pairs in
|
||||
the selected month and scope
|
||||
|
||||
Then normalize:
|
||||
|
||||
- `rivalry_edge_score = 100 * ln(1 + rivalry_edge_raw) / ln(1 + max_rivalry_edge_raw_eligible)`
|
||||
- `duel_control_score = 100 * ln(1 + duel_control_raw) / ln(1 + max_duel_control_raw_eligible)`
|
||||
|
||||
## Small-Sample Treatment
|
||||
|
||||
Advanced event signals should be damped on low-volume months.
|
||||
|
||||
Use:
|
||||
|
||||
- `advanced_confidence = min(1, total_kills / 35)`
|
||||
|
||||
Effect:
|
||||
|
||||
- under `35` kills, advanced components contribute only partially
|
||||
- at `35+` kills, the full advanced weight is available
|
||||
|
||||
This keeps V2 from overreacting to tiny rivalry samples.
|
||||
|
||||
## Normalized Core Component Scores
|
||||
|
||||
V2 keeps the same V1 normalization style on a `0..100` scale:
|
||||
|
||||
- `kills_score = 100 * ln(1 + total_kills) / ln(1 + max_total_kills_eligible)`
|
||||
- `support_score = 100 * ln(1 + total_support) / ln(1 + max_total_support_eligible)`
|
||||
- `kpm_score = 100 * ln(1 + kpm) / ln(1 + max_kpm_eligible)`
|
||||
- `kda_score = 100 * ln(1 + kda) / ln(1 + max_kda_eligible)`
|
||||
- `participation_score = 100 * min(1, total_time_seconds / 28800)`
|
||||
|
||||
## V2 Teamkill Penalty
|
||||
|
||||
V2 should be slightly stricter than V1 on discipline.
|
||||
|
||||
Use:
|
||||
|
||||
- `teamkill_penalty_v2 = min(8, total_teamkills * 0.75)`
|
||||
|
||||
Effect:
|
||||
|
||||
- `1` teamkill subtracts `0.75`
|
||||
- `4` teamkills subtract `3`
|
||||
- penalty caps at `8`
|
||||
|
||||
## V2 Scoring Formula
|
||||
|
||||
Recommended V2 monthly MVP score:
|
||||
|
||||
`mvp_v2_score = 0.30 * kills_score + 0.18 * support_score + 0.18 * kpm_score + 0.12 * kda_score + 0.10 * participation_score + advanced_confidence * (0.07 * rivalry_edge_score + 0.05 * duel_control_score) - teamkill_penalty_v2`
|
||||
|
||||
Weight rationale:
|
||||
|
||||
- `30%` kills keeps offense as the main visible driver
|
||||
- `18%` support preserves MVP rather than pure frag logic
|
||||
- `18%` KPM rewards productive time
|
||||
- `12%` KDA rewards cleaner performance without dominating the table
|
||||
- `10%` participation keeps monthly presence relevant
|
||||
- `7%` rivalry edge rewards players who repeatedly finish ahead in their
|
||||
strongest recurring encounter
|
||||
- `5%` duel control adds a second advanced signal but keeps it clearly bounded
|
||||
|
||||
## Why Weapon Kills Are Not Weighted Yet
|
||||
|
||||
The repository can already expose kills by weapon, but the current source layer:
|
||||
|
||||
- is summary-based, not a full raw kill feed
|
||||
- does not yet prove a stable weapon taxonomy for competitive weighting
|
||||
- would invite fragile distinctions such as tank vs infantry vs artillery too
|
||||
early
|
||||
|
||||
Decision:
|
||||
|
||||
- do not weight kills by weapon in V2
|
||||
- do not assign bonus or penalty by weapon type
|
||||
- keep weapon-kill outputs as audit and UI-facing data only
|
||||
|
||||
## Tie-Break Rules
|
||||
|
||||
If two players have the same `mvp_v2_score`, resolve ties in this order:
|
||||
|
||||
1. higher `advanced_confidence`
|
||||
2. higher `participation_score`
|
||||
3. higher `kills_score`
|
||||
4. higher `rivalry_edge_score`
|
||||
5. lower `total_teamkills`
|
||||
6. alphabetical `display_name`
|
||||
7. stable player key as final deterministic fallback
|
||||
|
||||
## Coexistence With V1
|
||||
|
||||
V1 and V2 should coexist explicitly:
|
||||
|
||||
- `V1` remains the stable default ranking
|
||||
- `V2` is a separate ranking version with its own `ranking_version`
|
||||
- both versions should use the same month and scope selectors
|
||||
- V2 should never overwrite or reinterpret the V1 payload contract
|
||||
|
||||
## Implementation Guidance For Next Task
|
||||
|
||||
The backend task should compute V2 from:
|
||||
|
||||
- the same monthly player totals already used by V1
|
||||
- direct player-event monthly aggregates derived from the raw ledger
|
||||
|
||||
Required per-player V2 outputs:
|
||||
|
||||
- `mvp_v2_score`
|
||||
- `advanced_confidence`
|
||||
- `rivalry_edge_raw`
|
||||
- `duel_control_raw`
|
||||
- `component_scores`
|
||||
- `teamkill_penalty_v2`
|
||||
|
||||
Recommended `ranking_version`:
|
||||
|
||||
- `v2`
|
||||
|
||||
## Final Recommendation
|
||||
|
||||
The correct V2 for the current repository is an incremental evolution of V1:
|
||||
|
||||
- keep the same explainable weighted-score structure
|
||||
- add only `most_killed` / `death_by` / duel-derived pressure signals
|
||||
- make discipline stricter
|
||||
- refuse weapon-type weighting until the signal quality improves
|
||||
|
||||
This yields a V2 that is materially richer than V1 without becoming speculative.
|
||||
246
docs/monthly-player-ranking-data-audit.md
Normal file
246
docs/monthly-player-ranking-data-audit.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Monthly Player Ranking Data Audit
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-24
|
||||
|
||||
## Scope
|
||||
|
||||
Auditoria tecnica del estado real de datos para un futuro ranking mensual de
|
||||
"mejores jugadores" usando:
|
||||
|
||||
- codigo y esquema historico del backend
|
||||
- persistencia local en `backend/data/hll_vietnam_dev.sqlite3`
|
||||
- snapshots historicos ya generados en `backend/data/snapshots/`
|
||||
- discovery ya documentada de la fuente CRCON/scoreboard
|
||||
|
||||
No se implementa todavia ninguna formula de ranking, tabla nueva ni cambio de
|
||||
UI.
|
||||
|
||||
## Evidence Reviewed
|
||||
|
||||
- `backend/app/historical_models.py`
|
||||
- `backend/app/historical_storage.py`
|
||||
- `backend/app/historical_ingestion.py`
|
||||
- `backend/app/historical_snapshots.py`
|
||||
- `backend/app/historical_snapshot_storage.py`
|
||||
- `backend/app/payloads.py`
|
||||
- `docs/historical-domain-model.md`
|
||||
- `docs/historical-data-quality-notes.md`
|
||||
- `docs/historical-crcon-source-discovery.md`
|
||||
- `docs/historical-coverage-report.md`
|
||||
|
||||
## Current Persisted State
|
||||
|
||||
Local SQLite currently contains:
|
||||
|
||||
- `historical_servers`: `3`
|
||||
- `historical_matches`: `9638`
|
||||
- `historical_players`: `163506`
|
||||
- `historical_player_match_stats`: `1062244`
|
||||
- `historical_ingestion_runs`: `32`
|
||||
|
||||
Coverage visible in the local database today:
|
||||
|
||||
- `comunidad-hispana-01`: `8602` matches, from `2024-05-17T20:48:40Z` to `2026-03-23T16:01:20Z`
|
||||
- `comunidad-hispana-02`: `753` matches, from `2025-11-04T17:10:19Z` to `2026-03-23T18:58:06Z`
|
||||
- `comunidad-hispana-03`: `283` matches, from `2026-01-14T22:34:18Z` to `2026-03-08T18:11:52Z`
|
||||
|
||||
Important quality notes from the local dataset:
|
||||
|
||||
- all `historical_player_match_stats` rows have populated values for kills,
|
||||
deaths, teamkills, time, KPM, KDA, combat, offense, defense, support, level
|
||||
and team side
|
||||
- `85,270 / 163,506` players have SteamID; the rest currently depend on
|
||||
`crcon-player:*` identity, so identity continuity is usable but not equally
|
||||
strong for every player
|
||||
- all persisted matches have start/end timestamps, map and game mode
|
||||
- `7,961 / 9,638` persisted matches currently have both allied/axis score
|
||||
|
||||
## What Is Persisted Today
|
||||
|
||||
### Match level
|
||||
|
||||
Persisted per match:
|
||||
|
||||
- server
|
||||
- external match id
|
||||
- creation/start/end timestamps
|
||||
- map name, pretty name, game mode, image
|
||||
- allied score
|
||||
- axis score
|
||||
|
||||
Not persisted at match level:
|
||||
|
||||
- raw full CRCON JSON payload
|
||||
- derived win/loss per player
|
||||
- any tactical event ledger
|
||||
|
||||
### Player identity level
|
||||
|
||||
Persisted per player:
|
||||
|
||||
- stable player key
|
||||
- display name
|
||||
- SteamID when available
|
||||
- source player id
|
||||
- first seen / last seen
|
||||
|
||||
### Player per match level
|
||||
|
||||
Persisted per player-match row:
|
||||
|
||||
- level
|
||||
- team side
|
||||
- kills
|
||||
- deaths
|
||||
- teamkills
|
||||
- time seconds
|
||||
- kills per minute
|
||||
- deaths per minute
|
||||
- kill/death ratio
|
||||
- combat
|
||||
- offense
|
||||
- defense
|
||||
- support
|
||||
|
||||
## What Exists In CRCON Source But Is Not Persisted
|
||||
|
||||
The documented CRCON detail payload already exposes fields that the project does
|
||||
not currently store:
|
||||
|
||||
- `kills_by_type`
|
||||
- `kills_streak`
|
||||
- `longest_life_secs`
|
||||
- `shortest_life_secs`
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- `weapons`
|
||||
- `death_by_weapons`
|
||||
|
||||
These fields are visible in the source discovery, but the current upsert logic
|
||||
only persists the smaller normalized subset listed above.
|
||||
|
||||
## What Was Not Confirmed As Available
|
||||
|
||||
The current repository evidence does not confirm any stable source fields for:
|
||||
|
||||
- garrisons destroyed
|
||||
- outposts destroyed
|
||||
- direct duel history in a structured reusable form
|
||||
- tactical actions such as node building, dismantling or commander abilities
|
||||
|
||||
For direct encounters, the source does expose `most_killed` and `death_by`, but
|
||||
that is not the same thing as a complete duel graph and is not stored today.
|
||||
|
||||
## Availability And Reliability Matrix
|
||||
|
||||
| Metric / signal | Exists in source | Persisted today | Reliability for ranking | Extra work | V1? |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Kills | Yes | Yes | High | None | Yes |
|
||||
| Deaths | Yes | Yes | High | None | Yes |
|
||||
| Support | Yes | Yes | High | None | Yes |
|
||||
| Combat | Yes | Yes | Medium-High | Query only | Maybe |
|
||||
| Offense | Yes | Yes | Medium-High | Query only | Maybe |
|
||||
| Defense | Yes | Yes | Medium-High | Query only | Maybe |
|
||||
| Teamkills | Yes | Yes | High as penalty signal | Query only | Maybe |
|
||||
| Match count | Yes | Derivable | High | Query only | Yes |
|
||||
| Time played | Yes | Yes | High | Query only | Yes |
|
||||
| KPM | Yes | Yes | Medium-High if computed from totals, lower if averaging raw per-match KPM | Query only | Yes |
|
||||
| KDA / KD ratio | Yes | Yes | Medium-High if computed from totals, lower if averaging raw per-match KDA | Query only | Yes |
|
||||
| 100+ kill matches | Derivable | Exposed in leaderboard | Medium | None | No |
|
||||
| Win/loss context | Partially | Derivable from team side + scores when scores exist | Medium | Query and validation | Maybe |
|
||||
| Weapons profile | Yes | No | Medium-Low for V1 | New persistence/modeling | No |
|
||||
| Kill streak / life metrics | Yes | No | Medium-Low for V1 | New persistence/modeling | No |
|
||||
| Direct encounters / duels | Partial only | No | Low today | New extraction plus modeling | No |
|
||||
| Garrisons destroyed | Not confirmed | No | Unknown | Source validation first | No |
|
||||
| OPs destroyed | Not confirmed | No | Unknown | Source validation first | No |
|
||||
| Tactical impact composite | Partial proxies only | Partial | Medium after design work | Query/design | No for strict V1 |
|
||||
|
||||
## Current Product Readiness
|
||||
|
||||
The backend is already able to expose monthly leaderboard snapshots, but only
|
||||
for these metrics:
|
||||
|
||||
- `kills`
|
||||
- `deaths`
|
||||
- `support`
|
||||
- `matches_over_100_kills`
|
||||
|
||||
This means:
|
||||
|
||||
- the project already supports a monthly ranking surface operationally
|
||||
- the current ranking surface is narrower than the real data persisted in SQLite
|
||||
- offense, defense, combat, KPM and KDA are available in the database but not
|
||||
yet wired as first-class monthly leaderboard metrics
|
||||
|
||||
## Recommendation For Ranking V1
|
||||
|
||||
A realistic V1 should use only metrics already persisted with strong coverage
|
||||
and low modeling risk:
|
||||
|
||||
- total kills
|
||||
- total support
|
||||
- KPM recomputed from `SUM(kills) / SUM(time_seconds)`
|
||||
- KDA recomputed from `SUM(kills) / NULLIF(SUM(deaths), 0)`
|
||||
- minimum participation gate based on matches played and/or minutes played
|
||||
- optional small penalty for teamkills
|
||||
|
||||
Why this is the safest V1:
|
||||
|
||||
- no new ingestion is required
|
||||
- all needed raw fields already exist locally
|
||||
- the ranking can avoid inflated outliers by requiring minimum activity
|
||||
- KPM and KDA become more defensible when derived from totals, not from average
|
||||
of precomputed per-match ratios
|
||||
|
||||
## Recommendation For Ranking V2
|
||||
|
||||
A stronger V2 can expand the model with already persisted but not yet surfaced
|
||||
signals:
|
||||
|
||||
- offense
|
||||
- defense
|
||||
- combat
|
||||
- win/loss context derived from player side and match result when scores exist
|
||||
|
||||
V2 may also evaluate source-only fields if a later task decides to persist them:
|
||||
|
||||
- weapons-based detail
|
||||
- kill streak and life-span signals
|
||||
- partial rivalry/encounter signals from `most_killed` and `death_by`
|
||||
|
||||
## Metrics Not Recommended For Early Use
|
||||
|
||||
Not recommended for V1 and not yet defensible for a serious monthly ranking:
|
||||
|
||||
- garrisons destroyed
|
||||
- OPs destroyed
|
||||
- duel ranking
|
||||
- generic "impact in match" as a single opaque score
|
||||
|
||||
Reason:
|
||||
|
||||
- either the source availability is not confirmed
|
||||
- or the source exists but the project does not yet persist enough structure to
|
||||
make the metric auditable and stable
|
||||
|
||||
## Final Conclusion
|
||||
|
||||
The repository already has enough persisted historical data for a credible
|
||||
monthly Top 3 V1 without touching ingestion:
|
||||
|
||||
- kills
|
||||
- support
|
||||
- time played
|
||||
- deaths
|
||||
- teamkills
|
||||
- offense
|
||||
- defense
|
||||
- combat
|
||||
|
||||
The most realistic first release is a constrained monthly ranking based on
|
||||
volume plus efficiency, using only persisted fields and explicit participation
|
||||
thresholds. Tactical metrics such as garrisons, OPs and real duel graphs should
|
||||
stay out of scope until the source is revalidated and the missing structures are
|
||||
persisted deliberately.
|
||||
340
docs/player-event-pipeline-v2-design.md
Normal file
340
docs/player-event-pipeline-v2-design.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Player Event Pipeline V2 Design
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-24
|
||||
|
||||
## Scope
|
||||
|
||||
Diseno tecnico minimo de una futura canalizacion de eventos de jugador para
|
||||
alimentar una V2 del ranking MVP mensual con metricas avanzadas.
|
||||
|
||||
Fuera de alcance:
|
||||
|
||||
- implementacion real del pipeline
|
||||
- nuevas tablas o migraciones
|
||||
- nuevos endpoints
|
||||
- cambios de UI
|
||||
|
||||
## Inputs Reviewed
|
||||
|
||||
- `docs/rcon-data-capability-audit.md`
|
||||
- `docs/crcon-advanced-metrics-origin-audit.md`
|
||||
- `docs/monthly-mvp-ranking-scoring-design.md`
|
||||
- `backend/README.md`
|
||||
- `backend/app/historical_models.py`
|
||||
- `backend/app/historical_storage.py`
|
||||
- `backend/app/rcon_client.py`
|
||||
|
||||
## Problem Statement
|
||||
|
||||
La capa historica actual persiste bien metricas agregadas por jugador y partida:
|
||||
|
||||
- kills
|
||||
- deaths
|
||||
- teamkills
|
||||
- tiempo
|
||||
- combat
|
||||
- offense
|
||||
- defense
|
||||
- support
|
||||
|
||||
Eso basta para un MVP V1. No basta para una V2 que quiera exponer o puntuar:
|
||||
|
||||
- killer -> victim
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- kills por arma
|
||||
- `kills_by_type`
|
||||
- `death_by_weapons`
|
||||
- distincion infantry / tank / artillery
|
||||
|
||||
La auditoria previa deja claro que esas senales no salen del RCON live minimo
|
||||
implementado hoy.
|
||||
|
||||
## Design Goals
|
||||
|
||||
La V2 necesita una capa nueva con estos objetivos:
|
||||
|
||||
- capturar eventos finos fuera del request path HTTP
|
||||
- persistir eventos crudos con identidad suficiente para reprocess y auditoria
|
||||
- derivar agregados reproducibles por partida, jugador y mes
|
||||
- convivir con el modelo historico actual sin romper V1
|
||||
|
||||
## Minimal Architecture
|
||||
|
||||
La arquitectura minima propuesta tiene cuatro capas:
|
||||
|
||||
1. Source adapter
|
||||
|
||||
- un adaptador futuro conectado a eventos, logs o feed equivalente
|
||||
- responsable solo de leer senales crudas y normalizarlas
|
||||
|
||||
2. Event ingestion worker
|
||||
|
||||
- proceso batch o loop dedicado, separado de `app.main`
|
||||
- valida, deduplica y persiste eventos crudos
|
||||
- nunca calcula ranking dentro del request HTTP
|
||||
|
||||
3. Raw event storage
|
||||
|
||||
- ledger append-only por evento
|
||||
- base de auditoria y reproceso
|
||||
|
||||
4. Derived aggregates
|
||||
|
||||
- jobs posteriores que resumen por partida, jugador y ventana mensual
|
||||
- capa consumible por un futuro MVP V2
|
||||
|
||||
## Proposed Flow
|
||||
|
||||
Flujo minimo:
|
||||
|
||||
1. El source adapter recibe un evento crudo del servidor o del origen elegido.
|
||||
2. El worker normaliza el evento a un contrato comun.
|
||||
3. El evento se persiste en un ledger crudo con claves de deduplicacion.
|
||||
4. Un agregador por partida resume los eventos cerrados del match.
|
||||
5. Un agregador mensual construye metricas V2 por jugador y servidor.
|
||||
6. El ranking MVP V2 consume solo agregados ya cerrados y auditables.
|
||||
|
||||
## Minimum Event Types
|
||||
|
||||
El subconjunto minimo recomendado es:
|
||||
|
||||
- `player_kill`
|
||||
- `player_death`
|
||||
- `player_teamkill`
|
||||
- `player_weapon_usage`
|
||||
|
||||
En la practica, `player_death` y `player_weapon_usage` pueden modelarse como
|
||||
parte del mismo evento de kill si la fuente trae toda la informacion en un solo
|
||||
registro. Aun asi, conceptualmente la V2 debe capturar estas piezas:
|
||||
|
||||
- match id o match scope
|
||||
- timestamp del evento
|
||||
- server slug
|
||||
- killer player key
|
||||
- victim player key
|
||||
- killer team
|
||||
- victim team
|
||||
- weapon id o nombre de arma
|
||||
- kill type o damage type
|
||||
- contexto opcional de vehiculo, artilleria o explosivo
|
||||
- indicador de teamkill
|
||||
|
||||
## Event Contract Proposal
|
||||
|
||||
Contrato minimo recomendado para un evento normalizado:
|
||||
|
||||
- `event_id`
|
||||
- `event_type`
|
||||
- `occurred_at`
|
||||
- `server_slug`
|
||||
- `external_match_id`
|
||||
- `source_kind`
|
||||
- `source_ref`
|
||||
- `killer_player_key`
|
||||
- `victim_player_key`
|
||||
- `killer_team_side`
|
||||
- `victim_team_side`
|
||||
- `weapon_name`
|
||||
- `weapon_category`
|
||||
- `kill_category`
|
||||
- `is_teamkill`
|
||||
- `raw_event_ref`
|
||||
|
||||
Campos opcionales pero utiles desde el inicio:
|
||||
|
||||
- `killer_display_name`
|
||||
- `victim_display_name`
|
||||
- `vehicle_name`
|
||||
- `explosive_name`
|
||||
- `match_phase`
|
||||
|
||||
## High-Level Persistence Model
|
||||
|
||||
No se crean tablas en esta task, pero la persistencia minima deberia separar:
|
||||
|
||||
### 1. Raw player event ledger
|
||||
|
||||
Rol:
|
||||
|
||||
- guardar cada evento de forma append-only
|
||||
- permitir reprocess y auditoria
|
||||
|
||||
Campos minimos:
|
||||
|
||||
- event key estable
|
||||
- server
|
||||
- match
|
||||
- timestamp
|
||||
- tipo de evento
|
||||
- actor y target
|
||||
- arma o categoria
|
||||
- flags de clasificacion
|
||||
|
||||
### 2. Match event aggregates
|
||||
|
||||
Rol:
|
||||
|
||||
- resumir por partida ya cerrada
|
||||
- evitar recalcular toda la historia cada vez
|
||||
|
||||
Ejemplos de campos:
|
||||
|
||||
- kills por jugador
|
||||
- deaths por jugador
|
||||
- teamkills por jugador
|
||||
- kills por arma
|
||||
- kills por categoria
|
||||
- tabla de killer -> victim mas frecuente
|
||||
- tabla de victim <- killer mas frecuente
|
||||
|
||||
### 3. Monthly player advanced aggregates
|
||||
|
||||
Rol:
|
||||
|
||||
- dejar lista la capa consumible por el ranking V2
|
||||
|
||||
Ejemplos de campos:
|
||||
|
||||
- total kills por arma
|
||||
- total kills por categoria
|
||||
- rival mas matado (`most_killed`)
|
||||
- rival que mas le mata (`death_by`)
|
||||
- teamkills mensuales
|
||||
- pesos o subscores avanzados V2
|
||||
|
||||
## Relationship With Current Historical Model
|
||||
|
||||
La propuesta no sustituye el modelo existente `historical_player_match_stats`.
|
||||
|
||||
Relacion recomendada:
|
||||
|
||||
- `historical_*` sigue guardando el resumen estable V1 por partida
|
||||
- el nuevo ledger de eventos guarda granularidad que hoy no existe
|
||||
- los agregados V2 se derivan del ledger y se pueden unir despues al dominio
|
||||
mensual existente
|
||||
|
||||
Esto evita dos errores:
|
||||
|
||||
- inflar `historical_player_match_stats` con JSON opaco o columnas prematuras
|
||||
- mezclar captura cruda y vistas derivadas en la misma tabla
|
||||
|
||||
## How Advanced Metrics Would Be Derived
|
||||
|
||||
### `most_killed`
|
||||
|
||||
Derivacion:
|
||||
|
||||
- agrupar eventos de kill por `killer_player_key` y `victim_player_key`
|
||||
- contar ocurrencias
|
||||
- elegir el victim con mayor conteo por jugador y ventana
|
||||
|
||||
### `death_by`
|
||||
|
||||
Derivacion:
|
||||
|
||||
- agrupar eventos de kill por `victim_player_key` y `killer_player_key`
|
||||
- contar ocurrencias
|
||||
- elegir el killer con mayor conteo recibido por jugador y ventana
|
||||
|
||||
### Kills por arma
|
||||
|
||||
Derivacion:
|
||||
|
||||
- agrupar kills por `killer_player_key` y `weapon_name`
|
||||
|
||||
### `kills_by_type`
|
||||
|
||||
Derivacion:
|
||||
|
||||
- clasificar cada kill en una categoria normalizada
|
||||
- agrupar por `killer_player_key` y `kill_category`
|
||||
|
||||
Categorias minimas sugeridas:
|
||||
|
||||
- `infantry`
|
||||
- `vehicle`
|
||||
- `artillery`
|
||||
- `explosive`
|
||||
- `unknown`
|
||||
|
||||
### `death_by_weapons`
|
||||
|
||||
Derivacion:
|
||||
|
||||
- agrupar eventos por `victim_player_key` y `weapon_name`
|
||||
|
||||
### Distincion infantry / tank / artillery
|
||||
|
||||
Solo es viable si el origen del evento trae senales suficientes para clasificar:
|
||||
|
||||
- arma
|
||||
- vehiculo
|
||||
- damage type
|
||||
- o una taxonomia equivalente ya normalizada
|
||||
|
||||
Si la fuente no trae esa precision, la categoria debe quedarse en `unknown` y
|
||||
la V2 no debe inventar inferencias fragiles.
|
||||
|
||||
## Recommended Processing Policy
|
||||
|
||||
Politica minima recomendada:
|
||||
|
||||
- ingesta continua o semi-continua fuera de HTTP
|
||||
- deduplicacion por `event_id` o hash determinista
|
||||
- agregacion solo sobre partidas cerradas
|
||||
- recomputacion idempotente por partida y por ventana mensual
|
||||
- capacidad de rehidratar agregados desde el ledger crudo
|
||||
|
||||
## Integration Point With Monthly MVP V2
|
||||
|
||||
La V2 mensual deberia seguir la misma forma general de la V1:
|
||||
|
||||
- elegibilidad explicita
|
||||
- componentes normalizados
|
||||
- pesos auditables
|
||||
- tie-breaks deterministas
|
||||
|
||||
La diferencia es la entrada:
|
||||
|
||||
- V1 consume agregados simples ya persistidos por partida
|
||||
- V2 consumiria un agregado mensual enriquecido derivado del ledger de eventos
|
||||
|
||||
Componentes plausibles para una futura V2:
|
||||
|
||||
- kills totales
|
||||
- support total
|
||||
- eficiencia base
|
||||
- variedad o efectividad por arma
|
||||
- penalizacion por teamkills
|
||||
- subscore de encounters a partir de killer/victim
|
||||
- categoria de impacto por tipo de kill si la fuente lo soporta
|
||||
|
||||
## Minimal Rollout Path
|
||||
|
||||
Secuencia minima recomendada para futuras tasks:
|
||||
|
||||
1. Validar la fuente real de eventos/logs reutilizable.
|
||||
2. Definir el contrato normalizado del evento.
|
||||
3. Implementar ledger crudo con deduplicacion.
|
||||
4. Implementar agregador por partida cerrada.
|
||||
5. Implementar agregado mensual avanzado por jugador.
|
||||
6. Disenar formula MVP V2 sobre esos agregados.
|
||||
7. Exponer lectura API solo cuando los agregados sean estables.
|
||||
|
||||
## Main Risks
|
||||
|
||||
- la fuente real puede no exponer todas las senales necesarias
|
||||
- clasificar kills por tipo puede requerir un mapa de armas adicional
|
||||
- sin deduplicacion robusta, los agregados V2 serian poco fiables
|
||||
- mezclar eventos abiertos con partidas no cerradas inflaria metricas
|
||||
|
||||
## Final Recommendation
|
||||
|
||||
La arquitectura minima correcta para una V2 no es ampliar el snapshot live ni
|
||||
sobrecargar `historical_player_match_stats`. Es anadir una capa separada de
|
||||
eventos crudos, con agregacion derivada por partida y por mes, para producir de
|
||||
forma auditable metricas como `most_killed`, `death_by`, kills por arma y
|
||||
clasificaciones avanzadas.
|
||||
42
docs/project-overview.md
Normal file
42
docs/project-overview.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Project Overview
|
||||
|
||||
## Vision del proyecto
|
||||
|
||||
HLL Vietnam busca convertirse en la base de una web de comunidad para centralizar la presencia digital de una comunidad hispana alrededor del juego, con una identidad visual sobria, tactica y coherente con el universo Vietnam.
|
||||
|
||||
## Objetivo inicial
|
||||
|
||||
Publicar una landing simple que permita presentar la comunidad, mostrar el trailer del proyecto y facilitar el acceso directo al servidor de Discord.
|
||||
|
||||
## Alcance actual
|
||||
|
||||
- Estructura inicial del repositorio.
|
||||
- Landing estatica en HTML, CSS y JavaScript.
|
||||
- Documentacion base para organizar el crecimiento del proyecto.
|
||||
- Preparacion de carpetas para backend y orquestacion futura.
|
||||
- Plataforma de tasks y orquestacion integrada para coordinar trabajo tecnico.
|
||||
|
||||
## Stack actual
|
||||
|
||||
- HTML
|
||||
- CSS
|
||||
- JavaScript
|
||||
- Git para control de versiones
|
||||
|
||||
## Stack futuro previsto
|
||||
|
||||
- Backend principal en Python
|
||||
- Integraciones de comunidad y automatizacion
|
||||
- Posible ampliacion de paneles administrativos y servicios internos
|
||||
|
||||
## Contrato inicial frontend backend
|
||||
|
||||
El repositorio define un contrato API inicial en `docs/frontend-backend-contract.md` para alinear la futura comunicacion entre la landing y el backend Python.
|
||||
|
||||
En esta fase solo existe `GET /health` como endpoint implementado. Las rutas de comunidad, trailer, Discord y servidores quedan documentadas como contrato previsto para futuras tasks sin cambiar todavia el comportamiento visible del frontend.
|
||||
|
||||
## Evolucion prevista del frontend
|
||||
|
||||
La landing debe seguir siendo funcional al abrirse directamente en navegador mientras los datos dinamicos se introducen de forma incremental. La estrategia de consumo prevista usa `fetch` y JavaScript simple cuando una task lo requiera, siempre conservando fallbacks estaticos mientras se valida cada endpoint.
|
||||
|
||||
La planificacion detallada de prioridades de consumo, estados de carga, errores y placeholders queda en `docs/frontend-data-consumption-plan.md`.
|
||||
191
docs/rcon-data-capability-audit.md
Normal file
191
docs/rcon-data-capability-audit.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# RCON Data Capability Audit
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-24
|
||||
|
||||
## Scope
|
||||
|
||||
Auditoria tecnica del alcance real de RCON en esta repo, separando con claridad:
|
||||
|
||||
- RCON directo implementado hoy en el backend
|
||||
- historico CRCON / scoreboard publico
|
||||
- metricas que solo serian posibles con captura propia de eventos o logs
|
||||
|
||||
No se implementa ninguna tabla, ruta, scoring ni captura adicional.
|
||||
|
||||
## Evidence Reviewed
|
||||
|
||||
- `backend/app/rcon_client.py`
|
||||
- `backend/app/providers/rcon_provider.py`
|
||||
- `backend/app/data_sources.py`
|
||||
- `backend/app/payloads.py`
|
||||
- `backend/README.md`
|
||||
- `docs/historical-crcon-source-discovery.md`
|
||||
- `docs/monthly-player-ranking-data-audit.md`
|
||||
- `docs/monthly-mvp-ranking-scoring-design.md`
|
||||
|
||||
## Current RCON Surface In This Repository
|
||||
|
||||
La implementacion RCON actual es minima y solo cubre estado live.
|
||||
|
||||
Capacidades confirmadas en codigo hoy:
|
||||
|
||||
- handshake `ServerConnect`
|
||||
- autenticacion `Login`
|
||||
- consulta `GetServerInformation`
|
||||
|
||||
No hay evidencia en la repo de otros comandos RCON ya integrados para:
|
||||
|
||||
- eventos de kill
|
||||
- detalle por arma
|
||||
- relaciones killer -> victim
|
||||
- teamkills por evento
|
||||
- destruccion de garrisons u OPs
|
||||
- historico de partidas cerradas
|
||||
|
||||
## What The Current Live Provider Exposes Today
|
||||
|
||||
El proveedor `RconLiveDataSource` solo normaliza estos campos para `/api/servers`:
|
||||
|
||||
- `external_server_id`
|
||||
- `server_name`
|
||||
- `status`
|
||||
- `players`
|
||||
- `max_players`
|
||||
- `current_map`
|
||||
- `region`
|
||||
- `source_name`
|
||||
- `snapshot_origin`
|
||||
- `source_ref`
|
||||
|
||||
Esto significa que RCON directo hoy ya alimenta de forma confirmada:
|
||||
|
||||
- disponibilidad online del servidor
|
||||
- nombre del servidor
|
||||
- numero de jugadores actual
|
||||
- capacidad maxima
|
||||
- mapa actual
|
||||
- metadata de procedencia del snapshot
|
||||
|
||||
## Data That Is Not Exposed By Direct RCON Today
|
||||
|
||||
Aunque la task pide revisar equipos, scoreboard actual y otros campos live, la
|
||||
repo no confirma que el proveedor actual los este devolviendo hoy.
|
||||
|
||||
No quedan expuestos en el snapshot live actual:
|
||||
|
||||
- composicion por equipos
|
||||
- scoreboard de jugadores en tiempo real
|
||||
- kills por jugador en la partida en curso
|
||||
- deaths por jugador en la partida en curso
|
||||
- support/combat/offense/defense live
|
||||
- teamkills live
|
||||
|
||||
Importante:
|
||||
|
||||
- esto no prueba que el protocolo HLL RCON no pueda ofrecer mas cosas
|
||||
- solo prueba que la implementacion actual de esta repo no las consulta ni las
|
||||
serializa
|
||||
|
||||
## Historical Boundary
|
||||
|
||||
La separacion entre live e historico queda clara en la repo:
|
||||
|
||||
- `get_live_data_source()` puede resolver `rcon`
|
||||
- `get_historical_data_source()` devuelve un placeholder `RconHistoricalDataSource`
|
||||
- ese proveedor historico lanza `RuntimeError("Historical RCON provider is not implemented yet.")`
|
||||
|
||||
Conclusion operativa:
|
||||
|
||||
- RCON esta operativo hoy para estado live de `/api/servers`
|
||||
- RCON no esta operativo hoy para ingesta historica
|
||||
- el historico reutilizable del proyecto sigue viniendo de CRCON / scoreboard publico
|
||||
|
||||
## Capability Matrix For Future MVP V2 Metrics
|
||||
|
||||
| Metrica / senal | RCON directo hoy en esta repo | Requeriria eventos/logs + persistencia | Estado actual |
|
||||
| --- | --- | --- | --- |
|
||||
| Estado del servidor | Si | No | Disponible |
|
||||
| Jugadores actuales totales | Si | No | Disponible |
|
||||
| Capacidad maxima | Si | No | Disponible |
|
||||
| Mapa actual | Si | No | Disponible |
|
||||
| Equipos live | No confirmado | Posiblemente no, depende de ampliar cliente | No expuesto hoy |
|
||||
| Scoreboard live por jugador | No confirmado | Posiblemente no, depende de ampliar cliente | No expuesto hoy |
|
||||
| Kills por arma | No | Si | Requiere pipeline nuevo |
|
||||
| Distincion artillery / tank / infantry | No | Si | Requiere pipeline nuevo |
|
||||
| Killer -> victim | No | Si | Requiere pipeline nuevo |
|
||||
| `most_killed` | No | Si | Requiere pipeline nuevo |
|
||||
| `death_by` | No | Si | Requiere pipeline nuevo |
|
||||
| Teamkills por evento | No | Si | Requiere pipeline nuevo |
|
||||
| Teamkills agregados por partida/mes | No desde RCON actual | Si | Requiere pipeline nuevo |
|
||||
| Garrisons destruidos | No confirmado | Si como minimo | No confirmado |
|
||||
| OPs destruidos | No confirmado | Si como minimo | No confirmado |
|
||||
| Otras metricas tacticas finas | No | Si | Requiere pipeline nuevo |
|
||||
| Partidas cerradas historicas | No | Si | No disponible hoy via RCON |
|
||||
|
||||
## What Can Feed An MVP V2 From RCON
|
||||
|
||||
Subset viable usando solo RCON directo ya implementado:
|
||||
|
||||
- ninguno de los componentes avanzados de scoring MVP
|
||||
- solo datos de presencia live del servidor, utiles para panel operativo pero no
|
||||
para ranking mensual
|
||||
|
||||
Subset viable si se amplia solo el cliente RCON pero sin pipeline historico:
|
||||
|
||||
- quiza mas detalle live si el protocolo ofrece comandos adicionales
|
||||
- aun asi no bastaria para un ranking mensual auditable, porque faltaria
|
||||
persistencia por evento o por partida cerrada
|
||||
|
||||
Subset viable si se construye una linea nueva de eventos/logs RCON:
|
||||
|
||||
- kills por arma
|
||||
- killer/victim
|
||||
- teamkills por evento
|
||||
- clasificacion artillery/tank/infantry
|
||||
- senales tacticas si el origen real las emite
|
||||
|
||||
Condiciones minimas para que eso sirva a un MVP V2:
|
||||
|
||||
- ampliar el cliente RCON con comandos o feeds adicionales reales
|
||||
- capturar eventos de forma continua fuera del request path HTTP
|
||||
- persistir historico propio por partida, jugador y evento
|
||||
- definir agregados reproducibles para mes y servidor
|
||||
|
||||
## Separation From CRCON / Public Scoreboard
|
||||
|
||||
La repo ya confirma que ciertas metricas avanzadas existen en CRCON publico,
|
||||
pero eso no debe confundirse con RCON directo.
|
||||
|
||||
La evidencia actual de CRCON/scoreboard publico incluye campos como:
|
||||
|
||||
- `kills_by_type`
|
||||
- `most_killed`
|
||||
- `death_by`
|
||||
- `weapons`
|
||||
- `death_by_weapons`
|
||||
|
||||
Eso pertenece al historico JSON publico ya documentado y no a la superficie
|
||||
RCON hoy implementada en `rcon_client.py`.
|
||||
|
||||
## Practical Conclusion
|
||||
|
||||
Para esta repo, la respuesta precisa hoy es:
|
||||
|
||||
- RCON directo sirve para estado live de servidores
|
||||
- RCON directo no sirve todavia para alimentar un MVP mensual V2
|
||||
- cualquier MVP V2 con armas, duelos, teamkills por evento o tacticas requiere
|
||||
una canalizacion nueva de eventos/logs y persistencia historica propia
|
||||
- garrisons y OPs siguen sin evidencia confirmada en la repo como metrica
|
||||
disponible por RCON
|
||||
|
||||
## Recommended Next Step
|
||||
|
||||
Antes de disenar scoring V2 sobre RCON, la siguiente decision tecnica correcta
|
||||
seria una task separada de discovery para definir:
|
||||
|
||||
- si el origen RCON real del servidor expone mas comandos aparte de `GetServerInformation`
|
||||
- si existe flujo de eventos reutilizable
|
||||
- que granularidad y frecuencia tendria la persistencia de esos eventos
|
||||
- que subset minimo merece convertirse en modelo historico propio
|
||||
271
docs/rcon-historical-ingestion-design.md
Normal file
271
docs/rcon-historical-ingestion-design.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# RCON Historical Ingestion Design
|
||||
|
||||
## Validation Date
|
||||
|
||||
- 2026-03-25
|
||||
|
||||
## Scope
|
||||
|
||||
Definir si la repo puede soportar historico por RCON con la implementacion
|
||||
actual y, si no puede hacerlo de forma retroactiva, dejar una arquitectura
|
||||
minima y defendible para una primera captura prospectiva.
|
||||
|
||||
Este documento se limita a la evidencia local de la repo. No asume comandos
|
||||
RCON no integrados ni capacidades externas no demostradas aqui.
|
||||
|
||||
## Evidence Reviewed
|
||||
|
||||
- `backend/app/data_sources.py`
|
||||
- `backend/app/providers/rcon_provider.py`
|
||||
- `backend/app/rcon_client.py`
|
||||
- `backend/app/historical_ingestion.py`
|
||||
- `backend/app/historical_storage.py`
|
||||
- `backend/app/player_event_worker.py`
|
||||
- `backend/app/player_event_storage.py`
|
||||
- `backend/README.md`
|
||||
- `docs/rcon-data-capability-audit.md`
|
||||
- `docs/crcon-advanced-metrics-origin-audit.md`
|
||||
|
||||
## Current State In Code
|
||||
|
||||
La separacion entre live e historico ya existe en la seleccion de proveedores:
|
||||
|
||||
- `get_live_data_source()` puede resolver `rcon`
|
||||
- `get_historical_data_source()` puede resolver `rcon`, pero hoy devuelve un
|
||||
placeholder que falla
|
||||
|
||||
La implementacion RCON real disponible en la repo es minima y esta concentrada
|
||||
en `backend/app/rcon_client.py`.
|
||||
|
||||
Comandos soportados hoy en codigo:
|
||||
|
||||
- `ServerConnect`
|
||||
- `Login`
|
||||
- `GetServerInformation`
|
||||
|
||||
No hay evidencia local de otros comandos ya integrados para:
|
||||
|
||||
- scoreboards por jugador
|
||||
- detalle de partida cerrada
|
||||
- eventos kill por kill
|
||||
- logs tacticos
|
||||
- historico retroactivo de matches cerrados
|
||||
|
||||
## Payload Available Today
|
||||
|
||||
La salida efectiva que la repo consume desde RCON hoy es la normalizada por
|
||||
`query_live_server_state()`:
|
||||
|
||||
- `external_server_id`
|
||||
- `server_name`
|
||||
- `status`
|
||||
- `players`
|
||||
- `max_players`
|
||||
- `current_map`
|
||||
- `region`
|
||||
- `source_name`
|
||||
- `snapshot_origin`
|
||||
- `source_ref`
|
||||
|
||||
Inferencia basada en `rcon_client.py`:
|
||||
|
||||
- el payload remoto de `GetServerInformation` contiene como minimo
|
||||
`serverName`, `playerCount`, `maxPlayerCount` y `mapId` o `mapName`
|
||||
- la repo no persiste hoy el payload crudo ni deriva entidades historicas de
|
||||
match o jugador a partir de RCON
|
||||
|
||||
## Operational Frequency Assessment
|
||||
|
||||
Inferencia basada en la implementacion actual:
|
||||
|
||||
- cada pasada por target abre una conexion TCP
|
||||
- realiza handshake `ServerConnect`
|
||||
- autentica con `Login`
|
||||
- ejecuta una consulta `GetServerInformation`
|
||||
|
||||
Con este alcance, una frecuencia inicial razonable para captura prospectiva es:
|
||||
|
||||
- cada `60` a `300` segundos para operativa normal
|
||||
- `30` segundos solo como validacion o monitoreo puntual
|
||||
|
||||
No hay evidencia en la repo para defender un polling mas agresivo de forma
|
||||
sostenida ni para asegurar que aportaria historico competitivo util.
|
||||
|
||||
## Viability Decision
|
||||
|
||||
Conclusion principal:
|
||||
|
||||
- no viable hoy para historico real retroactivo de partidas cerradas con el
|
||||
cliente actual
|
||||
- viable solo para captura prospectiva
|
||||
|
||||
Motivos:
|
||||
|
||||
- el cliente actual solo consulta estado live puntual
|
||||
- no existe base local para reconstruir partidas ya cerradas
|
||||
- no existe feed raw de eventos ni logs persistidos
|
||||
- `RconHistoricalDataSource` sigue siendo un placeholder y no puede sustituir a
|
||||
`public-scoreboard`
|
||||
|
||||
Conclusion secundaria:
|
||||
|
||||
- una capa historica parcial por RCON si es defendible, pero solo si se define
|
||||
como captura prospectiva de muestras live y no como backfill de matches ya
|
||||
perdidos
|
||||
|
||||
## Recommended Minimal Architecture
|
||||
|
||||
### Storage
|
||||
|
||||
Separar completamente la persistencia prospectiva RCON del historico actual
|
||||
`historical_*`.
|
||||
|
||||
Tablas minimas recomendadas:
|
||||
|
||||
- `rcon_historical_targets`
|
||||
- identidad estable del target configurado
|
||||
- ultimo estado conocido de configuracion
|
||||
- `rcon_historical_capture_runs`
|
||||
- una fila por ejecucion del worker
|
||||
- estado, inicio, fin, errores y target scope
|
||||
- `rcon_historical_samples`
|
||||
- una fila por muestra y target
|
||||
- `captured_at`
|
||||
- identidad de target
|
||||
- payload normalizado
|
||||
- payload crudo opcional de `GetServerInformation`
|
||||
|
||||
Si se quiere checkpoint explicito desde la primera version:
|
||||
|
||||
- `rcon_historical_checkpoints`
|
||||
- `target_key`
|
||||
- `last_successful_capture_at`
|
||||
- `last_sample_at`
|
||||
- `last_error`
|
||||
|
||||
### Workers
|
||||
|
||||
Worker dedicado fuera del request path HTTP:
|
||||
|
||||
- `python -m app.rcon_historical_worker capture`
|
||||
- `python -m app.rcon_historical_worker loop --interval 120`
|
||||
|
||||
Responsabilidades:
|
||||
|
||||
- cargar `HLL_BACKEND_RCON_TARGETS`
|
||||
- consultar cada target con el cliente RCON actual
|
||||
- persistir run tracking
|
||||
- persistir muestras idempotentes por target y timestamp
|
||||
- actualizar checkpoints
|
||||
|
||||
### Checkpoints
|
||||
|
||||
Como no existe backfill retroactivo real, el checkpoint no debe modelarse como
|
||||
pagina o offset de archivo historico. Debe modelarse como tiempo de captura.
|
||||
|
||||
Checkpoint minimo defendible:
|
||||
|
||||
- ultimo `captured_at` exitoso por target
|
||||
- ultimo error por target
|
||||
- ultimo run exitoso global
|
||||
|
||||
### Compatibility With `public-scoreboard`
|
||||
|
||||
Politica recomendada:
|
||||
|
||||
- `public-scoreboard` sigue siendo la fuente historica principal para:
|
||||
- leaderboards semanales y mensuales
|
||||
- MVP V1 y V2
|
||||
- recent matches cerrados
|
||||
- player events derivados de la capa publica actual
|
||||
- RCON prospectivo convive en una linea paralela para:
|
||||
- cobertura temporal hacia delante
|
||||
- disponibilidad del servidor
|
||||
- actividad reciente
|
||||
- trazabilidad de frescura por target
|
||||
|
||||
## Recommended Degradation Policy
|
||||
|
||||
Si en una fase posterior se habilita `HLL_BACKEND_HISTORICAL_DATA_SOURCE=rcon`,
|
||||
la degradacion minima correcta es esta:
|
||||
|
||||
- solo exponer endpoints o bloques claramente soportados por la persistencia
|
||||
prospectiva RCON
|
||||
- no simular leaderboards completos cuando no existan
|
||||
- devolver metadata de cobertura y frescura antes que rankings vacios
|
||||
|
||||
Contratos defendibles en una primera lectura minima:
|
||||
|
||||
- resumen de cobertura por servidor
|
||||
- actividad reciente por servidor
|
||||
- estado de frescura
|
||||
- rango temporal disponible
|
||||
|
||||
Contratos que deben seguir dependiendo de `public-scoreboard` hasta nueva
|
||||
evidencia:
|
||||
|
||||
- weekly leaderboards completos
|
||||
- monthly leaderboards completos
|
||||
- monthly MVP V1
|
||||
- monthly MVP V2
|
||||
- player profiles competitivos
|
||||
- equivalencia completa con `historico.html`
|
||||
|
||||
## Recommended Phases
|
||||
|
||||
### Phase 1: Prospective Capture
|
||||
|
||||
Objetivo:
|
||||
|
||||
- empezar a guardar muestras live RCON hacia delante
|
||||
|
||||
Incluye:
|
||||
|
||||
- storage separado
|
||||
- worker dedicado
|
||||
- run tracking
|
||||
- checkpoints temporales
|
||||
- ejecucion manual y en loop
|
||||
|
||||
No incluye:
|
||||
|
||||
- backfill retroactivo
|
||||
- paridad con `public-scoreboard`
|
||||
- endpoints competitivos nuevos
|
||||
|
||||
### Phase 2: Minimal Operational Read Model
|
||||
|
||||
Objetivo:
|
||||
|
||||
- leer la persistencia prospectiva RCON sin consultar RCON on-demand en HTTP
|
||||
|
||||
Incluye:
|
||||
|
||||
- resumen por servidor
|
||||
- ultima muestra
|
||||
- cobertura disponible
|
||||
- actividad reciente
|
||||
|
||||
### Phase 3: Competitive Metrics Only If Signal Improves
|
||||
|
||||
Objetivo:
|
||||
|
||||
- evaluar si aparecen comandos, eventos o logs suficientes para enriquecer la
|
||||
capa historica RCON
|
||||
|
||||
Solo deberia abrirse si existe evidencia real de:
|
||||
|
||||
- eventos reutilizables
|
||||
- scoreboards historificables
|
||||
- granularidad por jugador o por encounter
|
||||
|
||||
## Final Recommendation
|
||||
|
||||
La decision tecnica correcta para esta repo es:
|
||||
|
||||
- mantener `public-scoreboard` como fuente historica por defecto
|
||||
- tratar RCON historico como una linea prospectiva separada
|
||||
- no prometer reconstruccion retroactiva con el cliente actual
|
||||
- abrir implementacion incremental en dos tasks:
|
||||
- captura prospectiva persistida
|
||||
- lectura minima sobre persistencia local
|
||||
35
docs/roadmap.md
Normal file
35
docs/roadmap.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Roadmap
|
||||
|
||||
## Fase 1: base del repo
|
||||
|
||||
- Crear estructura inicial profesional.
|
||||
- Definir documentacion base del proyecto.
|
||||
- Publicar la primera landing estatica.
|
||||
|
||||
## Fase 2: landing mejorada
|
||||
|
||||
- Incorporar branding definitivo y recursos visuales.
|
||||
- Anadir mas secciones informativas de comunidad.
|
||||
- Mejorar experiencia responsive y contenido.
|
||||
|
||||
## Fase 3: backend Python
|
||||
|
||||
- Definir arquitectura del backend.
|
||||
- Incorporar servicios base en Python.
|
||||
- Preparar configuracion, entornos y despliegue inicial.
|
||||
|
||||
## Fase 4: integracion de datos de Discord/servidores
|
||||
|
||||
- Documentar el plan tecnico de datos para Discord y servidores antes de integrar fuentes reales.
|
||||
- Empezar por placeholders o datos manuales controlados desde el backend Python.
|
||||
- Incorporar integraciones limitadas y trazables solo despues de validar fuentes, limites y seguridad.
|
||||
- Diferenciar de forma explicita los servidores actuales de Hell Let Loose frente al futuro contexto HLL Vietnam.
|
||||
- Sustituir el bloque provisional de servidores actuales cuando existan datos mas cercanos al producto final.
|
||||
- Definir snapshots de servidores como unidad base para historicos y estadisticas basicas antes de persistir datos reales.
|
||||
- Separar por fases la ingesta, la normalizacion y la futura explotacion historica para no acoplar el frontend a fuentes externas.
|
||||
|
||||
## Fase 5: panel/admin y automatizacion
|
||||
|
||||
- Construir panel interno o administrativo.
|
||||
- Anadir flujos de gestion y publicacion.
|
||||
- Ampliar y madurar el sistema de tasks y orquestacion ya integrado en el repositorio.
|
||||
45
docs/scoreboard-correlation-debugging.md
Normal file
45
docs/scoreboard-correlation-debugging.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Scoreboard Correlation Debugging
|
||||
|
||||
Use backend commands to debug a missing public scoreboard button on an RCON
|
||||
historical match. Normal frontend payloads and pages should stay free of
|
||||
correlation diagnostics.
|
||||
|
||||
## Sequence
|
||||
|
||||
1. Refresh trusted public scoreboard candidates for the relevant server:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.scoreboard_candidate_backfill --server comunidad-hispana-02 --from 2026-05-20T00:00:00Z --to 2026-05-21T23:59:59Z --max-pages 5 --page-size 100
|
||||
```
|
||||
|
||||
2. Scan existing materialized RCON matches against those candidates:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.rcon_scoreboard_relink --server comunidad-hispana-02
|
||||
```
|
||||
|
||||
3. Inspect one match correlation:
|
||||
|
||||
```powershell
|
||||
docker compose exec backend python -m app.scoreboard_correlation_diagnostics --server comunidad-hispana-02 --match comunidad-hispana-02:1779310451:1779315851:foywarfare
|
||||
```
|
||||
|
||||
4. Verify the detail endpoint used by the match page:
|
||||
|
||||
```powershell
|
||||
Invoke-WebRequest 'http://localhost:8000/api/historical/matches/detail?server=comunidad-hispana-02&match=comunidad-hispana-02%3A1779310451%3A1779315851%3Afoywarfare' | Select-Object -ExpandProperty Content
|
||||
```
|
||||
|
||||
## Reading Output
|
||||
|
||||
The diagnostic JSON includes the RCON match window, score, candidate search
|
||||
window, safe top candidate summaries, the selected candidate when one is strong
|
||||
enough, and `final_reason`.
|
||||
|
||||
- `linked` means the detail read model can expose the trusted `match_url`.
|
||||
- `no-safe-candidate` means candidate persistence or map/window matching needs
|
||||
inspection.
|
||||
- `low-confidence` means candidates exist but evidence is insufficient.
|
||||
- `ambiguous-candidate` means two candidates tie and no public URL is selected.
|
||||
- `unsafe-url` in a candidate summary means the raw candidate URL is not emitted
|
||||
or selected.
|
||||
151
docs/stats-database-schema-foundation.md
Normal file
151
docs/stats-database-schema-foundation.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Stats Database Schema Foundation
|
||||
|
||||
## Objective
|
||||
|
||||
Definir una base de almacenamiento simple y reutilizable para snapshots de
|
||||
servidores y estadisticas iniciales, sin comprometer todavia una base de datos
|
||||
productiva concreta.
|
||||
|
||||
## Design Principles
|
||||
|
||||
- naming generico reutilizable para HLL actual y futuro HLL Vietnam
|
||||
- separacion entre identidad de servidor y observaciones historicas
|
||||
- persistir primero solo lo necesario para reconstruir actividad basica
|
||||
- dejar espacio para multiples fuentes sin acoplar el modelo a una integracion
|
||||
unica
|
||||
|
||||
## Proposed Core Entities
|
||||
|
||||
### `game_sources`
|
||||
|
||||
Proposito:
|
||||
describir el contexto del juego o dominio de origen de los datos.
|
||||
|
||||
Campos principales:
|
||||
|
||||
- `id`
|
||||
- `slug`
|
||||
- `display_name`
|
||||
- `provider_kind`
|
||||
- `is_active`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
Notas:
|
||||
|
||||
- `slug` puede tomar valores como `current-hll` y en el futuro otros contextos
|
||||
mas cercanos a HLL Vietnam.
|
||||
- Esta entidad evita incrustar el juego en cada nombre de tabla.
|
||||
|
||||
### `servers`
|
||||
|
||||
Proposito:
|
||||
mantener la identidad estable de cada servidor observado.
|
||||
|
||||
Campos principales:
|
||||
|
||||
- `id`
|
||||
- `game_source_id`
|
||||
- `external_server_id` nullable
|
||||
- `server_name`
|
||||
- `region` nullable
|
||||
- `first_seen_at`
|
||||
- `last_seen_at`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
|
||||
Claves y relaciones:
|
||||
|
||||
- primary key en `id`
|
||||
- foreign key a `game_sources.id`
|
||||
- unique recomendado sobre `game_source_id` + `external_server_id` cuando el
|
||||
origen entregue identificador externo fiable
|
||||
|
||||
Notas:
|
||||
|
||||
- `server_name` no debe usarse como clave unica porque puede cambiar.
|
||||
- `last_seen_at` resume la ultima observacion conocida sin sustituir a los
|
||||
snapshots historicos.
|
||||
|
||||
### `server_snapshots`
|
||||
|
||||
Proposito:
|
||||
registrar cada captura puntual normalizada de un servidor.
|
||||
|
||||
Campos principales:
|
||||
|
||||
- `id`
|
||||
- `server_id`
|
||||
- `captured_at`
|
||||
- `status`
|
||||
- `players`
|
||||
- `max_players`
|
||||
- `current_map` nullable
|
||||
- `source_name`
|
||||
- `raw_payload_ref` nullable
|
||||
- `created_at`
|
||||
|
||||
Claves y relaciones:
|
||||
|
||||
- primary key en `id`
|
||||
- foreign key a `servers.id`
|
||||
- index recomendado sobre `server_id` + `captured_at`
|
||||
|
||||
Notas:
|
||||
|
||||
- `status`, `players`, `max_players` y `current_map` son la base a persistir
|
||||
desde la primera fase.
|
||||
- `raw_payload_ref` queda como referencia opcional para trazabilidad futura si
|
||||
el backend decide guardar artefactos crudos fuera de esta tabla.
|
||||
|
||||
## Initial Statistics Layer
|
||||
|
||||
No es necesario persistir metricas complejas desde el inicio. La primera capa
|
||||
de estadisticas puede documentarse como derivada de `server_snapshots`.
|
||||
|
||||
Vistas o agregaciones recomendadas para una siguiente fase:
|
||||
|
||||
- ultima observacion por servidor
|
||||
- pico de jugadores por servidor en una ventana temporal
|
||||
- numero de snapshots online por servidor
|
||||
- ultima vez visto online
|
||||
|
||||
Si mas adelante aparecen necesidades de rendimiento o cuadros de mando
|
||||
persistentes, podra anadirse una tabla de agregados sin cambiar la base del
|
||||
modelo.
|
||||
|
||||
## What To Persist First
|
||||
|
||||
Persistir por snapshot:
|
||||
|
||||
- `server_id`
|
||||
- `captured_at`
|
||||
- `status`
|
||||
- `players`
|
||||
- `max_players`
|
||||
- `current_map` cuando exista
|
||||
- `source_name`
|
||||
|
||||
Puede derivarse despues:
|
||||
|
||||
- tendencias
|
||||
- medias por periodo
|
||||
- picos historicos
|
||||
- porcentaje de disponibilidad
|
||||
- rankings
|
||||
|
||||
## Technology Position
|
||||
|
||||
El repositorio todavia no fija una tecnologia de persistencia productiva. La
|
||||
base del esquema debe entenderse como modelo logico compatible con el backend en
|
||||
Python y trasladable despues a la opcion de almacenamiento que se valide en una
|
||||
task especifica.
|
||||
|
||||
En esta fase no se anaden migraciones, ORM ni ficheros de base de datos.
|
||||
|
||||
## Open Questions For Future Tasks
|
||||
|
||||
- que fuente aportara un identificador externo suficientemente estable
|
||||
- con que frecuencia debe capturarse un snapshot
|
||||
- si conviene guardar payload crudo completo o solo referencias
|
||||
- cuando merece la pena materializar agregados persistentes
|
||||
Reference in New Issue
Block a user