Add rcon-first elo mmr foundation

This commit is contained in:
devRaGonSa
2026-03-25 17:29:29 +01:00
parent 787e753f77
commit 6ed416f79a
18 changed files with 2282 additions and 33 deletions

View File

@@ -158,9 +158,16 @@
gap: 14px;
}
.historical-elo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
gap: 14px;
}
.historical-stat-card,
.historical-match-card,
.historical-mvp-card {
.historical-mvp-card,
.historical-elo-card {
position: relative;
overflow: hidden;
padding: 18px;
@@ -177,7 +184,10 @@
.historical-match-card strong,
.historical-mvp-card p,
.historical-mvp-card strong,
.historical-mvp-card span {
.historical-mvp-card span,
.historical-elo-card p,
.historical-elo-card strong,
.historical-elo-card span {
margin: 0;
}
@@ -298,6 +308,91 @@
gap: 10px;
}
.historical-elo-card {
display: grid;
gap: 16px;
border-color: rgba(96, 150, 124, 0.24);
background:
radial-gradient(circle at top right, rgba(96, 150, 124, 0.12), transparent 40%),
linear-gradient(180deg, rgba(21, 32, 27, 0.96), rgba(12, 15, 11, 0.98));
}
.historical-elo-card__top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.historical-elo-card__rank {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 42px;
min-height: 42px;
padding: 0 12px;
border: 1px solid rgba(96, 150, 124, 0.28);
border-radius: 999px;
color: #a8d4bf;
font-size: 0.76rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.historical-elo-card__accuracy {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 0 10px;
border: 1px solid rgba(96, 150, 124, 0.24);
border-radius: 999px;
color: #a8d4bf;
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.historical-elo-card__meta,
.historical-elo-card__scores {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.historical-elo-card__meta article,
.historical-elo-card__scores article {
padding: 10px 12px;
border: 1px solid rgba(96, 150, 124, 0.14);
border-radius: 14px;
background: rgba(13, 17, 12, 0.42);
}
.historical-elo-card__meta span,
.historical-elo-card__scores span {
display: block;
margin-bottom: 6px;
color: var(--muted);
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.historical-elo-card__summary {
color: var(--text-soft);
font-size: 0.9rem;
line-height: 1.5;
}
.historical-elo-card__footer {
padding-top: 12px;
border-top: 1px solid rgba(159, 168, 141, 0.12);
color: var(--text-soft);
font-size: 0.88rem;
line-height: 1.5;
}
.historical-mvp-card__signal-summary {
color: var(--text-soft);
font-size: 0.9rem;

View File

@@ -110,6 +110,10 @@ document.addEventListener("DOMContentLoaded", () => {
const comparisonStateNode = document.getElementById("mvp-comparison-state");
const comparisonListNode = document.getElementById("mvp-comparison-list");
const comparisonNoteNode = document.getElementById("mvp-comparison-note");
const eloMmrStateNode = document.getElementById("elo-mmr-state");
const eloMmrListNode = document.getElementById("elo-mmr-list");
const eloMmrNoteNode = document.getElementById("elo-mmr-note");
const eloMmrMetaNode = document.getElementById("elo-mmr-meta");
const weeklyValueHeadingNode = document.getElementById("weekly-leaderboard-value-heading");
const weeklyWindowNoteNode = document.getElementById("weekly-window-note");
const weeklySnapshotMetaNode = document.getElementById(
@@ -134,6 +138,7 @@ document.addEventListener("DOMContentLoaded", () => {
const leaderboardCache = new Map();
const monthlyMvpCache = new Map();
const monthlyMvpV2Cache = new Map();
const eloMmrCache = new Map();
const pendingRequestCache = new Map();
let latestMonthlyMvpResult = null;
let latestMonthlyMvpV2Result = null;
@@ -178,6 +183,14 @@ document.addEventListener("DOMContentLoaded", () => {
`${backendBaseUrl}/api/historical/snapshots/monthly-mvp-v2?server=${encodeURIComponent(serverSlug)}&limit=3`,
);
const getEloMmrLeaderboard = (serverSlug) =>
getCachedJson(
eloMmrCache,
pendingRequestCache,
buildEloMmrSnapshotKey(serverSlug),
`${backendBaseUrl}/api/historical/elo-mmr/leaderboard?server=${encodeURIComponent(serverSlug)}&limit=5`,
);
const refreshServerContent = async () => {
const requestId = activeServerRequestId + 1;
const leaderboardRequestId = activeLeaderboardRequestId + 1;
@@ -227,6 +240,10 @@ document.addEventListener("DOMContentLoaded", () => {
comparisonNoteNode.textContent =
"Cargando comparativa entre los rankings mensuales V1 y V2...";
setState(comparisonStateNode, "Preparando comparativa V1 vs V2...");
eloMmrListNode.innerHTML = "";
eloMmrNoteNode.textContent = "Cargando lectura del rating persistente y score mensual...";
setState(eloMmrStateNode, "Cargando leaderboard Elo/MMR...");
setSnapshotMeta(eloMmrMetaNode, "Cargando metadata de Elo/MMR...");
weeklyWindowNoteNode.textContent = "Cargando snapshot del ranking activo...";
setSnapshotMeta(
weeklySnapshotMetaNode,
@@ -297,6 +314,20 @@ document.addEventListener("DOMContentLoaded", () => {
);
}
const cachedEloMmrPayload = readCachedPayload(
eloMmrCache,
buildEloMmrSnapshotKey(activeServerSlug),
);
if (cachedEloMmrPayload) {
hydrateEloMmr(
{ status: "fulfilled", value: cachedEloMmrPayload },
eloMmrStateNode,
eloMmrListNode,
eloMmrNoteNode,
eloMmrMetaNode,
);
}
const cachedLeaderboardPayload = readCachedPayload(
leaderboardCache,
buildLeaderboardSnapshotKey(
@@ -471,6 +502,27 @@ document.addEventListener("DOMContentLoaded", () => {
comparisonNoteNode,
);
});
void settlePromise(getEloMmrLeaderboard(targetServerSlug)).then((eloMmrResult) => {
if (
!isActiveServerRequest(
requestId,
targetServerSlug,
targetTimeframe,
targetMetric,
)
) {
return;
}
hydrateEloMmr(
eloMmrResult,
eloMmrStateNode,
eloMmrListNode,
eloMmrNoteNode,
eloMmrMetaNode,
);
});
};
const refreshLeaderboardContent = async () => {
@@ -1166,6 +1218,89 @@ function renderMvpComparisonCard(item) {
`;
}
function hydrateEloMmr(result, stateNode, listNode, noteNode, metaNode) {
if (result?.status !== "fulfilled") {
setState(stateNode, "No se pudo cargar el leaderboard Elo/MMR.", true);
listNode.innerHTML = "";
noteNode.textContent =
"El sistema Elo/MMR sigue disponible solo cuando existe rebuild persistido.";
setSnapshotMeta(metaNode, "Error al cargar metadata de Elo/MMR.");
return;
}
const payload = result.value?.data;
const items = Array.isArray(payload?.items) ? payload.items : [];
if (!payload?.found || !items.length) {
setState(
stateNode,
"Todavia no hay leaderboard Elo/MMR mensual listo para este alcance.",
);
listNode.innerHTML = "";
noteNode.textContent =
"El bloque aparece cuando existe un rebuild persistido con jugadores elegibles.";
setSnapshotMeta(
metaNode,
buildEloMmrMeta(payload),
);
return;
}
stateNode.hidden = true;
noteNode.textContent = buildEloMmrNote(payload);
setSnapshotMeta(metaNode, buildEloMmrMeta(payload));
listNode.innerHTML = items.map((item) => renderEloMmrCard(item, payload)).join("");
}
function renderEloMmrCard(item, payload) {
return `
<article class="historical-elo-card">
<div class="historical-elo-card__top">
<div>
<span class="historical-elo-card__rank">#${escapeHtml(item?.ranking_position || "-")}</span>
</div>
<div>
<span class="historical-elo-card__accuracy">${escapeHtml(formatAccuracyMode(item?.accuracy_mode))}</span>
</div>
</div>
<div>
<strong class="historical-mvp-card__player">${escapeHtml(
item?.player?.name || "Jugador no identificado",
)}</strong>
</div>
<div class="historical-elo-card__scores">
<article>
<span>Score mensual</span>
<strong>${escapeHtml(formatDecimal(item?.monthly_rank_score, 2))}</strong>
</article>
<article>
<span>MMR persistente</span>
<strong>${escapeHtml(formatDecimal(item?.persistent_rating?.mmr, 1))}</strong>
</article>
</div>
<div class="historical-elo-card__meta">
<article>
<span>MMR gain</span>
<strong>${escapeHtml(formatSignedDecimal(item?.persistent_rating?.mmr_gain, 1))}</strong>
</article>
<article>
<span>Elegibilidad</span>
<strong>${escapeHtml(item?.eligible ? "Elegible" : "Parcial")}</strong>
</article>
<article>
<span>Partidas validas</span>
<strong>${escapeHtml(formatNumber(item?.valid_matches))}</strong>
</article>
<article>
<span>Confidence</span>
<strong>${escapeHtml(formatDecimal(item?.components?.confidence, 1))}</strong>
</article>
</div>
<p class="historical-elo-card__summary">${escapeHtml(buildEloMmrSummary(item))}</p>
<p class="historical-elo-card__footer">${escapeHtml(buildEloMmrFooter(item, payload))}</p>
</article>
`;
}
function setState(node, message, isError = false) {
node.textContent = message;
node.hidden = false;
@@ -1275,6 +1410,10 @@ function buildMonthlyMvpV2SnapshotKey(serverSlug) {
return `monthly-mvp-v2:${serverSlug}`;
}
function buildEloMmrSnapshotKey(serverSlug) {
return `elo-mmr:${serverSlug}`;
}
function buildRangeLabel(start, end) {
if (!start && !end) {
return "";
@@ -1414,6 +1553,31 @@ function buildMonthlyMvpV2Footer(item, payload) {
)} partidas · ${formatDecimal(hoursPlayed, 1)} h jugadas`;
}
function buildEloMmrNote(payload) {
const monthLabel = formatMonthKey(payload?.month_key);
const exactRatio = Number(payload?.capabilities_summary?.exact_ratio || 0);
const approximateRatio = Number(payload?.capabilities_summary?.approximate_ratio || 0);
return `${monthLabel || "Mes activo"}. Rating persistente + score mensual con ${formatPercent(exactRatio)} de senal exacta y ${formatPercent(approximateRatio)} de senal aproximada en este corte.`;
}
function buildEloMmrMeta(payload) {
const sourceLabel = payload?.selected_source || payload?.source || "origen no disponible";
const fallbackLabel = payload?.fallback_used
? `fallback ${payload?.fallback_reason || "activo"}`
: "sin fallback";
return `Generado ${formatTimestamp(payload?.generated_at)} · fuente ${sourceLabel} · ${fallbackLabel}`;
}
function buildEloMmrSummary(item) {
return `AvgMatchScore ${formatDecimal(item?.components?.avg_match_score, 1)}, actividad ${formatDecimal(item?.components?.activity, 1)} y strength-of-schedule ${formatDecimal(item?.components?.strength_of_schedule, 1)}.`;
}
function buildEloMmrFooter(item, payload) {
const monthLabel = formatMonthKey(payload?.month_key);
const hoursPlayed = Number(item?.total_time_seconds) / 3600;
return `${monthLabel || "Mes activo"} · ${formatNumber(item?.valid_matches)} validas / ${formatNumber(item?.total_matches)} totales · ${formatDecimal(hoursPlayed, 1)} h`;
}
function buildMvpComparisonItems(v1Items, v2Items) {
const v1TopItems = v1Items.slice(0, 3);
const v2TopItems = v2Items.slice(0, 3);
@@ -1612,6 +1776,28 @@ function formatPercent(value) {
}).format(parsedValue * 100)} %`;
}
function formatSignedDecimal(value, fractionDigits = 1) {
const parsedValue = Number(value);
if (!Number.isFinite(parsedValue)) {
return "0";
}
const prefix = parsedValue > 0 ? "+" : "";
return `${prefix}${formatDecimal(parsedValue, fractionDigits)}`;
}
function formatAccuracyMode(mode) {
if (mode === "exact") {
return "Exacto";
}
if (mode === "approximate") {
return "Aproximado";
}
if (mode === "partial") {
return "Parcial";
}
return "Mixto";
}
function formatMonthKey(monthKey) {
if (!monthKey) {
return "";

View File

@@ -167,6 +167,27 @@
</div>
</section>
<section class="panel historical-panel" aria-labelledby="elo-mmr-title">
<div class="panel__shell">
<div class="panel__header historical-panel__header">
<div>
<p class="eyebrow eyebrow--section">Elo/MMR mensual</p>
<h2 id="elo-mmr-title">Rating persistente y ranking mensual del alcance activo</h2>
<p class="historical-panel__note" id="elo-mmr-note">
Cargando sistema mensual de rating...
</p>
<p class="historical-snapshot-meta" id="elo-mmr-meta">
Cargando metadata de Elo/MMR...
</p>
</div>
</div>
<p class="historical-state" id="elo-mmr-state" aria-live="polite">
Cargando leaderboard Elo/MMR...
</p>
<div class="historical-elo-grid" id="elo-mmr-list"></div>
</div>
</section>
<section class="panel historical-panel" aria-labelledby="weekly-ranking-title">
<div class="panel__shell">
<div class="panel__header historical-panel__header">