Files
comunidadhll/ai/tasks/done/TASK-262-dedupe-current-match-live-player-stats.md
2026-06-17 15:27:15 +02:00

8.5 KiB

id, title, status, type, team, supporting_teams, roadmap_item, priority
id title status type team supporting_teams roadmap_item priority
TASK-262 Dedupe current match live player stats done backend Backend Senior
Frontend Senior
foundation high

TASK-262 - Dedupe current match live player stats

Goal

Ensure /partida-actual.html, section "Estadisticas en vivo", displays at most one row per player while preserving AdminLog-derived live stats.

Context

Real observations showed duplicated player rows in the live stats table, for example:

  • chino kudeiro | ALIADOS
  • chino kudeiro | ALIADOS
  • [LCM] acuario7 | NO DISPONIBLE
  • [LCM] acuario7 | ALIADOS
  • D-ibiz | NO DISPONIBLE
  • D-ibiz | EJE

This section must not be confused with the combat feed. The combat feed uses /api/current-match/kills; live player stats use /api/current-match/players.

The server population, for example 17/100, is not the same as this table. The table is derived from recent AdminLog events and may contain fewer rows than the live online population. It still must not contain duplicates.

Mandatory Analysis

Frontend

Files reviewed:

  • frontend/assets/js/partida-actual.js
  • frontend/partida-actual.html

Confirmed behavior:

  • Current match scoreboard refreshes every 30000 ms.
  • Combat feed refreshes every 1500 ms.
  • Live player stats refresh every 3000 ms.
  • Current match scoreboard uses /api/current-match?server={server}.
  • Combat feed uses /api/current-match/kills?server={server}&limit=18.
  • Live player stats uses /api/current-match/players?server={server}.
  • renderPlayerStats(...) currently sorts and renders data.items without a player-level dedupe guard.
  • The visible counter currently says Jugadores detectados: N, which can be confused with online server population.

Backend

Files reviewed:

  • backend/app/payloads.py
  • backend/app/rcon_admin_log_storage.py
  • backend/app/rcon_admin_log_parser.py
  • backend/app/routes.py
  • backend/tests/test_current_match_payload.py

Confirmed behavior:

  • /api/current-match/players is routed to build_current_match_player_stats_payload(server_slug=...).
  • The payload delegates to list_current_match_player_stats(server_key=..., ensure_storage=False).
  • The endpoint represents players detected from safe AdminLog evidence in the current or recent match window, not a complete online roster.
  • AdminLog event types used for player stats are kill, team_switch, connected, disconnected, chat and message.
  • connected and disconnected events can create a player row with unknown team.
  • kill, team_switch and chat can later provide Allies or Axis.
  • Current aggregation keys by player_id when present, otherwise by player_name.casefold().
  • The endpoint currently exposes player_id and player_name; it does not expose separate steam_id_64 or epic_id fields in this payload.
  • last_seen_at exists in serialized items and is derived from event timestamps.

Root Cause

The duplicate can happen when an early AdminLog event creates an id-less player bucket keyed by normalized name, and a later event for the same visible player includes a player_id and known team. The current aggregation treats those as separate keys (name:{name} and id:{player_id}), so the frontend receives two rows and renders them directly.

Functional Rules

  1. Live player stats must display at most one row per player.
  2. Dedupe identity preference:
    • player_id when available.
    • steam_id_64 when available.
    • epic_id when available.
    • normalized player_name fallback.
  3. Unknown/no disponible plus allies or axis must collapse to the known team.
  4. Stats split across duplicate buckets must be preserved.
  5. Same display name with different player_id values must remain distinct.
  6. If no reliable temporal team choice exists, known team wins over unknown.
  7. favorite_weapon should prefer available weapon evidence over no data.
  8. Counter copy must make clear that this is recent-event coverage, not online population.
  9. Do not touch TeamKills except as needed to preserve existing aggregation.

Expected Files to Modify

  • ai/tasks/in-progress/TASK-262-dedupe-current-match-live-player-stats.md
  • backend/app/rcon_admin_log_storage.py
  • backend/app/rcon_historical_storage.py
  • backend/tests/test_current_match_payload.py
  • frontend/assets/js/partida-actual.js
  • frontend/partida-actual.html

Constraints

  • Do not run ai-platform run.
  • Do not commit.
  • Do not push.
  • Do not touch physical assets, frontend/assets/img/, maps, weapons, clans or brands.
  • Do not touch scheduler or RCON server configuration.
  • Do not change 27001.
  • Do not reactivate Elo/MMR.
  • Do not reintroduce Comunidad Hispana #03.
  • Do not touch ai/system-metrics.md.
  • Do not include tmp/.
  • Do not include TASK-204.
  • Do not use git add ..
  • Do not include unrelated previous changes.

Implementation Plan

  1. Fix backend aggregation so name-key buckets merge into the player-id bucket when later identity evidence arrives.
  2. Keep known team when merging unknown and known evidence.
  3. Preserve counters, weapon counts, connection state, sources and latest timestamp when merging.
  4. Add frontend dedupe as a defensive guard before sorting/rendering.
  5. Change player counter copy to "Jugadores con estadisticas recientes: N".
  6. Add regression tests for unknown plus known team, same-team duplicates, split stats, distinct players, same name with different IDs and favorite weapon preservation.

Validation

Required checks:

  • python -m compileall backend/app
  • cd backend; python -m unittest tests.test_current_match_payload
  • node --check frontend/assets/js/partida-actual.js

Endpoint checks:

  • GET /api/current-match/players?server=comunidad-hispana-01
  • GET /api/current-match/players?server=comunidad-hispana-02

Visual checks:

  • /partida-actual.html?server=comunidad-hispana-01
  • /partida-actual.html?server=comunidad-hispana-02

Outcome

Implemented.

Backend changes:

  • /api/current-match/players now merges an id-less name bucket into the stronger player_id bucket when later AdminLog evidence identifies the same player.
  • Name-only events after one known identity now attach to that known identity when the name is unambiguous.
  • Same visible name with different player_id values remains distinct.
  • Known teams (Allies, Axis) are kept over unknown/None; unknown events no longer overwrite a known team.
  • Stats are accumulated with per-event keys for kills, deaths, teamkills, deaths by teamkill and weapon counts, so merging buckets does not double count the same event.
  • favorite_weapon continues to come from weapon frequency and is preserved when a real weapon exists.
  • rcon_historical_storage.py was additionally touched because its SQLite connection helper left files locked on Windows during the required tests. The helper now commits/rolls back and closes writer connections explicitly; readonly helpers close as well.

Frontend changes:

  • renderPlayerStats(...) now applies dedupeCurrentMatchPlayerStats(items) before sorting and rendering as a defensive guard.
  • Counter copy changed from Jugadores detectados: N to Jugadores con estadisticas recientes: N.
  • The static HTML default copy was updated to the same text.

Endpoint validation:

  • GET /api/current-match/players?server=comunidad-hispana-01
    • status: ok
    • scope: no-current-match-events
    • confidence: stale-filtered
    • rows: 0
    • duplicate player names: none
    • unknown plus known duplicates: none
  • GET /api/current-match/players?server=comunidad-hispana-02
    • status: ok
    • scope: open-admin-log-match-window
    • confidence: admin-log-boundary
    • rows: 1
    • duplicate player names: none
    • unknown plus known duplicates: none

Validation run:

  • Passed: python -m compileall backend/app
  • Passed: cd backend; python -m unittest tests.test_current_match_payload
  • Passed: node --check frontend/assets/js/partida-actual.js
  • Passed fallback HTML checks for:
    • http://127.0.0.1:8080/partida-actual.html?server=comunidad-hispana-01
    • http://127.0.0.1:8080/partida-actual.html?server=comunidad-hispana-02

Visual validation note:

  • The Browser plugin skill was read, but the required node_repl js browser control tool was not exposed in this session.
  • Local playwright was also not installed, so automated screenshot validation could not be completed.
  • HTML-served copy was validated with PowerShell as a fallback.

Working tree note:

  • Pre-existing unrelated changes remain outside this task, including ai/system-metrics.md, clan/map/weapon assets, tmp/, TASK-204 and TASK-242.