Add personal stats player endpoint

This commit is contained in:
devRaGonSa
2026-06-08 12:11:54 +02:00
parent 1b3cbbfb1b
commit 6292c8cf34
4 changed files with 713 additions and 0 deletions

View File

@@ -49,6 +49,8 @@ from .historical_storage import (
list_weekly_top_kills,
)
from .rcon_historical_read_model import get_rcon_historical_match_detail
from .rcon_historical_player_stats import search_rcon_materialized_players
from .rcon_historical_player_stats import get_rcon_materialized_player_stats
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
@@ -460,6 +462,23 @@ def _utc_timestamp_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _to_iso_or_none(value: object) -> str | None:
if value is None:
return None
if isinstance(value, datetime):
parsed = value
elif isinstance(value, str) and value.strip():
try:
parsed = datetime.fromisoformat(value.strip().replace("Z", "+00:00"))
except ValueError:
return None
else:
return None
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed.astimezone(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
@@ -573,6 +592,75 @@ def build_historical_leaderboard_payload(
}
def build_stats_player_search_payload(
*,
query: str,
server_id: str | None = None,
limit: int = 10,
) -> dict[str, object]:
"""Return lightweight player search results for future stats UX flows."""
normalized_query = query.strip()
if not normalized_query:
raise ValueError("Query cannot be empty.")
result = search_rcon_materialized_players(
query=normalized_query,
server_id=server_id,
limit=limit,
)
return {
"status": "ok",
"data": {
"query": result["query"],
"server_id": result["server_id"],
"items": result["items"],
},
}
def build_stats_player_profile_payload(
*,
player_id: str,
server_id: str | None = None,
timeframe: str = "weekly",
) -> dict[str, object]:
"""Return personal RCON materialized stats and weekly/monthly ranking context."""
result = get_rcon_materialized_player_stats(
player_id=player_id,
server_id=server_id,
timeframe=timeframe,
)
kills = int(result.get("kills", 0) or 0)
deaths = int(result.get("deaths", 0) or 0)
matches_considered = int(result.get("matches_considered", 0) or 0)
teamkills = int(result.get("teamkills", 0) or 0)
kills_per_match = round(kills / matches_considered, 2) if matches_considered else 0.0
deaths_per_match = round(deaths / matches_considered, 2) if matches_considered else 0.0
kd_ratio = round(kills / deaths, 2) if deaths else float(kills)
return {
"status": "ok",
"data": {
"player_id": result.get("player_id"),
"player_name": result.get("player_name"),
"server_id": result.get("server_id"),
"timeframe": result.get("timeframe"),
"window_start": _to_iso_or_none(result.get("window_start")),
"window_end": _to_iso_or_none(result.get("window_end")),
"window_kind": result.get("window_kind"),
"matches_considered": matches_considered,
"kills": kills,
"deaths": deaths,
"teamkills": teamkills,
"kd_ratio": kd_ratio,
"kills_per_match": kills_per_match,
"deaths_per_match": deaths_per_match,
"weekly_ranking": result.get("weekly_ranking"),
"monthly_ranking": result.get("monthly_ranking"),
"source": result.get("source"),
},
}
def build_weekly_leaderboard_payload(
*,
limit: int = 10,