Fix
This commit is contained in:
261
backend/app/player_event_aggregates.py
Normal file
261
backend/app/player_event_aggregates.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""Derived duel and weapon aggregates computed from the raw player event ledger."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
from .config import get_database_url, get_storage_path
|
||||
from .player_event_storage import initialize_player_event_storage
|
||||
|
||||
|
||||
def list_most_killed(
|
||||
*,
|
||||
server_slug: str | None = None,
|
||||
month: str | None = None,
|
||||
external_match_id: str | None = None,
|
||||
limit: int = 10,
|
||||
db_path: Path | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
"""Return strongest killer -> victim summaries from the raw ledger."""
|
||||
return _query_pair_summary(
|
||||
event_type="player_kill_summary",
|
||||
server_slug=server_slug,
|
||||
month=month,
|
||||
external_match_id=external_match_id,
|
||||
limit=limit,
|
||||
db_path=db_path,
|
||||
)
|
||||
|
||||
|
||||
def list_death_by(
|
||||
*,
|
||||
server_slug: str | None = None,
|
||||
month: str | None = None,
|
||||
external_match_id: str | None = None,
|
||||
limit: int = 10,
|
||||
db_path: Path | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
"""Return strongest killer -> victim summaries from the victim perspective."""
|
||||
return _query_pair_summary(
|
||||
event_type="player_death_summary",
|
||||
server_slug=server_slug,
|
||||
month=month,
|
||||
external_match_id=external_match_id,
|
||||
limit=limit,
|
||||
db_path=db_path,
|
||||
)
|
||||
|
||||
|
||||
def list_net_duel_summaries(
|
||||
*,
|
||||
server_slug: str | None = None,
|
||||
month: str | None = None,
|
||||
external_match_id: str | None = None,
|
||||
limit: int = 10,
|
||||
db_path: Path | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
"""Return partial net duel summaries using the strongest encounter signals available."""
|
||||
resolved_path = initialize_player_event_storage(db_path=db_path)
|
||||
where_sql, params = _build_common_where(
|
||||
event_type="player_kill_summary",
|
||||
server_slug=server_slug,
|
||||
month=month,
|
||||
external_match_id=external_match_id,
|
||||
)
|
||||
with _connect(resolved_path) as connection:
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
WITH duel_pairs AS (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN COALESCE(killer_player_key, '') <= COALESCE(victim_player_key, '')
|
||||
THEN killer_player_key
|
||||
ELSE victim_player_key
|
||||
END AS player_a_key,
|
||||
CASE
|
||||
WHEN COALESCE(killer_player_key, '') <= COALESCE(victim_player_key, '')
|
||||
THEN killer_display_name
|
||||
ELSE victim_display_name
|
||||
END AS player_a_name,
|
||||
CASE
|
||||
WHEN COALESCE(killer_player_key, '') <= COALESCE(victim_player_key, '')
|
||||
THEN victim_player_key
|
||||
ELSE killer_player_key
|
||||
END AS player_b_key,
|
||||
CASE
|
||||
WHEN COALESCE(killer_player_key, '') <= COALESCE(victim_player_key, '')
|
||||
THEN victim_display_name
|
||||
ELSE killer_display_name
|
||||
END AS player_b_name,
|
||||
CASE
|
||||
WHEN COALESCE(killer_player_key, '') <= COALESCE(victim_player_key, '')
|
||||
THEN event_value
|
||||
ELSE -event_value
|
||||
END AS net_value,
|
||||
event_value
|
||||
FROM player_event_raw_ledger
|
||||
WHERE {where_sql}
|
||||
AND killer_player_key IS NOT NULL
|
||||
AND victim_player_key IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
player_a_key,
|
||||
player_a_name,
|
||||
player_b_key,
|
||||
player_b_name,
|
||||
COALESCE(SUM(event_value), 0) AS total_encounters,
|
||||
COALESCE(SUM(net_value), 0) AS net_duel_value
|
||||
FROM duel_pairs
|
||||
GROUP BY player_a_key, player_a_name, player_b_key, player_b_name
|
||||
ORDER BY ABS(COALESCE(SUM(net_value), 0)) DESC,
|
||||
COALESCE(SUM(event_value), 0) DESC,
|
||||
player_a_name ASC,
|
||||
player_b_name ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
[*params, limit],
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def list_weapon_kills(
|
||||
*,
|
||||
server_slug: str | None = None,
|
||||
month: str | None = None,
|
||||
external_match_id: str | None = None,
|
||||
limit: int = 10,
|
||||
db_path: Path | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
"""Return partial weapon summaries derived from top kill events."""
|
||||
resolved_path = initialize_player_event_storage(db_path=db_path)
|
||||
where_sql, params = _build_common_where(
|
||||
event_type="player_weapon_kill_summary",
|
||||
server_slug=server_slug,
|
||||
month=month,
|
||||
external_match_id=external_match_id,
|
||||
)
|
||||
with _connect(resolved_path) as connection:
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
SELECT
|
||||
killer_player_key,
|
||||
killer_display_name,
|
||||
COALESCE(weapon_name, 'unknown') AS weapon_name,
|
||||
COALESCE(SUM(event_value), 0) AS total_kills
|
||||
FROM player_event_raw_ledger
|
||||
WHERE {where_sql}
|
||||
AND killer_player_key IS NOT NULL
|
||||
GROUP BY killer_player_key, killer_display_name, COALESCE(weapon_name, 'unknown')
|
||||
ORDER BY total_kills DESC, killer_display_name ASC, weapon_name ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
[*params, limit],
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def list_teamkill_summaries(
|
||||
*,
|
||||
server_slug: str | None = None,
|
||||
month: str | None = None,
|
||||
external_match_id: str | None = None,
|
||||
limit: int = 10,
|
||||
db_path: Path | None = None,
|
||||
) -> list[dict[str, object]]:
|
||||
"""Return derived teamkill totals per player from the raw ledger."""
|
||||
resolved_path = initialize_player_event_storage(db_path=db_path)
|
||||
where_sql, params = _build_common_where(
|
||||
event_type="player_teamkill_summary",
|
||||
server_slug=server_slug,
|
||||
month=month,
|
||||
external_match_id=external_match_id,
|
||||
)
|
||||
with _connect(resolved_path) as connection:
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
SELECT
|
||||
killer_player_key,
|
||||
killer_display_name,
|
||||
COALESCE(SUM(event_value), 0) AS total_teamkills
|
||||
FROM player_event_raw_ledger
|
||||
WHERE {where_sql}
|
||||
AND killer_player_key IS NOT NULL
|
||||
GROUP BY killer_player_key, killer_display_name
|
||||
ORDER BY total_teamkills DESC, killer_display_name ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
[*params, limit],
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def _query_pair_summary(
|
||||
*,
|
||||
event_type: str,
|
||||
server_slug: str | None,
|
||||
month: str | None,
|
||||
external_match_id: str | None,
|
||||
limit: int,
|
||||
db_path: Path | None,
|
||||
) -> list[dict[str, object]]:
|
||||
resolved_path = initialize_player_event_storage(db_path=db_path)
|
||||
where_sql, params = _build_common_where(
|
||||
event_type=event_type,
|
||||
server_slug=server_slug,
|
||||
month=month,
|
||||
external_match_id=external_match_id,
|
||||
)
|
||||
with _connect(resolved_path) as connection:
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
SELECT
|
||||
killer_player_key,
|
||||
killer_display_name,
|
||||
victim_player_key,
|
||||
victim_display_name,
|
||||
COALESCE(SUM(event_value), 0) AS total_kills
|
||||
FROM player_event_raw_ledger
|
||||
WHERE {where_sql}
|
||||
AND killer_player_key IS NOT NULL
|
||||
AND victim_player_key IS NOT NULL
|
||||
GROUP BY killer_player_key, killer_display_name, victim_player_key, victim_display_name
|
||||
ORDER BY total_kills DESC, killer_display_name ASC, victim_display_name ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
[*params, limit],
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
def _build_common_where(
|
||||
*,
|
||||
event_type: str,
|
||||
server_slug: str | None,
|
||||
month: str | None,
|
||||
external_match_id: str | None,
|
||||
) -> tuple[str, list[object]]:
|
||||
clauses = ["event_type = ?"]
|
||||
params: list[object] = [event_type]
|
||||
|
||||
if server_slug and server_slug != "all-servers":
|
||||
clauses.append("server_slug = ?")
|
||||
params.append(server_slug.strip())
|
||||
if month:
|
||||
clauses.append("substr(COALESCE(CAST(occurred_at AS TEXT), ''), 1, 7) = ?")
|
||||
params.append(month.strip())
|
||||
if external_match_id:
|
||||
clauses.append("external_match_id = ?")
|
||||
params.append(external_match_id.strip())
|
||||
|
||||
return " AND ".join(clauses), params
|
||||
|
||||
|
||||
def _connect(db_path: Path) -> sqlite3.Connection:
|
||||
if get_database_url():
|
||||
from .postgres_display_storage import connect_postgres_compat
|
||||
|
||||
return connect_postgres_compat()
|
||||
connection = sqlite3.connect(db_path or get_storage_path())
|
||||
connection.row_factory = sqlite3.Row
|
||||
return connection
|
||||
Reference in New Issue
Block a user