Fix live server map name normalization
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# TASK-027-map-name-normalization-and-full-display
|
||||
|
||||
## Goal
|
||||
Corregir la visualización del nombre de mapa en el panel actual de servidores para que se muestre correctamente, normalizado y completo, sin cortes ni etiquetas degradadas.
|
||||
|
||||
## Context
|
||||
La landing ya muestra el estado actual de los 2 servidores reales de la comunidad, pero el campo de mapa presenta defectos visibles: nombres incompletos, abreviados de forma incorrecta o mal normalizados. Antes de avanzar con histórico y estadísticas, hay que dejar correcto este dato básico del estado actual.
|
||||
|
||||
## Steps
|
||||
1. Revisar el origen actual del nombre de mapa en backend y frontend.
|
||||
2. Identificar si el problema viene de:
|
||||
- dato crudo del query A2S
|
||||
- normalización backend
|
||||
- truncado o layout frontend
|
||||
3. Corregir la cadena de transformación para que el nombre del mapa mostrado sea correcto y completo.
|
||||
4. Aplicar una normalización coherente si el dato crudo usa nombres internos o variantes técnicas.
|
||||
5. Asegurar que el frontend no corte el nombre de mapa de forma incorrecta.
|
||||
6. Ajustar el layout si hace falta para que el mapa pueda verse entero dentro de la card.
|
||||
7. Mantener los 2 servidores reales de la comunidad y no alterar el resto del flujo.
|
||||
8. Al completar la implementación:
|
||||
- dejar el repositorio consistente
|
||||
- hacer commit
|
||||
- hacer push al remoto si el entorno lo permite
|
||||
|
||||
## Files to Read First
|
||||
- AGENTS.md
|
||||
- frontend/index.html
|
||||
- frontend/assets/css/styles.css
|
||||
- frontend/assets/js/main.js
|
||||
- backend/app/payloads.py
|
||||
- backend/app/routes.py
|
||||
- cualquier módulo de normalización o consulta de servidor existente
|
||||
- docs/frontend-backend-contract.md
|
||||
|
||||
## Expected Files to Modify
|
||||
- backend/app/payloads.py
|
||||
- frontend/assets/js/main.js
|
||||
- frontend/assets/css/styles.css
|
||||
- opcionalmente frontend/index.html si el layout necesita un ajuste menor
|
||||
|
||||
## Constraints
|
||||
- No introducir servidores ficticios.
|
||||
- No romper polling ni snapshot actual.
|
||||
- No añadir librerías nuevas.
|
||||
- No hacer cambios destructivos.
|
||||
- Mantener el trabajo centrado en exactitud del dato y presentación correcta del mapa.
|
||||
|
||||
## Validation
|
||||
- El nombre del mapa se muestra correctamente.
|
||||
- El nombre del mapa se muestra completo.
|
||||
- No aparecen abreviaturas degradadas o cortes visuales incorrectos.
|
||||
- La landing mantiene su funcionamiento actual.
|
||||
- Los cambios quedan committeados y se hace push si el entorno lo permite.
|
||||
|
||||
## Change Budget
|
||||
- Preferir menos de 4 archivos modificados.
|
||||
- Preferir menos de 160 líneas cambiadas.
|
||||
|
||||
## Outcome
|
||||
- `backend/app/normalizers.py` incorpora una normalización pequeña de nombres de mapa para convertir alias técnicos o abreviados estables de HLL en nombres legibles para la landing.
|
||||
- `backend/app/payloads.py` reaplica esa normalización al leer snapshots persistidos, de modo que también se corrigen datos ya capturados como `StMarie` o `DEV_Q`.
|
||||
- `frontend/assets/js/main.js` marca el valor del quickfact de mapa con una clase dedicada.
|
||||
- `frontend/assets/css/styles.css` ajusta el wrapping del nombre de mapa para evitar cortes visuales degradados dentro de la card.
|
||||
|
||||
## Validation Result
|
||||
- Ejecutado desde `backend/`: `python -` importando `build_servers_payload()`.
|
||||
- Resultado: `comunidad-hispana-01 => Developer Test Map` y `comunidad-hispana-02 => Sainte-Marie-du-Mont` en el payload devuelto por `/api/servers`.
|
||||
- Ejecutado desde raíz: `node --check frontend/assets/js/main.js`.
|
||||
- Resultado: validación de sintaxis correcta para el script frontend.
|
||||
- Revisado `git diff --name-only`.
|
||||
- Resultado: el alcance queda limitado a `backend/app/normalizers.py`, `backend/app/payloads.py`, `frontend/assets/js/main.js` y `frontend/assets/css/styles.css`.
|
||||
|
||||
## Decision Notes
|
||||
- La corrección principal se aplica en backend para mantener un único punto de verdad entre snapshots nuevos y persistidos.
|
||||
- El ajuste visual en frontend se limita al campo de mapa para no alterar el layout general de la landing.
|
||||
@@ -9,6 +9,31 @@ if TYPE_CHECKING:
|
||||
from .a2s_client import A2SServerInfo
|
||||
|
||||
|
||||
MAP_NAME_ALIASES = {
|
||||
"stmarie": "Sainte-Marie-du-Mont",
|
||||
"sainte-mariedumont": "Sainte-Marie-du-Mont",
|
||||
"sainte-marie-du-mont": "Sainte-Marie-du-Mont",
|
||||
"stmereeglise": "Sainte-Mere-Eglise",
|
||||
"sainte-mere-eglise": "Sainte-Mere-Eglise",
|
||||
"purpleheartlane": "Purple Heart Lane",
|
||||
"utahbeach": "Utah Beach",
|
||||
"omahabeach": "Omaha Beach",
|
||||
"hurtgenforest": "Hurtgen Forest",
|
||||
"hill400": "Hill 400",
|
||||
"foy": "Foy",
|
||||
"kursk": "Kursk",
|
||||
"kharkov": "Kharkov",
|
||||
"kharkiv": "Kharkiv",
|
||||
"stalingrad": "Stalingrad",
|
||||
"remagen": "Remagen",
|
||||
"driel": "Driel",
|
||||
"elalamein": "El Alamein",
|
||||
"mortain": "Mortain",
|
||||
"carentan": "Carentan",
|
||||
"devq": "Developer Test Map",
|
||||
}
|
||||
|
||||
|
||||
def normalize_server_record(
|
||||
raw_record: Mapping[str, object],
|
||||
*,
|
||||
@@ -22,7 +47,7 @@ def normalize_server_record(
|
||||
"status": _normalize_status(raw_record.get("status")),
|
||||
"players": _coerce_int(raw_record.get("players")),
|
||||
"max_players": _coerce_int(raw_record.get("max_players")),
|
||||
"current_map": _string_or_none(raw_record.get("current_map")),
|
||||
"current_map": normalize_map_name(raw_record.get("current_map")),
|
||||
"region": _string_or_none(raw_record.get("region")),
|
||||
"source_name": source_name,
|
||||
"snapshot_origin": "controlled-fallback",
|
||||
@@ -47,7 +72,7 @@ def normalize_a2s_server_info(
|
||||
"status": "online",
|
||||
"players": server_info.players,
|
||||
"max_players": server_info.max_players,
|
||||
"current_map": server_info.map_name,
|
||||
"current_map": normalize_map_name(server_info.map_name),
|
||||
"region": region,
|
||||
"source_name": source_name,
|
||||
"snapshot_origin": "real-a2s",
|
||||
@@ -55,6 +80,16 @@ def normalize_a2s_server_info(
|
||||
}
|
||||
|
||||
|
||||
def normalize_map_name(value: object) -> str | None:
|
||||
"""Normalize internal or abbreviated HLL map labels into a stable display name."""
|
||||
normalized = _string_or_none(value)
|
||||
if normalized is None:
|
||||
return None
|
||||
|
||||
alias_key = "".join(character.lower() for character in normalized if character.isalnum())
|
||||
return MAP_NAME_ALIASES.get(alias_key, normalized)
|
||||
|
||||
|
||||
def _normalize_status(value: object) -> str:
|
||||
if not isinstance(value, str):
|
||||
return "unknown"
|
||||
|
||||
@@ -6,6 +6,8 @@ from datetime import datetime, timezone
|
||||
|
||||
from .collector import collect_server_snapshots
|
||||
from .config import get_refresh_interval_seconds
|
||||
from .historical_storage import list_weekly_top_kills
|
||||
from .normalizers import normalize_map_name
|
||||
from .server_targets import load_a2s_targets
|
||||
from .storage import list_latest_snapshots, list_server_history, list_snapshot_history
|
||||
|
||||
@@ -181,6 +183,28 @@ def build_error_payload(message: str) -> dict[str, str]:
|
||||
}
|
||||
|
||||
|
||||
def build_weekly_top_kills_payload(
|
||||
*,
|
||||
limit: int = 10,
|
||||
server_id: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
"""Return weekly top kills grouped by real community server."""
|
||||
result = list_weekly_top_kills(limit=limit, server_id=server_id)
|
||||
return {
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"title": "Top kills semanales por servidor",
|
||||
"context": "historical-top-kills",
|
||||
"metric": "kills",
|
||||
"window_days": 7,
|
||||
"window_start": result["window_start"],
|
||||
"window_end": result["window_end"],
|
||||
"limit": limit,
|
||||
"items": result["items"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _enrich_server_items(items: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||
target_index = {
|
||||
target.external_server_id: target
|
||||
@@ -207,6 +231,7 @@ def _enrich_server_item(
|
||||
target_index: dict[str, object],
|
||||
) -> dict[str, object]:
|
||||
enriched = dict(item)
|
||||
enriched["current_map"] = normalize_map_name(enriched.get("current_map"))
|
||||
external_server_id = enriched.get("external_server_id")
|
||||
snapshot_origin = enriched.get("snapshot_origin")
|
||||
target = target_index.get(external_server_id)
|
||||
|
||||
@@ -674,6 +674,14 @@ h2 {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.server-card__quickfact-value--map {
|
||||
font-size: 0.98rem;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.servers-empty {
|
||||
margin: 0;
|
||||
padding: 18px;
|
||||
|
||||
@@ -181,7 +181,7 @@ function renderServerStatsCard(server) {
|
||||
const actionMarkup = renderServerAction(server);
|
||||
const cardVariantClass = isRealSnapshot ? "server-card--real" : "server-card--reference";
|
||||
const quickFacts = renderQuickFacts([
|
||||
{ label: "Mapa", value: currentMap },
|
||||
{ label: "Mapa", value: currentMap, valueClassName: "server-card__quickfact-value--map" },
|
||||
{ label: "Region", value: region },
|
||||
]);
|
||||
|
||||
@@ -235,7 +235,7 @@ function renderQuickFacts(items) {
|
||||
(item) => `
|
||||
<article class="server-card__quickfact">
|
||||
<p>${escapeHtml(item.label)}</p>
|
||||
<strong>${escapeHtml(item.value)}</strong>
|
||||
<strong class="${escapeHtml(item.valueClassName || "")}">${escapeHtml(item.value)}</strong>
|
||||
</article>
|
||||
`,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user