2188 lines
85 KiB
Python
2188 lines
85 KiB
Python
"""Payload builders for the HLL Vietnam backend."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
import re
|
|
|
|
from .config import (
|
|
get_historical_data_source_kind,
|
|
get_live_data_source_kind,
|
|
get_refresh_interval_seconds,
|
|
)
|
|
from .data_sources import (
|
|
LIVE_SOURCE_A2S,
|
|
SOURCE_KIND_PUBLIC_SCOREBOARD,
|
|
SOURCE_KIND_RCON,
|
|
build_source_attempt,
|
|
build_source_policy,
|
|
build_historical_runtime_source_policy,
|
|
describe_historical_runtime_policy,
|
|
get_live_data_source,
|
|
get_rcon_historical_read_model,
|
|
)
|
|
from .historical_snapshot_storage import get_historical_snapshot
|
|
from .historical_snapshots import (
|
|
DEFAULT_MONTHLY_SNAPSHOT_WINDOW,
|
|
DEFAULT_SNAPSHOT_WINDOW,
|
|
DEFAULT_WEEKLY_SNAPSHOT_WINDOW,
|
|
SNAPSHOT_TYPE_MONTHLY_LEADERBOARD,
|
|
SNAPSHOT_TYPE_MONTHLY_MVP,
|
|
SNAPSHOT_TYPE_MONTHLY_MVP_V2,
|
|
SNAPSHOT_TYPE_PLAYER_EVENT_DEATH_BY,
|
|
SNAPSHOT_TYPE_PLAYER_EVENT_DUELS,
|
|
SNAPSHOT_TYPE_PLAYER_EVENT_MOST_KILLED,
|
|
SNAPSHOT_TYPE_PLAYER_EVENT_TEAMKILLS,
|
|
SNAPSHOT_TYPE_PLAYER_EVENT_WEAPON_KILLS,
|
|
SNAPSHOT_TYPE_RECENT_MATCHES,
|
|
SNAPSHOT_TYPE_SERVER_SUMMARY,
|
|
SNAPSHOT_TYPE_WEEKLY_LEADERBOARD,
|
|
)
|
|
from .historical_storage import (
|
|
ALL_SERVERS_SLUG,
|
|
get_historical_match_detail,
|
|
get_historical_player_profile,
|
|
list_historical_server_summaries,
|
|
list_monthly_leaderboard,
|
|
list_recent_historical_matches,
|
|
list_weekly_leaderboard,
|
|
list_weekly_top_kills,
|
|
)
|
|
from .rcon_historical_read_model import get_rcon_historical_match_detail
|
|
from .normalizers import normalize_map_name
|
|
from .rcon_client import load_rcon_targets, query_live_server_sample
|
|
from .rcon_admin_log_storage import list_current_match_kill_feed, list_current_match_player_stats
|
|
from .scoreboard_origins import get_trusted_public_scoreboard_origin
|
|
from .storage import list_latest_snapshots, list_server_history, list_snapshot_history
|
|
|
|
|
|
def build_health_payload() -> dict[str, str]:
|
|
"""Return a small status payload without committing to business contracts."""
|
|
return {
|
|
"status": "ok",
|
|
"service": "hll-vietnam-backend",
|
|
"phase": "bootstrap",
|
|
"live_data_source": get_live_data_source_kind(),
|
|
"historical_data_source": get_historical_data_source_kind(),
|
|
"historical_runtime_policy": describe_historical_runtime_policy()["mode"],
|
|
"live_runtime_policy": (
|
|
"rcon-first-with-a2s-fallback"
|
|
if get_live_data_source_kind() == SOURCE_KIND_RCON
|
|
else "a2s-primary"
|
|
),
|
|
}
|
|
|
|
|
|
def build_community_payload() -> dict[str, object]:
|
|
"""Return placeholder community content aligned with the documented contract."""
|
|
return {
|
|
"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",
|
|
},
|
|
}
|
|
|
|
|
|
def build_trailer_payload() -> dict[str, object]:
|
|
"""Return placeholder trailer metadata for future frontend consumption."""
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"video_url": "https://www.youtube.com/embed/JzYzYNVWZ_A",
|
|
"title": "Trailer HLL Vietnam",
|
|
"provider": "youtube",
|
|
},
|
|
}
|
|
|
|
|
|
def build_discord_payload() -> dict[str, object]:
|
|
"""Return public Discord placeholder data without real integration."""
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"invite_url": "https://discord.com/invite/PedEqZ2Xsa",
|
|
"label": "Unirse al Discord",
|
|
"availability": "manual",
|
|
},
|
|
}
|
|
|
|
|
|
def build_servers_payload() -> dict[str, object]:
|
|
"""Return current server status, refreshing stale snapshots before responding."""
|
|
max_snapshot_age_seconds = get_refresh_interval_seconds()
|
|
persisted_items = _select_primary_snapshot_items(
|
|
_enrich_server_items(list_latest_snapshots())
|
|
)
|
|
persisted_snapshot_at = _resolve_last_snapshot_at(persisted_items)
|
|
persisted_snapshot_age_seconds = _calculate_snapshot_age_seconds(persisted_snapshot_at)
|
|
|
|
refresh_attempted = _should_refresh_snapshot(
|
|
persisted_items,
|
|
persisted_snapshot_age_seconds,
|
|
max_snapshot_age_seconds,
|
|
)
|
|
refresh_errors: list[dict[str, object]] = []
|
|
refresh_source_policy = build_source_policy(
|
|
primary_source=get_live_data_source_kind(),
|
|
selected_source="none",
|
|
fallback_reason=None,
|
|
source_attempts=[],
|
|
)
|
|
|
|
if refresh_attempted:
|
|
refreshed_items, refresh_errors, refresh_source_policy = _try_collect_real_time_snapshot()
|
|
if refreshed_items:
|
|
refreshed_snapshot_at = _resolve_last_snapshot_at(refreshed_items)
|
|
refreshed_snapshot_age_seconds = _calculate_snapshot_age_seconds(refreshed_snapshot_at)
|
|
return _build_servers_response(
|
|
items=refreshed_items,
|
|
response_source=_build_live_response_source(refresh_source_policy),
|
|
last_snapshot_at=refreshed_snapshot_at,
|
|
snapshot_age_seconds=refreshed_snapshot_age_seconds,
|
|
max_snapshot_age_seconds=max_snapshot_age_seconds,
|
|
refresh_attempted=True,
|
|
refresh_status="success",
|
|
refresh_errors=refresh_errors,
|
|
source_policy=refresh_source_policy,
|
|
)
|
|
|
|
if persisted_items:
|
|
refresh_status = "failed" if refresh_attempted else "not-needed"
|
|
response_source = (
|
|
"persisted-stale-snapshot" if refresh_attempted else "persisted-fresh-snapshot"
|
|
)
|
|
return _build_servers_response(
|
|
items=persisted_items,
|
|
response_source=response_source,
|
|
last_snapshot_at=persisted_snapshot_at,
|
|
snapshot_age_seconds=persisted_snapshot_age_seconds,
|
|
max_snapshot_age_seconds=max_snapshot_age_seconds,
|
|
refresh_attempted=refresh_attempted,
|
|
refresh_status=refresh_status,
|
|
refresh_errors=refresh_errors,
|
|
source_policy=_infer_live_source_policy_from_items(
|
|
persisted_items,
|
|
refresh_attempted=refresh_attempted,
|
|
refresh_errors=refresh_errors,
|
|
),
|
|
)
|
|
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Estado actual de servidores",
|
|
"context": "current-hll-status",
|
|
"source": "no-snapshot-available",
|
|
"last_snapshot_at": None,
|
|
"snapshot_age_seconds": None,
|
|
"snapshot_age_minutes": None,
|
|
"max_snapshot_age_seconds": max_snapshot_age_seconds,
|
|
"is_stale": True,
|
|
"freshness": "stale",
|
|
"refresh_attempted": refresh_attempted,
|
|
"refresh_status": "failed" if refresh_attempted else "not-needed",
|
|
"refresh_errors": refresh_errors,
|
|
**refresh_source_policy,
|
|
"items": [],
|
|
},
|
|
}
|
|
|
|
|
|
def build_server_latest_payload() -> dict[str, object]:
|
|
"""Return the latest persisted snapshot for each known server."""
|
|
items = _enrich_server_items(list_latest_snapshots())
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Ultimo estado conocido de servidores",
|
|
"context": "current-hll-history",
|
|
"source": "local-snapshot-storage",
|
|
"summary_window_size": 6,
|
|
"items": items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_server_history_payload(*, limit: int = 20) -> dict[str, object]:
|
|
"""Return recent persisted snapshots across all known servers."""
|
|
items = _enrich_server_items(list_snapshot_history(limit=limit))
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Historial reciente de servidores",
|
|
"context": "current-hll-history",
|
|
"source": "local-snapshot-storage",
|
|
"limit": limit,
|
|
"items": items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_server_detail_history_payload(
|
|
server_id: str,
|
|
*,
|
|
limit: int = 20,
|
|
) -> dict[str, object]:
|
|
"""Return recent persisted snapshots for one server."""
|
|
items = _enrich_server_items(list_server_history(server_id, limit=limit))
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Historial por servidor",
|
|
"context": "current-hll-history",
|
|
"source": "local-snapshot-storage",
|
|
"server_id": server_id,
|
|
"limit": limit,
|
|
"items": items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_current_match_payload(*, server_slug: str) -> dict[str, object]:
|
|
"""Return the live page projection for one trusted active server."""
|
|
origin = get_trusted_public_scoreboard_origin(server_slug)
|
|
if origin is None:
|
|
raise ValueError("Unsupported current match server.")
|
|
|
|
sample = _query_current_match_rcon_sample(origin.slug)
|
|
if sample is not None:
|
|
normalized = sample["normalized"]
|
|
raw_session = sample["raw_session"]
|
|
captured_at = _utc_timestamp_now()
|
|
map_id = raw_session.get("mapId") or normalized.get("current_map")
|
|
map_name = raw_session.get("mapName") or map_id
|
|
map_pretty_name = normalize_map_name(map_name)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"found": True,
|
|
"server_slug": origin.slug,
|
|
"server_name": normalized.get("server_name") or origin.display_name,
|
|
"status": normalized.get("status") or "unavailable",
|
|
"map": map_pretty_name,
|
|
"map_id": map_id,
|
|
"map_pretty_name": map_pretty_name,
|
|
"game_mode": normalized.get("game_mode"),
|
|
"started_at": None,
|
|
"allied_score": normalized.get("allied_score"),
|
|
"axis_score": normalized.get("axis_score"),
|
|
"allied_players": normalized.get("allied_players"),
|
|
"axis_players": normalized.get("axis_players"),
|
|
"players": normalized.get("players"),
|
|
"max_players": normalized.get("max_players"),
|
|
# RCA: getSession currently reports 0 while the public scoreboard
|
|
# can show players, so session population is exposed but unverified.
|
|
"player_count_quality": (
|
|
"rcon-session-unverified"
|
|
if normalized.get("players") is not None
|
|
else None
|
|
),
|
|
"player_count_source": _source_when_present(
|
|
normalized.get("players"),
|
|
source="rcon-session",
|
|
),
|
|
"score_source": _source_when_present(
|
|
normalized.get("allied_score"),
|
|
normalized.get("axis_score"),
|
|
source="rcon-session",
|
|
),
|
|
"map_source": _source_when_present(map_id, map_name, source="rcon-session"),
|
|
"match_time_seconds": normalized.get("match_time_seconds"),
|
|
"remaining_match_time_seconds": normalized.get(
|
|
"remaining_match_time_seconds"
|
|
),
|
|
"captured_at": captured_at,
|
|
"updated_at": captured_at,
|
|
"public_scoreboard_url": origin.base_url,
|
|
},
|
|
}
|
|
|
|
# The generic live server snapshot is a fallback only. It intentionally
|
|
# drops richer RCON session fields such as game mode and current scores.
|
|
server_payload = build_servers_payload()
|
|
server_data = server_payload["data"]
|
|
item = _find_current_match_snapshot_item(server_data.get("items", []), origin)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"found": item is not None,
|
|
"server_slug": origin.slug,
|
|
"server_name": item.get("server_name") if item else origin.display_name,
|
|
"status": item.get("status") if item else "unavailable",
|
|
"map": item.get("current_map") if item else None,
|
|
"map_id": None,
|
|
"map_pretty_name": item.get("current_map") if item else None,
|
|
"game_mode": item.get("game_mode") if item else None,
|
|
"started_at": item.get("started_at") if item else None,
|
|
"allied_score": item.get("allied_score") if item else None,
|
|
"axis_score": item.get("axis_score") if item else None,
|
|
"allied_players": item.get("allied_players") if item else None,
|
|
"axis_players": item.get("axis_players") if item else None,
|
|
"players": item.get("players") if item else None,
|
|
"max_players": item.get("max_players") if item else None,
|
|
"player_count_quality": _snapshot_player_count_quality(item),
|
|
"player_count_source": _snapshot_player_count_source(item),
|
|
"score_source": _source_when_present(
|
|
item.get("allied_score") if item else None,
|
|
item.get("axis_score") if item else None,
|
|
source="live-server-snapshot",
|
|
),
|
|
"map_source": _source_when_present(
|
|
item.get("current_map") if item else None,
|
|
source="live-server-snapshot",
|
|
),
|
|
"match_time_seconds": item.get("match_time_seconds") if item else None,
|
|
"remaining_match_time_seconds": (
|
|
item.get("remaining_match_time_seconds") if item else None
|
|
),
|
|
"captured_at": item.get("captured_at") if item else None,
|
|
"updated_at": server_data.get("last_snapshot_at"),
|
|
"public_scoreboard_url": origin.base_url,
|
|
},
|
|
}
|
|
|
|
|
|
def _find_current_match_snapshot_item(
|
|
items: list[dict[str, object]],
|
|
origin: object,
|
|
) -> dict[str, object] | None:
|
|
"""Resolve one trusted live snapshot for the current-match fallback."""
|
|
origin_slug = str(getattr(origin, "slug", "") or "").strip()
|
|
source_markers = {
|
|
"comunidad-hispana-01": ("152.114.195.174", ":7779"),
|
|
"comunidad-hispana-02": ("152.114.195.150", ":7879"),
|
|
}.get(origin_slug)
|
|
server_number = getattr(origin, "server_number", None)
|
|
|
|
for item in items:
|
|
if any(
|
|
str(item.get(field) or "").strip() == origin_slug
|
|
for field in (
|
|
"external_server_id",
|
|
"server_slug",
|
|
"target_key",
|
|
"slug",
|
|
"community_slug",
|
|
)
|
|
):
|
|
return item
|
|
|
|
server_label = str(item.get("server_name") or item.get("name") or "")
|
|
if _current_match_server_name_matches(server_label, server_number):
|
|
return item
|
|
|
|
source_identity = " ".join(
|
|
str(item.get(field) or "") for field in ("external_server_id", "source_ref")
|
|
)
|
|
if source_markers and any(marker in source_identity for marker in source_markers):
|
|
return item
|
|
|
|
return None
|
|
|
|
|
|
def _current_match_server_name_matches(server_label: str, server_number: object) -> bool:
|
|
if not isinstance(server_number, int):
|
|
return False
|
|
|
|
normalized_label = server_label.strip().casefold()
|
|
if not normalized_label:
|
|
return False
|
|
|
|
numbered_marker = re.compile(rf"(?<!\d)#0*{server_number}(?!\d)")
|
|
if numbered_marker.search(normalized_label):
|
|
return True
|
|
|
|
return f"comunidad hispana #{server_number:02d}" in normalized_label
|
|
|
|
|
|
def build_current_match_kill_feed_payload(
|
|
*,
|
|
server_slug: str,
|
|
limit: int = 30,
|
|
since_event_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return normalized AdminLog kill rows for one trusted current-match page."""
|
|
origin = get_trusted_public_scoreboard_origin(server_slug)
|
|
if origin is None:
|
|
raise ValueError("Unsupported current match server.")
|
|
feed = list_current_match_kill_feed(
|
|
server_key=origin.slug,
|
|
limit=limit,
|
|
since_event_id=since_event_id,
|
|
)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"server_slug": origin.slug,
|
|
"server_name": origin.display_name,
|
|
**feed,
|
|
},
|
|
}
|
|
|
|
|
|
def build_current_match_player_stats_payload(*, server_slug: str) -> dict[str, object]:
|
|
"""Return current player stats only when safe AdminLog evidence exists."""
|
|
origin = get_trusted_public_scoreboard_origin(server_slug)
|
|
if origin is None:
|
|
raise ValueError("Unsupported current match server.")
|
|
stats = list_current_match_player_stats(server_key=origin.slug)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"server_slug": origin.slug,
|
|
"server_name": origin.display_name,
|
|
**stats,
|
|
},
|
|
}
|
|
|
|
|
|
def _query_current_match_rcon_sample(server_slug: str) -> dict[str, object] | None:
|
|
"""Read one configured trusted RCON target for the current-match view."""
|
|
try:
|
|
targets = load_rcon_targets()
|
|
except (RuntimeError, ValueError):
|
|
return None
|
|
target = next(
|
|
(candidate for candidate in targets if candidate.external_server_id == server_slug),
|
|
None,
|
|
)
|
|
if target is None:
|
|
return None
|
|
try:
|
|
return query_live_server_sample(target)
|
|
except Exception: # noqa: BLE001 - fall back to the existing live snapshot read
|
|
return None
|
|
|
|
|
|
def _utc_timestamp_now() -> str:
|
|
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
|
|
|
|
def _source_when_present(*values: object, source: str) -> str | None:
|
|
return source if any(value is not None for value in values) else None
|
|
|
|
|
|
def _snapshot_player_count_quality(item: dict[str, object] | None) -> str | None:
|
|
if item is None or item.get("players") is None:
|
|
return None
|
|
if item.get("snapshot_origin") == "real-rcon":
|
|
return "rcon-session-unverified"
|
|
if item.get("snapshot_origin") == "real-a2s":
|
|
return "a2s-query"
|
|
return "snapshot-unverified"
|
|
|
|
|
|
def _snapshot_player_count_source(item: dict[str, object] | None) -> str | None:
|
|
if item is None or item.get("players") is None:
|
|
return None
|
|
if item.get("snapshot_origin") == "real-rcon":
|
|
return "rcon-session"
|
|
if item.get("snapshot_origin") == "real-a2s":
|
|
return "a2s"
|
|
return "live-server-snapshot"
|
|
|
|
|
|
def build_error_payload(message: str) -> dict[str, str]:
|
|
"""Return the shared error payload shape used by the backend bootstrap."""
|
|
return {
|
|
"status": "error",
|
|
"message": message,
|
|
}
|
|
|
|
|
|
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",
|
|
"summary_basis": "closed-matches-last-7-days",
|
|
"window_days": 7,
|
|
"window_start": result["window_start"],
|
|
"window_end": result["window_end"],
|
|
"limit": limit,
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-weekly-top-kills",
|
|
),
|
|
"items": result["items"],
|
|
},
|
|
}
|
|
|
|
|
|
def build_historical_leaderboard_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
metric: str = "kills",
|
|
timeframe: str = "weekly",
|
|
) -> dict[str, object]:
|
|
"""Return one historical leaderboard for the requested timeframe and metric."""
|
|
normalized_timeframe = timeframe.strip().lower() if isinstance(timeframe, str) else "weekly"
|
|
if normalized_timeframe == "monthly":
|
|
result = list_monthly_leaderboard(limit=limit, server_id=server_id, metric=metric)
|
|
summary_basis = "closed-matches-calendar-month"
|
|
context = "historical-monthly-leaderboard"
|
|
else:
|
|
normalized_timeframe = "weekly"
|
|
result = list_weekly_leaderboard(limit=limit, server_id=server_id, metric=metric)
|
|
summary_basis = "closed-matches-calendar-week"
|
|
context = "historical-weekly-leaderboard"
|
|
|
|
is_all_servers = server_id == ALL_SERVERS_SLUG
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": _build_leaderboard_title(
|
|
metric=metric,
|
|
timeframe=normalized_timeframe,
|
|
is_all_servers=is_all_servers,
|
|
),
|
|
"context": context,
|
|
"timeframe": normalized_timeframe,
|
|
"metric": metric,
|
|
"summary_basis": summary_basis,
|
|
"window_days": result.get("window_days", 7),
|
|
"window_start": result["window_start"],
|
|
"window_end": result["window_end"],
|
|
"window_kind": result.get("window_kind"),
|
|
"window_label": result.get("window_label"),
|
|
"uses_fallback": bool(result.get("uses_fallback")),
|
|
"selection_reason": result.get("selection_reason"),
|
|
"current_week_start": result.get("current_week_start"),
|
|
"current_week_closed_matches": result.get("current_week_closed_matches"),
|
|
"previous_week_closed_matches": result.get("previous_week_closed_matches"),
|
|
"current_month_start": result.get("current_month_start"),
|
|
"current_month_closed_matches": result.get("current_month_closed_matches"),
|
|
"previous_month_closed_matches": result.get("previous_month_closed_matches"),
|
|
"sufficient_sample": result.get("sufficient_sample"),
|
|
"limit": limit,
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-competitive-leaderboards",
|
|
),
|
|
"items": result["items"],
|
|
},
|
|
}
|
|
|
|
|
|
def build_weekly_leaderboard_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
metric: str = "kills",
|
|
) -> dict[str, object]:
|
|
"""Return one weekly historical leaderboard for the requested metric."""
|
|
return build_historical_leaderboard_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
metric=metric,
|
|
timeframe="weekly",
|
|
)
|
|
|
|
|
|
def build_monthly_leaderboard_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
metric: str = "kills",
|
|
) -> dict[str, object]:
|
|
"""Return one monthly historical leaderboard for the requested metric."""
|
|
return build_historical_leaderboard_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
metric=metric,
|
|
timeframe="monthly",
|
|
)
|
|
|
|
|
|
def build_recent_historical_matches_payload(
|
|
*,
|
|
limit: int = 20,
|
|
server_slug: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return recent historical matches from persisted CRCON data."""
|
|
if get_historical_data_source_kind() == "rcon":
|
|
data_source = get_rcon_historical_read_model()
|
|
if data_source is not None:
|
|
capabilities = data_source.describe_capabilities()
|
|
try:
|
|
items = data_source.list_recent_activity(server_key=server_slug, limit=limit)
|
|
except Exception as error: # noqa: BLE001 - explicit runtime fallback boundary
|
|
items = []
|
|
rcon_source_policy = build_historical_runtime_source_policy(
|
|
operation="historical-recent-matches",
|
|
rcon_status="error",
|
|
fallback_reason="rcon-historical-read-model-request-failed",
|
|
rcon_message=str(error),
|
|
)
|
|
else:
|
|
rcon_source_policy = build_historical_runtime_source_policy(
|
|
operation="historical-recent-matches",
|
|
rcon_status=(
|
|
"success"
|
|
if data_source.has_recent_activity_coverage(items)
|
|
else "empty"
|
|
),
|
|
fallback_reason="rcon-historical-read-model-has-no-recent-activity",
|
|
)
|
|
|
|
if not bool(rcon_source_policy.get("fallback_used")):
|
|
if 0 < len(items) < limit and not _recent_items_include_rcon_results(items):
|
|
fallback_items = [
|
|
_with_recent_result_source(item, "public-scoreboard-fallback")
|
|
for item in list_recent_historical_matches(
|
|
limit=limit,
|
|
server_slug=server_slug,
|
|
)
|
|
]
|
|
merged_items = _merge_recent_match_items(
|
|
primary_items=items,
|
|
fallback_items=fallback_items,
|
|
limit=limit,
|
|
)
|
|
if len(merged_items) > len(items):
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Actividad competitiva reciente capturada por RCON",
|
|
"context": "historical-recent-matches",
|
|
"source": "hybrid-rcon-plus-public-scoreboard",
|
|
"historical_data_source": "rcon",
|
|
"supported": True,
|
|
"coverage_basis": "rcon-competitive-windows-plus-public-scoreboard-fallback",
|
|
"limit": limit,
|
|
"server_slug": server_slug,
|
|
**build_source_policy(
|
|
primary_source=SOURCE_KIND_RCON,
|
|
selected_source="hybrid-rcon-plus-public-scoreboard",
|
|
fallback_used=True,
|
|
fallback_reason=(
|
|
"rcon-historical-recent-matches-did-not-reach-requested-limit"
|
|
),
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_RCON,
|
|
role="primary",
|
|
status="success",
|
|
reason="historical-recent-matches-served-by-rcon",
|
|
),
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_PUBLIC_SCOREBOARD,
|
|
role="fallback",
|
|
status="success",
|
|
reason="historical-recent-matches-completed-from-public-scoreboard",
|
|
message=(
|
|
f"RCON returned {len(items)} items, completed to "
|
|
f"{len(merged_items)} of requested {limit}."
|
|
),
|
|
),
|
|
],
|
|
),
|
|
"items": merged_items,
|
|
"capabilities": capabilities,
|
|
},
|
|
}
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Actividad competitiva reciente capturada por RCON",
|
|
"context": "historical-recent-matches",
|
|
"source": "rcon-historical-competitive-read-model",
|
|
"historical_data_source": "rcon",
|
|
"supported": True,
|
|
"coverage_basis": "rcon-competitive-windows",
|
|
"limit": limit,
|
|
"server_slug": server_slug,
|
|
**rcon_source_policy,
|
|
"items": items,
|
|
"capabilities": capabilities,
|
|
},
|
|
}
|
|
items = [
|
|
_with_recent_result_source(item, "public-scoreboard-fallback")
|
|
for item in list_recent_historical_matches(limit=limit, server_slug=server_slug)
|
|
]
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Partidas recientes por servidor",
|
|
"context": "historical-recent-matches",
|
|
"source": "historical-crcon-storage",
|
|
"limit": limit,
|
|
"server_slug": server_slug,
|
|
**(
|
|
rcon_source_policy
|
|
if get_historical_data_source_kind() == "rcon"
|
|
and "rcon_source_policy" in locals()
|
|
else _resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-has-no-recent-activity",
|
|
)
|
|
),
|
|
"items": items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_historical_match_detail_payload(
|
|
*,
|
|
server_slug: str,
|
|
match_id: str,
|
|
) -> dict[str, object]:
|
|
"""Return available detail for one historical match without inventing external URLs."""
|
|
if get_historical_data_source_kind() == SOURCE_KIND_RCON:
|
|
item = get_rcon_historical_match_detail(
|
|
server_key=server_slug,
|
|
match_id=match_id,
|
|
)
|
|
if item is not None:
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Detalle de partida historica",
|
|
"context": "historical-match-detail",
|
|
"source": "rcon-historical-competitive-read-model",
|
|
"found": True,
|
|
**build_source_policy(
|
|
primary_source=SOURCE_KIND_RCON,
|
|
selected_source=SOURCE_KIND_RCON,
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_RCON,
|
|
role="primary",
|
|
status="success",
|
|
reason="historical-match-detail-served-by-rcon",
|
|
)
|
|
],
|
|
),
|
|
"item": item,
|
|
},
|
|
}
|
|
|
|
item = get_historical_match_detail(server_slug=server_slug, match_id=match_id)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Detalle de partida historica",
|
|
"context": "historical-match-detail",
|
|
"source": "historical-crcon-storage",
|
|
"found": item is not None,
|
|
**(
|
|
_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-has-no-match-detail"
|
|
)
|
|
if get_historical_data_source_kind() == SOURCE_KIND_RCON
|
|
else build_source_policy(
|
|
primary_source=SOURCE_KIND_PUBLIC_SCOREBOARD,
|
|
selected_source=SOURCE_KIND_PUBLIC_SCOREBOARD,
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_PUBLIC_SCOREBOARD,
|
|
role="primary",
|
|
status="success" if item is not None else "empty",
|
|
reason="historical-match-detail-served-by-public-scoreboard",
|
|
)
|
|
],
|
|
)
|
|
),
|
|
"item": item,
|
|
},
|
|
}
|
|
|
|
|
|
def build_monthly_mvp_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return the precomputed monthly MVP payload through the stable API surface."""
|
|
snapshot_payload = build_monthly_mvp_snapshot_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
)
|
|
data = snapshot_payload["data"]
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
**data,
|
|
"title": _build_monthly_mvp_title(
|
|
is_all_servers=server_id == ALL_SERVERS_SLUG,
|
|
snapshot=False,
|
|
),
|
|
"context": "historical-monthly-mvp",
|
|
"source": "historical-precomputed-snapshots",
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-monthly-mvp-yet",
|
|
),
|
|
},
|
|
}
|
|
|
|
|
|
def build_player_event_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
view: str = "most-killed",
|
|
) -> dict[str, object]:
|
|
"""Return one V2 player-event payload through the stable API surface."""
|
|
snapshot_payload = build_player_event_snapshot_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
view=view,
|
|
)
|
|
data = snapshot_payload["data"]
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
**data,
|
|
"title": _build_player_event_title(
|
|
view=view,
|
|
is_all_servers=server_id == ALL_SERVERS_SLUG,
|
|
snapshot=False,
|
|
),
|
|
"context": "historical-player-events",
|
|
"source": "historical-precomputed-player-event-snapshots",
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-player-events-yet",
|
|
),
|
|
},
|
|
}
|
|
|
|
|
|
def build_monthly_mvp_v2_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return the precomputed monthly MVP V2 payload through the stable API surface."""
|
|
snapshot_payload = build_monthly_mvp_v2_snapshot_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
)
|
|
data = snapshot_payload["data"]
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
**data,
|
|
"title": _build_monthly_mvp_v2_title(
|
|
is_all_servers=server_id == ALL_SERVERS_SLUG,
|
|
snapshot=False,
|
|
),
|
|
"context": "historical-monthly-mvp-v2",
|
|
"source": "historical-precomputed-snapshots",
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-monthly-mvp-v2-yet",
|
|
),
|
|
},
|
|
}
|
|
|
|
|
|
def build_historical_server_summary_snapshot_payload(
|
|
*,
|
|
server_slug: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed summary snapshot without recalculating aggregates."""
|
|
snapshot = _get_historical_snapshot_record(
|
|
server_key=server_slug,
|
|
snapshot_type=SNAPSHOT_TYPE_SERVER_SUMMARY,
|
|
window=DEFAULT_SNAPSHOT_WINDOW,
|
|
)
|
|
payload = snapshot.get("payload") if snapshot else {}
|
|
item = payload.get("item") if isinstance(payload, dict) else None
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Snapshot historico de resumen por servidor",
|
|
"context": "historical-server-summary-snapshot",
|
|
"source": "historical-precomputed-snapshots",
|
|
"server_slug": server_slug,
|
|
"found": snapshot is not None and isinstance(item, dict),
|
|
**(
|
|
build_source_policy(
|
|
primary_source=SOURCE_KIND_RCON,
|
|
selected_source=SOURCE_KIND_RCON,
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_RCON,
|
|
role="primary",
|
|
status="success",
|
|
reason="server-summary-snapshot-served-by-rcon-competitive-model",
|
|
)
|
|
],
|
|
)
|
|
if get_historical_data_source_kind() == SOURCE_KIND_RCON and isinstance(item, dict)
|
|
else _resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-historical-snapshots-yet",
|
|
)
|
|
),
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"item": item if isinstance(item, dict) else None,
|
|
},
|
|
}
|
|
|
|
|
|
def build_leaderboard_snapshot_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
metric: str = "kills",
|
|
timeframe: str = "weekly",
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed leaderboard snapshot for the requested timeframe."""
|
|
normalized_timeframe = timeframe.strip().lower() if isinstance(timeframe, str) else "weekly"
|
|
if normalized_timeframe == "monthly":
|
|
snapshot_type = SNAPSHOT_TYPE_MONTHLY_LEADERBOARD
|
|
window = DEFAULT_MONTHLY_SNAPSHOT_WINDOW
|
|
context = "historical-monthly-leaderboard-snapshot"
|
|
else:
|
|
normalized_timeframe = "weekly"
|
|
snapshot_type = SNAPSHOT_TYPE_WEEKLY_LEADERBOARD
|
|
window = DEFAULT_WEEKLY_SNAPSHOT_WINDOW
|
|
context = "historical-weekly-leaderboard-snapshot"
|
|
|
|
snapshot = _get_historical_snapshot_record(
|
|
server_key=server_id,
|
|
snapshot_type=snapshot_type,
|
|
metric=metric,
|
|
window=window,
|
|
)
|
|
payload = snapshot.get("payload") if snapshot else {}
|
|
items = payload.get("items") if isinstance(payload, dict) else None
|
|
sliced_items = list(items[:limit]) if isinstance(items, list) else []
|
|
runtime_enrichment_applied = False
|
|
if _leaderboard_snapshot_items_need_playtime_enrichment(sliced_items):
|
|
runtime_items = _load_runtime_leaderboard_items(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
metric=metric,
|
|
timeframe=normalized_timeframe,
|
|
)
|
|
if runtime_items:
|
|
sliced_items = runtime_items[:limit]
|
|
runtime_enrichment_applied = True
|
|
is_all_servers = server_id == ALL_SERVERS_SLUG
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": _build_leaderboard_title(
|
|
metric=metric,
|
|
timeframe=normalized_timeframe,
|
|
is_all_servers=is_all_servers,
|
|
snapshot=True,
|
|
),
|
|
"context": context,
|
|
"source": "historical-precomputed-snapshots",
|
|
"server_slug": server_id,
|
|
"timeframe": normalized_timeframe,
|
|
"metric": metric,
|
|
"found": snapshot is not None,
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"window_days": payload.get("window_days") if isinstance(payload, dict) else 7,
|
|
"window_start": payload.get("window_start") if isinstance(payload, dict) else None,
|
|
"window_end": payload.get("window_end") if isinstance(payload, dict) else None,
|
|
"window_kind": payload.get("window_kind") if isinstance(payload, dict) else None,
|
|
"window_label": payload.get("window_label") if isinstance(payload, dict) else None,
|
|
"uses_fallback": bool(payload.get("uses_fallback")) if isinstance(payload, dict) else False,
|
|
"selection_reason": payload.get("selection_reason") if isinstance(payload, dict) else None,
|
|
"current_week_start": payload.get("current_week_start") if isinstance(payload, dict) else None,
|
|
"current_week_closed_matches": (
|
|
payload.get("current_week_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"previous_week_closed_matches": (
|
|
payload.get("previous_week_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"current_month_start": payload.get("current_month_start") if isinstance(payload, dict) else None,
|
|
"current_month_closed_matches": (
|
|
payload.get("current_month_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"previous_month_closed_matches": (
|
|
payload.get("previous_month_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"sufficient_sample": payload.get("sufficient_sample") if isinstance(payload, dict) else None,
|
|
"snapshot_limit": payload.get("limit") if isinstance(payload, dict) else None,
|
|
"limit": limit,
|
|
"runtime_enrichment": {
|
|
"applied": runtime_enrichment_applied,
|
|
"reason": (
|
|
"snapshot-items-missing-total-time-seconds"
|
|
if runtime_enrichment_applied
|
|
else None
|
|
),
|
|
},
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-historical-snapshots-yet",
|
|
),
|
|
"items": sliced_items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_weekly_leaderboard_snapshot_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
metric: str = "kills",
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed weekly leaderboard snapshot."""
|
|
return build_leaderboard_snapshot_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
metric=metric,
|
|
timeframe="weekly",
|
|
)
|
|
|
|
|
|
def build_monthly_leaderboard_snapshot_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
metric: str = "kills",
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed monthly leaderboard snapshot."""
|
|
return build_leaderboard_snapshot_payload(
|
|
limit=limit,
|
|
server_id=server_id,
|
|
metric=metric,
|
|
timeframe="monthly",
|
|
)
|
|
|
|
|
|
def build_recent_historical_matches_snapshot_payload(
|
|
*,
|
|
limit: int = 20,
|
|
server_slug: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed recent-matches snapshot."""
|
|
snapshot = _get_historical_snapshot_record(
|
|
server_key=server_slug,
|
|
snapshot_type=SNAPSHOT_TYPE_RECENT_MATCHES,
|
|
window=DEFAULT_SNAPSHOT_WINDOW,
|
|
)
|
|
payload = snapshot.get("payload") if snapshot else {}
|
|
items = payload.get("items") if isinstance(payload, dict) else None
|
|
sliced_items = list(items[:limit]) if isinstance(items, list) else []
|
|
if (
|
|
get_historical_data_source_kind() == SOURCE_KIND_RCON
|
|
and 0 < len(sliced_items) < limit
|
|
):
|
|
fallback_items = list_recent_historical_matches(limit=limit, server_slug=server_slug)
|
|
merged_items = _merge_recent_match_items(
|
|
primary_items=sliced_items,
|
|
fallback_items=fallback_items,
|
|
limit=limit,
|
|
)
|
|
if len(merged_items) > len(sliced_items):
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Snapshot historico de partidas recientes por servidor",
|
|
"context": "historical-recent-matches-snapshot",
|
|
"source": "historical-precomputed-snapshots",
|
|
"server_slug": server_slug,
|
|
"found": snapshot is not None,
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"snapshot_limit": payload.get("limit") if isinstance(payload, dict) else None,
|
|
"limit": limit,
|
|
**build_source_policy(
|
|
primary_source=SOURCE_KIND_RCON,
|
|
selected_source="hybrid-rcon-plus-public-scoreboard",
|
|
fallback_used=True,
|
|
fallback_reason="rcon-historical-recent-matches-did-not-reach-requested-limit",
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_RCON,
|
|
role="primary",
|
|
status="success",
|
|
reason="recent-matches-snapshot-served-by-rcon-competitive-model",
|
|
),
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_PUBLIC_SCOREBOARD,
|
|
role="fallback",
|
|
status="success",
|
|
reason="recent-matches-snapshot-completed-from-public-scoreboard",
|
|
message=(
|
|
f"RCON snapshot returned {len(sliced_items)} items, completed to "
|
|
f"{len(merged_items)} of requested {limit}."
|
|
),
|
|
),
|
|
],
|
|
),
|
|
"items": merged_items,
|
|
},
|
|
}
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Snapshot historico de partidas recientes por servidor",
|
|
"context": "historical-recent-matches-snapshot",
|
|
"source": "historical-precomputed-snapshots",
|
|
"server_slug": server_slug,
|
|
"found": snapshot is not None,
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"snapshot_limit": payload.get("limit") if isinstance(payload, dict) else None,
|
|
"limit": limit,
|
|
**(
|
|
build_source_policy(
|
|
primary_source=SOURCE_KIND_RCON,
|
|
selected_source=SOURCE_KIND_RCON,
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=SOURCE_KIND_RCON,
|
|
role="primary",
|
|
status="success",
|
|
reason="recent-matches-snapshot-served-by-rcon-competitive-model",
|
|
)
|
|
],
|
|
)
|
|
if get_historical_data_source_kind() == SOURCE_KIND_RCON and sliced_items
|
|
else _resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-historical-snapshots-yet",
|
|
)
|
|
),
|
|
"items": sliced_items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_monthly_mvp_snapshot_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed monthly MVP snapshot."""
|
|
snapshot = _get_historical_snapshot_record(
|
|
server_key=server_id,
|
|
snapshot_type=SNAPSHOT_TYPE_MONTHLY_MVP,
|
|
window=DEFAULT_MONTHLY_SNAPSHOT_WINDOW,
|
|
)
|
|
payload = snapshot.get("payload") if snapshot else {}
|
|
items = payload.get("items") if isinstance(payload, dict) else None
|
|
sliced_items = list(items[:limit]) if isinstance(items, list) else []
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": _build_monthly_mvp_title(
|
|
is_all_servers=server_id == ALL_SERVERS_SLUG,
|
|
snapshot=True,
|
|
),
|
|
"context": "historical-monthly-mvp-snapshot",
|
|
"source": "historical-precomputed-snapshots",
|
|
"server_slug": server_id,
|
|
"timeframe": "monthly",
|
|
"metric": "mvp",
|
|
"found": snapshot is not None,
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"month_key": payload.get("month_key") if isinstance(payload, dict) else None,
|
|
"window_days": payload.get("window_days") if isinstance(payload, dict) else None,
|
|
"window_start": payload.get("window_start") if isinstance(payload, dict) else None,
|
|
"window_end": payload.get("window_end") if isinstance(payload, dict) else None,
|
|
"window_kind": payload.get("window_kind") if isinstance(payload, dict) else None,
|
|
"window_label": payload.get("window_label") if isinstance(payload, dict) else None,
|
|
"uses_fallback": bool(payload.get("uses_fallback")) if isinstance(payload, dict) else False,
|
|
"selection_reason": payload.get("selection_reason") if isinstance(payload, dict) else None,
|
|
"current_month_start": payload.get("current_month_start") if isinstance(payload, dict) else None,
|
|
"current_month_closed_matches": (
|
|
payload.get("current_month_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"previous_month_closed_matches": (
|
|
payload.get("previous_month_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"sufficient_sample": payload.get("sufficient_sample") if isinstance(payload, dict) else None,
|
|
"eligibility": payload.get("eligibility") if isinstance(payload, dict) else None,
|
|
"ranking_version": payload.get("ranking_version") if isinstance(payload, dict) else None,
|
|
"eligible_players_count": (
|
|
payload.get("eligible_players_count") if isinstance(payload, dict) else 0
|
|
),
|
|
"snapshot_limit": payload.get("limit") if isinstance(payload, dict) else None,
|
|
"limit": limit,
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-historical-snapshots-yet",
|
|
),
|
|
"items": sliced_items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_monthly_mvp_v2_snapshot_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed monthly MVP V2 snapshot."""
|
|
snapshot = _get_historical_snapshot_record(
|
|
server_key=server_id,
|
|
snapshot_type=SNAPSHOT_TYPE_MONTHLY_MVP_V2,
|
|
window=DEFAULT_MONTHLY_SNAPSHOT_WINDOW,
|
|
)
|
|
payload = snapshot.get("payload") if snapshot else {}
|
|
items = payload.get("items") if isinstance(payload, dict) else None
|
|
sliced_items = list(items[:limit]) if isinstance(items, list) else []
|
|
found = bool(payload.get("found")) if isinstance(payload, dict) else False
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": _build_monthly_mvp_v2_title(
|
|
is_all_servers=server_id == ALL_SERVERS_SLUG,
|
|
snapshot=True,
|
|
),
|
|
"context": "historical-monthly-mvp-v2-snapshot",
|
|
"source": "historical-precomputed-snapshots",
|
|
"server_slug": server_id,
|
|
"timeframe": "monthly",
|
|
"metric": "mvp-v2",
|
|
"found": snapshot is not None and found,
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"month_key": payload.get("month_key") if isinstance(payload, dict) else None,
|
|
"window_days": payload.get("window_days") if isinstance(payload, dict) else None,
|
|
"window_start": payload.get("window_start") if isinstance(payload, dict) else None,
|
|
"window_end": payload.get("window_end") if isinstance(payload, dict) else None,
|
|
"window_kind": payload.get("window_kind") if isinstance(payload, dict) else None,
|
|
"window_label": payload.get("window_label") if isinstance(payload, dict) else None,
|
|
"uses_fallback": bool(payload.get("uses_fallback")) if isinstance(payload, dict) else False,
|
|
"selection_reason": payload.get("selection_reason") if isinstance(payload, dict) else None,
|
|
"current_month_start": payload.get("current_month_start") if isinstance(payload, dict) else None,
|
|
"current_month_closed_matches": (
|
|
payload.get("current_month_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"previous_month_closed_matches": (
|
|
payload.get("previous_month_closed_matches") if isinstance(payload, dict) else None
|
|
),
|
|
"sufficient_sample": payload.get("sufficient_sample") if isinstance(payload, dict) else None,
|
|
"eligibility": payload.get("eligibility") if isinstance(payload, dict) else None,
|
|
"ranking_version": payload.get("ranking_version") if isinstance(payload, dict) else None,
|
|
"event_coverage": payload.get("event_coverage") if isinstance(payload, dict) else None,
|
|
"eligible_players_count": (
|
|
payload.get("eligible_players_count") if isinstance(payload, dict) else 0
|
|
),
|
|
"snapshot_limit": payload.get("limit") if isinstance(payload, dict) else None,
|
|
"limit": limit,
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-historical-snapshots-yet",
|
|
),
|
|
"items": sliced_items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_player_event_snapshot_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
view: str = "most-killed",
|
|
) -> dict[str, object]:
|
|
"""Return one precomputed V2 player-event snapshot."""
|
|
snapshot_type = _resolve_player_event_snapshot_type(view)
|
|
snapshot = _get_historical_snapshot_record(
|
|
server_key=server_id,
|
|
snapshot_type=snapshot_type,
|
|
window=DEFAULT_MONTHLY_SNAPSHOT_WINDOW,
|
|
)
|
|
payload = snapshot.get("payload") if snapshot else {}
|
|
items = payload.get("items") if isinstance(payload, dict) else None
|
|
sliced_items = list(items[:limit]) if isinstance(items, list) else []
|
|
found = bool(payload.get("found")) if isinstance(payload, dict) else False
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": _build_player_event_title(
|
|
view=view,
|
|
is_all_servers=server_id == ALL_SERVERS_SLUG,
|
|
snapshot=True,
|
|
),
|
|
"context": "historical-player-events-snapshot",
|
|
"source": "historical-precomputed-player-event-snapshots",
|
|
"server_slug": server_id,
|
|
"timeframe": "monthly",
|
|
"metric": view,
|
|
"found": snapshot is not None and found,
|
|
**_build_historical_snapshot_metadata(snapshot),
|
|
"period": payload.get("period") if isinstance(payload, dict) else "monthly",
|
|
"month_key": payload.get("month_key") if isinstance(payload, dict) else None,
|
|
"snapshot_limit": payload.get("limit") if isinstance(payload, dict) else None,
|
|
"limit": limit,
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-historical-snapshots-yet",
|
|
),
|
|
"items": sliced_items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_historical_server_summary_payload(
|
|
*,
|
|
server_slug: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return aggregated historical metrics per server."""
|
|
if get_historical_data_source_kind() == "rcon":
|
|
data_source = get_rcon_historical_read_model()
|
|
if data_source is not None:
|
|
capabilities = data_source.describe_capabilities()
|
|
try:
|
|
items = data_source.list_server_summaries(server_key=server_slug)
|
|
except Exception as error: # noqa: BLE001 - explicit runtime fallback boundary
|
|
items = []
|
|
rcon_source_policy = build_historical_runtime_source_policy(
|
|
operation="historical-server-summary",
|
|
rcon_status="error",
|
|
fallback_reason="rcon-historical-read-model-request-failed",
|
|
rcon_message=str(error),
|
|
)
|
|
else:
|
|
rcon_source_policy = build_historical_runtime_source_policy(
|
|
operation="historical-server-summary",
|
|
rcon_status=(
|
|
"success"
|
|
if data_source.has_server_summary_coverage(items)
|
|
else "empty"
|
|
),
|
|
fallback_reason="rcon-historical-read-model-has-no-summary-coverage",
|
|
)
|
|
|
|
if not bool(rcon_source_policy.get("fallback_used")):
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": (
|
|
"Cobertura historica minima por RCON"
|
|
if server_slug != ALL_SERVERS_SLUG
|
|
else "Cobertura historica minima RCON agregada"
|
|
),
|
|
"context": "historical-server-summary",
|
|
"source": "rcon-historical-competitive-read-model",
|
|
"historical_data_source": "rcon",
|
|
"summary_basis": "rcon-competitive-windows",
|
|
"server_slug": server_slug,
|
|
"supported": True,
|
|
**rcon_source_policy,
|
|
"items": items,
|
|
"capabilities": capabilities,
|
|
},
|
|
}
|
|
items = list_historical_server_summaries(server_slug=server_slug)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": (
|
|
"Cobertura historica agregada de todos los servidores"
|
|
if server_slug == ALL_SERVERS_SLUG
|
|
else "Cobertura historica importada por servidor"
|
|
),
|
|
"context": "historical-server-summary",
|
|
"source": "historical-crcon-storage",
|
|
"summary_basis": "persisted-import",
|
|
"weekly_ranking_window_days": 7,
|
|
"server_slug": server_slug,
|
|
**(
|
|
rcon_source_policy
|
|
if get_historical_data_source_kind() == "rcon"
|
|
and "rcon_source_policy" in locals()
|
|
else _resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-has-no-summary-coverage",
|
|
)
|
|
),
|
|
"items": items,
|
|
},
|
|
}
|
|
|
|
|
|
def build_historical_player_profile_payload(player_id: str) -> dict[str, object]:
|
|
"""Return aggregate historical metrics for one player identity."""
|
|
profile = get_historical_player_profile(player_id)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Perfil historico de jugador",
|
|
"context": "historical-player-profile",
|
|
"source": "historical-crcon-storage",
|
|
"player_id": player_id,
|
|
"found": profile is not None,
|
|
**_resolve_historical_fallback_policy(
|
|
fallback_reason="rcon-historical-read-model-does-not-support-player-profile-yet",
|
|
),
|
|
"profile": profile,
|
|
},
|
|
}
|
|
|
|
|
|
def build_elo_mmr_leaderboard_payload(
|
|
*,
|
|
limit: int = 10,
|
|
server_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return the current Elo/MMR monthly leaderboard."""
|
|
engine = _load_elo_mmr_engine()
|
|
if engine is None:
|
|
return _build_elo_mmr_unavailable_payload(
|
|
context="historical-elo-mmr-leaderboard",
|
|
title=(
|
|
"Leaderboard mensual Elo/MMR global"
|
|
if server_id == ALL_SERVERS_SLUG
|
|
else "Leaderboard mensual Elo/MMR por servidor"
|
|
),
|
|
server_id=server_id,
|
|
limit=limit,
|
|
extra={"items": []},
|
|
operation="elo-mmr-leaderboard",
|
|
)
|
|
|
|
list_elo_mmr_leaderboard_payload = engine[1]
|
|
payload = list_elo_mmr_leaderboard_payload(server_id=server_id, limit=limit)
|
|
is_all_servers = server_id == ALL_SERVERS_SLUG
|
|
accuracy_contract = _build_elo_accuracy_contract(payload.get("capabilities_summary"))
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": (
|
|
"Leaderboard mensual Elo/MMR global"
|
|
if is_all_servers
|
|
else "Leaderboard mensual Elo/MMR por servidor"
|
|
),
|
|
"context": "historical-elo-mmr-leaderboard",
|
|
"source": "elo-mmr-persisted-read-model",
|
|
"server_slug": server_id,
|
|
"month_key": payload.get("month_key"),
|
|
"found": bool(payload.get("found")),
|
|
"generated_at": payload.get("generated_at"),
|
|
"limit": limit,
|
|
**(payload.get("source_policy") or _resolve_historical_fallback_policy(
|
|
operation="elo-mmr-leaderboard",
|
|
fallback_reason="elo-mmr-source-policy-missing",
|
|
)),
|
|
"capabilities_summary": payload.get("capabilities_summary"),
|
|
"accuracy_contract": accuracy_contract,
|
|
"model_contract": _build_elo_model_contract(accuracy_contract),
|
|
"items": [
|
|
_enrich_elo_leaderboard_item(item, accuracy_contract=accuracy_contract)
|
|
for item in (payload.get("items") or [])
|
|
if isinstance(item, dict)
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def build_elo_mmr_player_payload(
|
|
*,
|
|
player_id: str,
|
|
server_id: str | None = None,
|
|
) -> dict[str, object]:
|
|
"""Return one Elo/MMR player profile."""
|
|
engine = _load_elo_mmr_engine()
|
|
if engine is None:
|
|
return _build_elo_mmr_unavailable_payload(
|
|
context="historical-elo-mmr-player",
|
|
title="Perfil Elo/MMR de jugador",
|
|
server_id=server_id,
|
|
extra={
|
|
"player_id": player_id,
|
|
"found": False,
|
|
"profile": None,
|
|
},
|
|
operation="elo-mmr-player",
|
|
)
|
|
|
|
get_elo_mmr_player_payload, list_elo_mmr_leaderboard_payload = engine
|
|
profile = get_elo_mmr_player_payload(player_id=player_id, server_id=server_id)
|
|
source_policy = list_elo_mmr_leaderboard_payload(server_id=server_id, limit=1).get("source_policy")
|
|
accuracy_contract = _build_elo_player_accuracy_contract(profile)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Perfil Elo/MMR de jugador",
|
|
"context": "historical-elo-mmr-player",
|
|
"source": "elo-mmr-persisted-read-model",
|
|
"player_id": player_id,
|
|
"server_slug": server_id,
|
|
"found": profile is not None,
|
|
**(source_policy or _resolve_historical_fallback_policy(
|
|
operation="elo-mmr-player",
|
|
fallback_reason="elo-mmr-player-source-policy-missing",
|
|
)),
|
|
"accuracy_contract": accuracy_contract,
|
|
"model_contract": _build_elo_model_contract(accuracy_contract),
|
|
"profile": _enrich_elo_profile(profile, accuracy_contract=accuracy_contract),
|
|
},
|
|
}
|
|
|
|
|
|
def _load_elo_mmr_engine():
|
|
try:
|
|
from .elo_mmr_engine import ( # noqa: PLC0415 - lazy boundary for paused Elo/MMR
|
|
get_elo_mmr_player_payload,
|
|
list_elo_mmr_leaderboard_payload,
|
|
)
|
|
except ImportError:
|
|
return None
|
|
return get_elo_mmr_player_payload, list_elo_mmr_leaderboard_payload
|
|
|
|
|
|
def _build_elo_mmr_unavailable_payload(
|
|
*,
|
|
context: str,
|
|
title: str,
|
|
server_id: str | None,
|
|
operation: str,
|
|
limit: int | None = None,
|
|
extra: dict[str, object] | None = None,
|
|
) -> dict[str, object]:
|
|
accuracy_contract = _build_elo_accuracy_contract(None)
|
|
data = {
|
|
"title": title,
|
|
"context": context,
|
|
"source": "elo-mmr-paused",
|
|
"server_slug": server_id,
|
|
"available": False,
|
|
"unavailable_reason": "elo-mmr-engine-import-unavailable",
|
|
**_resolve_historical_fallback_policy(
|
|
operation=operation,
|
|
fallback_reason="elo-mmr-operationally-paused",
|
|
),
|
|
"capabilities_summary": None,
|
|
"accuracy_contract": accuracy_contract,
|
|
"model_contract": _build_elo_model_contract(accuracy_contract),
|
|
}
|
|
if limit is not None:
|
|
data["limit"] = limit
|
|
if extra:
|
|
data.update(extra)
|
|
return {
|
|
"status": "ok",
|
|
"data": data,
|
|
}
|
|
|
|
|
|
def _build_elo_player_accuracy_contract(profile: dict[str, object] | None) -> dict[str, object]:
|
|
if not isinstance(profile, dict):
|
|
return _build_elo_accuracy_contract(None)
|
|
monthly_ranking = profile.get("monthly_ranking")
|
|
if isinstance(monthly_ranking, dict) and isinstance(monthly_ranking.get("capabilities"), dict):
|
|
return _build_elo_accuracy_contract(monthly_ranking.get("capabilities"))
|
|
persistent_rating = profile.get("persistent_rating")
|
|
if isinstance(persistent_rating, dict) and isinstance(persistent_rating.get("capabilities"), dict):
|
|
return _build_elo_accuracy_contract(persistent_rating.get("capabilities"))
|
|
return _build_elo_accuracy_contract(None)
|
|
|
|
|
|
def _build_elo_accuracy_contract(summary: dict[str, object] | None) -> dict[str, object]:
|
|
capabilities = summary if isinstance(summary, dict) else {}
|
|
signals = capabilities.get("signals")
|
|
normalized_signals = [signal for signal in signals if isinstance(signal, dict)] if isinstance(signals, list) else []
|
|
component_status = {
|
|
str(signal.get("name") or "").strip(): signal.get("status")
|
|
for signal in normalized_signals
|
|
if str(signal.get("name") or "").strip()
|
|
}
|
|
return {
|
|
"accuracy_mode": capabilities.get("accuracy_mode") or "unknown",
|
|
"exact_ratio": capabilities.get("exact_ratio"),
|
|
"approximate_ratio": capabilities.get("approximate_ratio"),
|
|
"not_available_ratio": capabilities.get("unavailable_ratio"),
|
|
"component_status": component_status,
|
|
"blocked_components": [
|
|
name for name, status in component_status.items() if status == "not_available"
|
|
],
|
|
"explanation": {
|
|
"exact": "computed from persisted repository signals without proxy substitution",
|
|
"approximate": "computed with explicit proxies because the ideal telemetry is not stored yet",
|
|
"not_available": "not computable yet with the current repository telemetry",
|
|
},
|
|
}
|
|
|
|
|
|
def _build_elo_model_contract(accuracy_contract: dict[str, object]) -> dict[str, object]:
|
|
blocked_components = accuracy_contract.get("blocked_components")
|
|
return {
|
|
"persistent_rating": {
|
|
"meaning": "long-lived competitive rating rebuilt from persisted matches for the selected scope",
|
|
"primary_field": "persistent_rating.mmr",
|
|
},
|
|
"monthly_rank_score": {
|
|
"meaning": "monthly leaderboard ordering score that combines rating movement, match quality, activity and confidence",
|
|
"primary_field": "monthly_rank_score",
|
|
},
|
|
"elo_core": {
|
|
"meaning": "competitive rating movement driven by expected-vs-actual outcome against opponent rating pressure",
|
|
"fields": ["components.elo_core_gain"],
|
|
},
|
|
"performance_modifiers": {
|
|
"meaning": "bounded HLL-specific adjustments layered on top of the competitive Elo core",
|
|
"fields": [
|
|
"components.performance_modifier_gain",
|
|
"components.proxy_modifier_gain",
|
|
],
|
|
},
|
|
"proxy_boundary": {
|
|
"meaning": "subset of modifier logic that still depends on approximate signals such as role, objective, schedule or discipline proxies",
|
|
"blocked_by_telemetry": blocked_components if isinstance(blocked_components, list) else [],
|
|
},
|
|
}
|
|
|
|
|
|
def _enrich_elo_leaderboard_item(
|
|
item: dict[str, object],
|
|
*,
|
|
accuracy_contract: dict[str, object],
|
|
) -> dict[str, object]:
|
|
enriched = dict(item)
|
|
components = item.get("components") if isinstance(item.get("components"), dict) else {}
|
|
persistent_rating = item.get("persistent_rating") if isinstance(item.get("persistent_rating"), dict) else {}
|
|
delta_breakdown = _resolve_elo_delta_sources(
|
|
components,
|
|
persistent_rating=persistent_rating,
|
|
)
|
|
enriched["rating_breakdown"] = {
|
|
"persistent_rating": {
|
|
"mmr": persistent_rating.get("mmr"),
|
|
"baseline_mmr": persistent_rating.get("baseline_mmr"),
|
|
"net_mmr_gain": persistent_rating.get("mmr_gain"),
|
|
},
|
|
"monthly_ranking": {
|
|
"score": item.get("monthly_rank_score"),
|
|
"valid_matches": item.get("valid_matches"),
|
|
"confidence": components.get("confidence"),
|
|
},
|
|
"delta_sources": delta_breakdown["values"],
|
|
"materialization": delta_breakdown["materialization"],
|
|
"telemetry_boundary": {
|
|
"approximate_ratio": accuracy_contract.get("approximate_ratio"),
|
|
"blocked_components": accuracy_contract.get("blocked_components") or [],
|
|
},
|
|
}
|
|
return enriched
|
|
|
|
|
|
def _enrich_elo_profile(
|
|
profile: dict[str, object] | None,
|
|
*,
|
|
accuracy_contract: dict[str, object],
|
|
) -> dict[str, object] | None:
|
|
if not isinstance(profile, dict):
|
|
return profile
|
|
enriched = dict(profile)
|
|
monthly_ranking = dict(profile.get("monthly_ranking")) if isinstance(profile.get("monthly_ranking"), dict) else None
|
|
if monthly_ranking is not None:
|
|
components = monthly_ranking.get("components") if isinstance(monthly_ranking.get("components"), dict) else {}
|
|
delta_breakdown = _resolve_elo_delta_sources(
|
|
components,
|
|
persistent_rating={
|
|
"mmr_gain": monthly_ranking.get("mmr_gain"),
|
|
"baseline_mmr": monthly_ranking.get("baseline_mmr"),
|
|
"mmr": monthly_ranking.get("current_mmr"),
|
|
},
|
|
)
|
|
monthly_ranking["rating_breakdown"] = {
|
|
"monthly_rank_score": monthly_ranking.get("monthly_rank_score"),
|
|
"current_mmr": monthly_ranking.get("current_mmr"),
|
|
"baseline_mmr": monthly_ranking.get("baseline_mmr"),
|
|
"net_mmr_gain": monthly_ranking.get("mmr_gain"),
|
|
"elo_core_gain": delta_breakdown["values"]["elo_core_gain"],
|
|
"performance_modifier_gain": delta_breakdown["values"]["performance_modifier_gain"],
|
|
"proxy_modifier_gain": delta_breakdown["values"]["proxy_modifier_gain"],
|
|
"confidence": components.get("confidence"),
|
|
"avg_participation_ratio": components.get("avg_participation_ratio"),
|
|
"materialization": delta_breakdown["materialization"],
|
|
}
|
|
enriched["monthly_ranking"] = monthly_ranking
|
|
persistent_rating = dict(profile.get("persistent_rating")) if isinstance(profile.get("persistent_rating"), dict) else None
|
|
if persistent_rating is not None:
|
|
persistent_rating["meaning"] = "persistent competitive rating for the selected scope"
|
|
enriched["persistent_rating"] = persistent_rating
|
|
enriched["telemetry_boundary"] = {
|
|
"accuracy_mode": accuracy_contract.get("accuracy_mode"),
|
|
"blocked_components": accuracy_contract.get("blocked_components") or [],
|
|
}
|
|
return enriched
|
|
|
|
|
|
def _resolve_elo_delta_sources(
|
|
components: dict[str, object],
|
|
*,
|
|
persistent_rating: dict[str, object] | None,
|
|
) -> dict[str, object]:
|
|
elo_core_gain = _coerce_optional_float(components.get("elo_core_gain"))
|
|
performance_modifier_gain = _coerce_optional_float(components.get("performance_modifier_gain"))
|
|
proxy_modifier_gain = _coerce_optional_float(components.get("proxy_modifier_gain"))
|
|
if (
|
|
elo_core_gain is not None
|
|
or performance_modifier_gain is not None
|
|
or proxy_modifier_gain is not None
|
|
):
|
|
return {
|
|
"values": {
|
|
"elo_core_gain": elo_core_gain,
|
|
"performance_modifier_gain": performance_modifier_gain,
|
|
"proxy_modifier_gain": proxy_modifier_gain,
|
|
},
|
|
"materialization": {
|
|
"status": "v3-materialized",
|
|
"reason": "persisted-monthly-ranking-includes-v3-delta-sources",
|
|
"delta_sources_accuracy": "exact-or-proxy-as-persisted",
|
|
},
|
|
}
|
|
|
|
legacy_net_gain = _coerce_optional_float(components.get("mmr_gain_raw"))
|
|
if legacy_net_gain is None and isinstance(persistent_rating, dict):
|
|
legacy_net_gain = _coerce_optional_float(persistent_rating.get("mmr_gain"))
|
|
if legacy_net_gain is None:
|
|
return {
|
|
"values": {
|
|
"elo_core_gain": None,
|
|
"performance_modifier_gain": None,
|
|
"proxy_modifier_gain": None,
|
|
},
|
|
"materialization": {
|
|
"status": "v3-delta-sources-unavailable",
|
|
"reason": (
|
|
"persisted-monthly-ranking-predates-v3-delta-split-and-has-no-compatible-net-gain"
|
|
),
|
|
"delta_sources_accuracy": "not_available",
|
|
},
|
|
}
|
|
|
|
return {
|
|
"values": {
|
|
"elo_core_gain": legacy_net_gain,
|
|
"performance_modifier_gain": 0.0,
|
|
"proxy_modifier_gain": 0.0,
|
|
},
|
|
"materialization": {
|
|
"status": "legacy-compatibility-approximation",
|
|
"reason": (
|
|
"persisted-monthly-ranking-predates-v3-delta-split-api-approximates-delta-sources-"
|
|
"from-legacy-net-mmr-gain"
|
|
),
|
|
"delta_sources_accuracy": "approximate",
|
|
},
|
|
}
|
|
|
|
|
|
def _coerce_optional_float(value: object) -> float | None:
|
|
if value is None:
|
|
return None
|
|
try:
|
|
return round(float(value), 3)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _leaderboard_snapshot_items_need_playtime_enrichment(items: list[object]) -> bool:
|
|
normalized_items = [item for item in items if isinstance(item, dict)]
|
|
if not normalized_items:
|
|
return False
|
|
return any("total_time_seconds" not in item for item in normalized_items)
|
|
|
|
|
|
def _load_runtime_leaderboard_items(
|
|
*,
|
|
limit: int,
|
|
server_id: str | None,
|
|
metric: str,
|
|
timeframe: str,
|
|
) -> list[dict[str, object]]:
|
|
if timeframe == "monthly":
|
|
result = list_monthly_leaderboard(limit=limit, server_id=server_id, metric=metric)
|
|
else:
|
|
result = list_weekly_leaderboard(limit=limit, server_id=server_id, metric=metric)
|
|
items = result.get("items") if isinstance(result, dict) else None
|
|
return [item for item in items if isinstance(item, dict)] if isinstance(items, list) else []
|
|
|
|
|
|
def _get_historical_snapshot_record(
|
|
*,
|
|
server_key: str | None,
|
|
snapshot_type: str,
|
|
metric: str | None = None,
|
|
window: str | None = None,
|
|
) -> dict[str, object] | None:
|
|
if not server_key:
|
|
return None
|
|
return get_historical_snapshot(
|
|
server_key=server_key,
|
|
snapshot_type=snapshot_type,
|
|
metric=metric,
|
|
window=window,
|
|
)
|
|
|
|
|
|
def _build_historical_snapshot_metadata(snapshot: dict[str, object] | None) -> dict[str, object]:
|
|
if snapshot is None:
|
|
return {
|
|
"snapshot_status": "missing",
|
|
"missing_reason": "snapshot-not-generated",
|
|
"request_path_policy": "read-only-fast-path",
|
|
"generation_policy": "out-of-band-refresh-only",
|
|
"generated_at": None,
|
|
"source_range_start": None,
|
|
"source_range_end": None,
|
|
"is_stale": True,
|
|
"freshness": "stale",
|
|
}
|
|
is_stale = bool(snapshot.get("is_stale", False))
|
|
return {
|
|
"snapshot_status": "ready",
|
|
"missing_reason": None,
|
|
"request_path_policy": "read-only-fast-path",
|
|
"generation_policy": "out-of-band-refresh-only",
|
|
"generated_at": snapshot.get("generated_at"),
|
|
"source_range_start": snapshot.get("source_range_start"),
|
|
"source_range_end": snapshot.get("source_range_end"),
|
|
"is_stale": is_stale,
|
|
"freshness": "stale" if is_stale else "fresh",
|
|
}
|
|
|
|
|
|
def _build_leaderboard_title(
|
|
*,
|
|
metric: str,
|
|
timeframe: str,
|
|
is_all_servers: bool,
|
|
snapshot: bool = False,
|
|
) -> str:
|
|
timeframe_label = "mensual" if timeframe == "monthly" else "semanal"
|
|
scope_label = "totales" if is_all_servers else "por servidor"
|
|
prefix = "Snapshot " if snapshot else ""
|
|
title_by_metric = {
|
|
"kills": f"{prefix}Top kills {timeframe_label} {scope_label}",
|
|
"deaths": f"{prefix}Top muertes {timeframe_label} {scope_label}",
|
|
"support": f"{prefix}Top puntos de soporte {timeframe_label} {scope_label}",
|
|
"matches_over_100_kills": f"{prefix}Top partidas de 100+ kills {timeframe_label} {scope_label}",
|
|
}
|
|
fallback_label = f"{prefix}Ranking {timeframe_label} por servidor".strip()
|
|
return title_by_metric.get(metric, fallback_label)
|
|
|
|
|
|
def _build_monthly_mvp_title(*, is_all_servers: bool, snapshot: bool = False) -> str:
|
|
prefix = "Snapshot " if snapshot else ""
|
|
scope_label = "global" if is_all_servers else "por servidor"
|
|
return f"{prefix}Top MVP mensual {scope_label}"
|
|
|
|
|
|
def _build_monthly_mvp_v2_title(*, is_all_servers: bool, snapshot: bool = False) -> str:
|
|
prefix = "Snapshot " if snapshot else ""
|
|
scope_label = "global" if is_all_servers else "por servidor"
|
|
return f"{prefix}Top MVP mensual V2 {scope_label}"
|
|
|
|
|
|
def _build_player_event_title(
|
|
*,
|
|
view: str,
|
|
is_all_servers: bool,
|
|
snapshot: bool = False,
|
|
) -> str:
|
|
prefix = "Snapshot " if snapshot else ""
|
|
scope_label = "global" if is_all_servers else "por servidor"
|
|
title_by_view = {
|
|
"most-killed": f"{prefix}Most killed mensual {scope_label}",
|
|
"death-by": f"{prefix}Death by mensual {scope_label}",
|
|
"duels": f"{prefix}Duelos netos mensuales {scope_label}",
|
|
"weapon-kills": f"{prefix}Kills por arma mensuales {scope_label}",
|
|
"teamkills": f"{prefix}Teamkills mensuales {scope_label}",
|
|
}
|
|
return title_by_view.get(view, f"{prefix}Metricas V2 mensuales {scope_label}")
|
|
|
|
|
|
def _resolve_player_event_snapshot_type(view: str) -> str:
|
|
normalized_view = view.strip().lower() if isinstance(view, str) else "most-killed"
|
|
snapshot_type_by_view = {
|
|
"most-killed": SNAPSHOT_TYPE_PLAYER_EVENT_MOST_KILLED,
|
|
"death-by": SNAPSHOT_TYPE_PLAYER_EVENT_DEATH_BY,
|
|
"duels": SNAPSHOT_TYPE_PLAYER_EVENT_DUELS,
|
|
"weapon-kills": SNAPSHOT_TYPE_PLAYER_EVENT_WEAPON_KILLS,
|
|
"teamkills": SNAPSHOT_TYPE_PLAYER_EVENT_TEAMKILLS,
|
|
}
|
|
return snapshot_type_by_view.get(normalized_view, SNAPSHOT_TYPE_PLAYER_EVENT_MOST_KILLED)
|
|
|
|
|
|
def _enrich_server_items(items: list[dict[str, object]]) -> list[dict[str, object]]:
|
|
target_index = get_live_data_source().build_target_index()
|
|
enriched_items: list[dict[str, object]] = []
|
|
for item in items:
|
|
enriched_items.append(_enrich_server_item(item, target_index))
|
|
return enriched_items
|
|
|
|
|
|
def _select_primary_snapshot_items(items: list[dict[str, object]]) -> list[dict[str, object]]:
|
|
preferred_origin = (
|
|
"real-rcon"
|
|
if get_live_data_source_kind() == "rcon"
|
|
else "real-a2s"
|
|
)
|
|
preferred_items = [
|
|
item
|
|
for item in items
|
|
if item.get("snapshot_origin") == preferred_origin
|
|
]
|
|
return preferred_items or items
|
|
|
|
|
|
def _enrich_server_item(
|
|
item: dict[str, object],
|
|
target_index: dict[str, object],
|
|
) -> dict[str, object]:
|
|
enriched = dict(item)
|
|
enriched["current_map"] = normalize_map_name(enriched.get("current_map"))
|
|
history_url = _resolve_community_history_url(enriched.get("external_server_id"))
|
|
enriched["community_history_url"] = history_url
|
|
enriched["community_history_available"] = bool(history_url)
|
|
external_server_id = enriched.get("external_server_id")
|
|
snapshot_origin = enriched.get("snapshot_origin")
|
|
target = target_index.get(external_server_id)
|
|
|
|
if not target or snapshot_origin not in {"real-a2s", "real-rcon"}:
|
|
enriched["host"] = None
|
|
enriched["query_port"] = None
|
|
enriched["game_port"] = None
|
|
return enriched
|
|
|
|
enriched["host"] = target.host
|
|
enriched["query_port"] = target.query_port
|
|
enriched["game_port"] = target.game_port
|
|
return enriched
|
|
|
|
|
|
def _resolve_last_snapshot_at(items: list[dict[str, object]]) -> str | None:
|
|
timestamps = [
|
|
str(item["captured_at"])
|
|
for item in items
|
|
if item.get("captured_at")
|
|
]
|
|
if not timestamps:
|
|
return None
|
|
|
|
return max(timestamps)
|
|
|
|
|
|
def _should_refresh_snapshot(
|
|
items: list[dict[str, object]],
|
|
snapshot_age_seconds: int | None,
|
|
max_snapshot_age_seconds: int,
|
|
) -> bool:
|
|
if not items:
|
|
return True
|
|
|
|
if snapshot_age_seconds is None:
|
|
return True
|
|
|
|
return snapshot_age_seconds > max_snapshot_age_seconds
|
|
|
|
|
|
def _try_collect_real_time_snapshot() -> tuple[
|
|
list[dict[str, object]],
|
|
list[dict[str, object]],
|
|
dict[str, object],
|
|
]:
|
|
payload = get_live_data_source().collect_snapshots(persist=False)
|
|
snapshots = payload.get("snapshots")
|
|
items = _select_primary_snapshot_items(_enrich_server_items(list(snapshots or [])))
|
|
errors = payload.get("errors")
|
|
return (
|
|
items,
|
|
list(errors or []),
|
|
{
|
|
"primary_source": payload.get("primary_source"),
|
|
"selected_source": payload.get("selected_source"),
|
|
"fallback_used": bool(payload.get("fallback_used")),
|
|
"fallback_reason": payload.get("fallback_reason"),
|
|
"source_attempts": list(payload.get("source_attempts") or []),
|
|
},
|
|
)
|
|
|
|
|
|
def _build_servers_response(
|
|
*,
|
|
items: list[dict[str, object]],
|
|
response_source: str,
|
|
last_snapshot_at: str | None,
|
|
snapshot_age_seconds: int | None,
|
|
max_snapshot_age_seconds: int,
|
|
refresh_attempted: bool,
|
|
refresh_status: str,
|
|
refresh_errors: list[dict[str, object]],
|
|
source_policy: dict[str, object],
|
|
) -> dict[str, object]:
|
|
freshness = (
|
|
"fresh"
|
|
if snapshot_age_seconds is not None and snapshot_age_seconds <= max_snapshot_age_seconds
|
|
else "stale"
|
|
)
|
|
return {
|
|
"status": "ok",
|
|
"data": {
|
|
"title": "Estado actual de servidores",
|
|
"context": "current-hll-status",
|
|
"source": response_source,
|
|
"last_snapshot_at": last_snapshot_at,
|
|
"snapshot_age_seconds": snapshot_age_seconds,
|
|
"snapshot_age_minutes": _to_snapshot_age_minutes(snapshot_age_seconds),
|
|
"max_snapshot_age_seconds": max_snapshot_age_seconds,
|
|
"is_stale": freshness == "stale",
|
|
"freshness": freshness,
|
|
"refresh_attempted": refresh_attempted,
|
|
"refresh_status": refresh_status,
|
|
"refresh_errors": refresh_errors,
|
|
**source_policy,
|
|
"items": items,
|
|
},
|
|
}
|
|
|
|
|
|
def _calculate_snapshot_age_seconds(timestamp: str | None) -> int | None:
|
|
if not timestamp:
|
|
return None
|
|
|
|
normalized = timestamp.replace("Z", "+00:00")
|
|
captured_at = datetime.fromisoformat(normalized)
|
|
if captured_at.tzinfo is None:
|
|
captured_at = captured_at.replace(tzinfo=timezone.utc)
|
|
|
|
age = datetime.now(timezone.utc) - captured_at.astimezone(timezone.utc)
|
|
return max(0, int(age.total_seconds()))
|
|
|
|
|
|
def _to_snapshot_age_minutes(snapshot_age_seconds: int | None) -> int | None:
|
|
if snapshot_age_seconds is None:
|
|
return None
|
|
|
|
return snapshot_age_seconds // 60
|
|
|
|
|
|
def _resolve_historical_fallback_policy(
|
|
*,
|
|
fallback_reason: str,
|
|
operation: str = "historical-read",
|
|
) -> dict[str, object]:
|
|
return build_historical_runtime_source_policy(
|
|
operation=operation,
|
|
rcon_status="unsupported",
|
|
fallback_reason=fallback_reason,
|
|
)
|
|
|
|
|
|
def _merge_recent_match_items(
|
|
*,
|
|
primary_items: list[dict[str, object]],
|
|
fallback_items: list[dict[str, object]],
|
|
limit: int,
|
|
) -> list[dict[str, object]]:
|
|
merged: list[dict[str, object]] = []
|
|
seen_keys: set[str] = set()
|
|
for item in list(primary_items) + list(fallback_items):
|
|
if not isinstance(item, dict):
|
|
continue
|
|
dedupe_key = _build_recent_match_dedupe_key(item)
|
|
if dedupe_key in seen_keys:
|
|
continue
|
|
seen_keys.add(dedupe_key)
|
|
merged.append(item)
|
|
merged.sort(key=_recent_match_sort_key, reverse=True)
|
|
return merged[:limit]
|
|
|
|
|
|
def _with_recent_result_source(
|
|
item: dict[str, object],
|
|
result_source: str,
|
|
) -> dict[str, object]:
|
|
enriched = dict(item)
|
|
enriched.setdefault("result_source", result_source)
|
|
return enriched
|
|
|
|
|
|
def _recent_items_include_rcon_results(items: list[dict[str, object]]) -> bool:
|
|
return any(
|
|
item.get("result_source") in {"admin-log-match-ended", "rcon-session"}
|
|
for item in items
|
|
if isinstance(item, dict)
|
|
)
|
|
|
|
|
|
def _build_recent_match_dedupe_key(item: dict[str, object]) -> str:
|
|
server = item.get("server") if isinstance(item.get("server"), dict) else {}
|
|
map_payload = item.get("map") if isinstance(item.get("map"), dict) else {}
|
|
match_id = str(item.get("match_id") or "").strip()
|
|
server_slug = str(server.get("slug") or server.get("external_server_id") or "").strip()
|
|
map_name = str(map_payload.get("name") or map_payload.get("pretty_name") or "").strip().lower()
|
|
closed_at = _truncate_recent_match_timestamp(
|
|
item.get("closed_at") or item.get("ended_at")
|
|
)
|
|
started_at = _truncate_recent_match_timestamp(item.get("started_at"))
|
|
if match_id and match_id.isdigit():
|
|
return f"scoreboard:{server_slug}:{match_id}"
|
|
return f"recent:{server_slug}:{map_name}:{started_at}:{closed_at}"
|
|
|
|
|
|
def _truncate_recent_match_timestamp(value: object) -> str:
|
|
normalized = str(value or "").strip()
|
|
return normalized[:16] if normalized else ""
|
|
|
|
|
|
def _recent_match_sort_key(item: dict[str, object]) -> tuple[str, str]:
|
|
closed_at = str(item.get("closed_at") or item.get("ended_at") or "").strip()
|
|
started_at = str(item.get("started_at") or "").strip()
|
|
return (closed_at, started_at)
|
|
|
|
|
|
def _infer_live_source_policy_from_items(
|
|
items: list[dict[str, object]],
|
|
*,
|
|
refresh_attempted: bool,
|
|
refresh_errors: list[dict[str, object]],
|
|
) -> dict[str, object]:
|
|
selected_source = "persisted-snapshot"
|
|
fallback_used = False
|
|
fallback_reason = None
|
|
snapshot_origins = {
|
|
str(item.get("snapshot_origin") or "").strip()
|
|
for item in items
|
|
if item.get("snapshot_origin")
|
|
}
|
|
if "real-rcon" in snapshot_origins:
|
|
selected_source = SOURCE_KIND_RCON
|
|
elif "real-a2s" in snapshot_origins:
|
|
selected_source = LIVE_SOURCE_A2S
|
|
if get_live_data_source_kind() == SOURCE_KIND_RCON:
|
|
fallback_used = True
|
|
fallback_reason = "persisted-live-snapshot-came-from-a2s"
|
|
|
|
attempt_status = "success" if items else ("error" if refresh_attempted else "cached")
|
|
attempt_reason = None if items else "no-live-snapshot-items"
|
|
if refresh_errors and attempt_reason is None:
|
|
attempt_reason = "live-refresh-errors-present"
|
|
|
|
return build_source_policy(
|
|
primary_source=get_live_data_source_kind(),
|
|
selected_source=selected_source,
|
|
fallback_used=fallback_used,
|
|
fallback_reason=fallback_reason,
|
|
source_attempts=[
|
|
build_source_attempt(
|
|
source=selected_source,
|
|
role="served-response",
|
|
status=attempt_status,
|
|
reason=attempt_reason,
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def _build_live_response_source(source_policy: dict[str, object]) -> str:
|
|
selected_source = str(source_policy.get("selected_source") or "")
|
|
if selected_source == SOURCE_KIND_RCON:
|
|
return "real-time-rcon-refresh"
|
|
if selected_source == LIVE_SOURCE_A2S:
|
|
return "real-time-a2s-fallback"
|
|
return "real-time-refresh"
|
|
|
|
|
|
def _resolve_community_history_url(external_server_id: object) -> str | None:
|
|
normalized_server_id = str(external_server_id or "").strip()
|
|
if not normalized_server_id:
|
|
return None
|
|
origin = get_trusted_public_scoreboard_origin(normalized_server_id)
|
|
return f"{origin.base_url}/games" if origin else None
|