This commit is contained in:
devRaGonSa
2026-06-05 16:57:25 +02:00
commit 0da8338ba8
310 changed files with 45849 additions and 0 deletions

View 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.

View 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.

View 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.

View 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 applications 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
View 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.

View 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.

View 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.

View 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

View 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.

View 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.

View 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.

View 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.

View 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

View 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.

View 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.

View 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

View 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.

View 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.

View 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
View 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`.

View 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

View 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
View 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.

View 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.

View 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