Investigate current match live staleness and clean copy
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
---
|
||||
id: TASK-267
|
||||
title: Investigate current match live data staleness and clean copy
|
||||
status: in-progress
|
||||
type: investigation
|
||||
team: Backend Senior
|
||||
supporting_teams: ["Frontend Senior"]
|
||||
roadmap_item: current-match
|
||||
priority: high
|
||||
---
|
||||
|
||||
# TASK-267 - Investigate current match live data staleness and clean copy
|
||||
|
||||
## Goal
|
||||
|
||||
Determine whether the current match killfeed and live player statistics are stale in the API, stale in frontend rendering, or mismatched against CRCON/RCON live data. Apply only the requested visible copy cleanup unless the diagnosis proves a safe code fix is required.
|
||||
|
||||
## Context
|
||||
|
||||
The current match page can show older live stats than CRCON/RCON live view. For example, CRCON may show a player with many more kills while the page still displays an older count. The killfeed may also appear stale.
|
||||
|
||||
Potential causes include stale `/api/current-match/players`, stale `/api/current-match/kills`, AdminLog ingestion issues, current-match window filtering, frontend signature/dedupe behavior, browser cache, or a source mismatch between live RCON scoreboard and derived event-based stats.
|
||||
|
||||
## Files to Read First
|
||||
|
||||
- `AGENTS.md`
|
||||
- `ai/architecture-index.md`
|
||||
- `ai/repo-context.md`
|
||||
- `ai/orchestrator/frontend-senior.md`
|
||||
- `ai/orchestrator/backend-senior.md`
|
||||
- `frontend/assets/js/partida-actual.js`
|
||||
- `backend/app/payloads.py`
|
||||
- `backend/app/rcon_admin_log_storage.py`
|
||||
- `backend/app/postgres_rcon_storage.py`
|
||||
|
||||
## Investigation Steps
|
||||
|
||||
1. Repeatedly sample killfeed endpoints for `comunidad-hispana-01` and `comunidad-hispana-02` with cache busters and no-cache headers.
|
||||
2. Repeatedly sample current-match player endpoints for `comunidad-hispana-01` and `comunidad-hispana-02` with cache busters and no-cache headers.
|
||||
3. Audit frontend polling, fetch cache behavior, render signatures, in-flight guards and dedupe logic.
|
||||
4. Audit backend payload, storage and query paths for current-match kills and players.
|
||||
5. Run read-only DB checks for recent AdminLog events where available.
|
||||
6. Inspect local service/container availability and logs where available.
|
||||
7. Classify the stale-display cause before applying any staleness fix.
|
||||
8. Apply requested visible copy cleanup.
|
||||
9. Validate syntax and document the result.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not execute `ai-platform run`.
|
||||
- Do not commit or push.
|
||||
- Do not touch physical assets or `frontend/assets/img/`.
|
||||
- Do not touch maps, weapons, clans or brands.
|
||||
- Do not change RCON hosts/ports, `27001`, or server configuration.
|
||||
- Do not reactivate Elo/MMR.
|
||||
- Do not reintroduce Comunidad Hispana #03.
|
||||
- Do not touch `ai/system-metrics.md`.
|
||||
- Do not include `tmp/`, TASK-204 or unrelated pending changes.
|
||||
- Do not use `git add .`.
|
||||
- Do not introduce technical backend wording into visible UI copy.
|
||||
|
||||
## Required Copy Changes
|
||||
|
||||
- Remove visible text: `Estadisticas derivadas de los eventos recientes.`
|
||||
- Remove visible text: `Mostrando las ultimas 12 bajas detectadas.`
|
||||
- Replace visible text: `Leyendo eventos recientes para esta partida.` with `Leyendo los enfrentamientos recientes de la partida.`
|
||||
|
||||
## Validation
|
||||
|
||||
If frontend changes:
|
||||
|
||||
- `node --check frontend/assets/js/partida-actual.js`
|
||||
|
||||
If backend changes:
|
||||
|
||||
- `python -m compileall backend/app`
|
||||
- `cd backend; python -m unittest tests.test_current_match_payload`
|
||||
|
||||
Functional checks:
|
||||
|
||||
- Repeated endpoint samples identify whether kills and players change over time.
|
||||
- Public endpoints return 200 for kills and players on #01 and #02.
|
||||
- UI copy no longer contains the removed text.
|
||||
- No unrelated pending changes are included.
|
||||
|
||||
## Findings
|
||||
|
||||
### Repeated Production Endpoint Samples
|
||||
|
||||
All repeated samples used cache busters and no-cache request headers.
|
||||
|
||||
Killfeed samples:
|
||||
|
||||
- `GET /api/current-match/kills?server=comunidad-hispana-01&limit=18`
|
||||
- 10 samples over roughly 100 seconds.
|
||||
- `scope=open-admin-log-match-window`.
|
||||
- `count=0` for all samples.
|
||||
- No first/last event id because the endpoint returned no kill rows.
|
||||
- `GET /api/current-match/kills?server=comunidad-hispana-02&limit=18`
|
||||
- 10 samples over roughly 100 seconds.
|
||||
- `scope=open-admin-log-match-window`.
|
||||
- `count=18` for all samples.
|
||||
- First event stayed `rcon-admin-log:comunidad-hispana-02:4246587`.
|
||||
- Last event stayed `rcon-admin-log:comunidad-hispana-02:4246570`.
|
||||
- Top rows stayed identical. Top event was `Jack -> AleixCommander` with `SMLE No.1 Mk III`.
|
||||
|
||||
Player-stat samples:
|
||||
|
||||
- `GET /api/current-match/players?server=comunidad-hispana-01`
|
||||
- 10 samples over roughly 100 seconds.
|
||||
- `count=1` for all samples.
|
||||
- Only visible row stayed `Core.D | Axis | K=0 | D=0`.
|
||||
- `GET /api/current-match/players?server=comunidad-hispana-02`
|
||||
- 10 samples over roughly 100 seconds.
|
||||
- `count=12` for all samples.
|
||||
- Top row stayed `Jack | Allies | K=16 | D=6 | SMLE No.1 Mk III`.
|
||||
- Full single follow-up sample confirmed Jack had `kills=16`, `last_seen_at=2026-06-17T15:34:14.312Z`, and source `connected,kill,message,team_switch`.
|
||||
|
||||
Public endpoint status validation:
|
||||
|
||||
- `current-match-kills-comunidad-hispana-01`: 200.
|
||||
- `current-match-kills-comunidad-hispana-02`: 200.
|
||||
- `current-match-players-comunidad-hispana-01`: 200.
|
||||
- `current-match-players-comunidad-hispana-02`: 200.
|
||||
|
||||
### Frontend Analysis
|
||||
|
||||
- `CURRENT_MATCH_KILL_FEED_POLL_INTERVAL_MS` is `1500`; killfeed fetch is scheduled every 1.5 seconds.
|
||||
- `CURRENT_MATCH_PLAYER_STATS_POLL_INTERVAL_MS` is `3000`; player stats fetch is scheduled every 3 seconds.
|
||||
- `killFeedRefreshInFlight` and `playerStatsRefreshInFlight` are reset in `finally`, so normal request failures should not leave either flag stuck.
|
||||
- Killfeed rendering consumes `data.items` directly, filters invalid events, dedupes by event id/semantic key, and renders the latest visible window after TASK-262 style dedupe.
|
||||
- Killfeed visible signature includes event/window key, killer/victim names, teams, weapon and teamkill flag.
|
||||
- Player stats rendering consumes `data.items`, dedupes rows, sorts by kills/deaths/name, and renders the table from the current endpoint payload.
|
||||
- Player stats visible signature includes player name, team, kills, deaths, teamkills, deaths by teamkill, favorite weapon and last seen.
|
||||
- If endpoint data changes in any of those fields, the frontend should re-render.
|
||||
- `fetchJson` currently uses plain `fetch(url)` and does not set `cache: "no-store"`. The repeated cache-busted production samples still stayed stable, so cache was not proven as the cause in this run.
|
||||
- No evidence was found that visibleSignature would block a real kills/stat change from the endpoint.
|
||||
|
||||
### Backend Analysis
|
||||
|
||||
- `/api/current-match/kills` is routed in `backend/app/routes.py` to `build_current_match_kill_feed_payload(...)`.
|
||||
- `/api/current-match/players` is routed in `backend/app/routes.py` to `build_current_match_player_stats_payload(...)`.
|
||||
- Both payload builders read from AdminLog storage through:
|
||||
- `list_current_match_kill_feed(...)`
|
||||
- `list_current_match_player_stats(...)`
|
||||
- Neither endpoint queries live RCON directly during the public GET.
|
||||
- Killfeed source table is `rcon_admin_log_events`.
|
||||
- Player stat source is also `rcon_admin_log_events`; it derives kills/deaths/teamkills/favorite weapon by replaying current-window AdminLog events.
|
||||
- Current match window is selected from the latest `match_start` / `match_end` boundary for the target server.
|
||||
- If the latest boundary is `match_start`, the endpoints use `server_time >= open_start_time`.
|
||||
- If no open match boundary exists, fallback mode uses recent AdminLog rows and filters by freshness.
|
||||
- Target matching uses `(target_key = ? OR external_server_id = ?)`, so both target fields are considered for `comunidad-hispana-01` and `comunidad-hispana-02`.
|
||||
|
||||
### DB / Service Checks
|
||||
|
||||
- Local config reports:
|
||||
- `use_postgres_rcon_storage=False`
|
||||
- `database_url_set=False`
|
||||
- `storage_path=D:\Proyectos\HLL Vietnam\backend\data\hll_vietnam_dev.sqlite3`
|
||||
- `historical_data_source_kind=rcon`
|
||||
- Local SQLite schema confirms `rcon_admin_log_events` stores the current-match event source.
|
||||
- Local SQLite data is not production-current:
|
||||
- Latest local `comunidad-hispana-01` events are from May 20, 2026.
|
||||
- Latest local `comunidad-hispana-02` events are from May 20, 2026.
|
||||
- Production endpoint rows sampled during this task are from June 17, 2026.
|
||||
- Local DB max samples repeated after 60 seconds did not change, which only proves local ingestion was not running in this workspace.
|
||||
- Docker is not available locally:
|
||||
- `docker ps --format "{{.Names}}"` failed because Docker Desktop/Linux engine is not running.
|
||||
- Container logs could not be inspected from this workspace.
|
||||
- Production DB/container access was not available from this workspace, so production ingestion status and worker logs could not be directly verified.
|
||||
|
||||
### Classification
|
||||
|
||||
- During the sample window, both production endpoints were stable. This does not prove a bug by itself because there may have been no new kills/events during the 100-second window.
|
||||
- A later real-world check found that the server had changed map and the new match had no kills yet, so `/api/current-match/kills` returning `count=0` in that state is expected and does not prove a bug.
|
||||
- The concrete stale-display example described by the user was not reproduced in the sampled state: production `players` for #02 reported Jack with 16 kills, which is newer than the example of 15 in CRCON and much newer than the stale UI example of 5.
|
||||
- The active data source for both current-match feed and player stats is stored AdminLog, not direct live scoreboard state.
|
||||
- If CRCON live changes while these endpoints do not, the cause is backend/source-side: AdminLog ingestion, AdminLog query/window filtering, or mismatch between live scoreboard data and derived event data.
|
||||
- No evidence was found that frontend visibleSignature/dedupe would ignore changed endpoint values.
|
||||
- No backend staleness fix was applied because production DB/log access was unavailable and the current endpoint samples did not prove a specific query/window bug.
|
||||
|
||||
## Exact Cause
|
||||
|
||||
For the stale display reported by the user, the exact confirmed cause is not a frontend render blocker. The current page displays exactly what `/api/current-match/kills` and `/api/current-match/players` return, and the render signatures include the fields needed to refresh changed rows.
|
||||
|
||||
The most precise verified cause is source-model mismatch and/or AdminLog read-model freshness: current-match player stats are derived from stored AdminLog events, while CRCON live can show direct live scoreboard state. If stored AdminLog ingestion or the current-match AdminLog window lags behind CRCON live, both killfeed and player stats will appear stale even though the frontend polling continues.
|
||||
|
||||
Root-cause confirmation still requires a future validation window where CRCON/RCON live shows new kills after a map change and the current-match endpoints can be sampled at the same time.
|
||||
|
||||
## Changes Applied
|
||||
|
||||
- `frontend/partida-actual.html`
|
||||
- Replaced visible copy:
|
||||
- From `Leyendo eventos recientes para esta partida.`
|
||||
- To `Leyendo los enfrentamientos recientes de la partida.`
|
||||
- `frontend/assets/js/partida-actual.js`
|
||||
- Removed the visible feed status/count line when kill rows exist.
|
||||
- Removed the visible player-stats line `Estadisticas derivadas de los eventos recientes.` when stat rows exist.
|
||||
- Empty/error states remain visible.
|
||||
|
||||
No backend, scheduler, RCON, server configuration, physical asset or image files were changed.
|
||||
|
||||
## Validation Results
|
||||
|
||||
- Repeated production endpoint sampling for kills #01/#02 and players #01/#02.
|
||||
- Public endpoint status checks for kills #01/#02 and players #01/#02 returned 200.
|
||||
- Frontend polling/render/signature audit completed.
|
||||
- Backend route/payload/storage audit completed.
|
||||
- Local SQLite read-only schema/data checks completed.
|
||||
- Docker/service log inspection attempted; Docker engine was unavailable locally.
|
||||
- `node --check frontend/assets/js/partida-actual.js` passed.
|
||||
- Verified the requested removed/replaced visible text with `rg`.
|
||||
- Reviewed `git diff -- frontend/assets/js/partida-actual.js frontend/partida-actual.html`.
|
||||
@@ -282,11 +282,8 @@ function renderKillFeedEvents({ data, events, nodes, state }) {
|
||||
state.visibleSignature = visibleSignature;
|
||||
state.visibleCount = visualEvents.length;
|
||||
}
|
||||
nodes.feedState.textContent = formatKillFeedCoverage(
|
||||
data.scope,
|
||||
visualEvents.length,
|
||||
events.length,
|
||||
);
|
||||
nodes.feedState.textContent = "";
|
||||
nodes.feedState.hidden = true;
|
||||
nodes.feedState.classList.remove("historical-state--error");
|
||||
}
|
||||
|
||||
@@ -601,7 +598,9 @@ function renderPlayerStats(data, nodes, state) {
|
||||
state.visibleSignature = signature;
|
||||
}
|
||||
nodes.playerStatsShell.hidden = false;
|
||||
setState(nodes.playerStatsState, "Estadisticas derivadas de los eventos recientes.");
|
||||
nodes.playerStatsState.textContent = "";
|
||||
nodes.playerStatsState.hidden = true;
|
||||
nodes.playerStatsState.classList.remove("historical-state--error");
|
||||
}
|
||||
|
||||
function renderDetectedPlayerCount(count, nodes) {
|
||||
@@ -1078,12 +1077,6 @@ function formatDuration(value) {
|
||||
}
|
||||
|
||||
function formatKillFeedCoverage(scope, visibleCount = 0, totalCount = 0) {
|
||||
if (
|
||||
visibleCount > 0 &&
|
||||
(totalCount > visibleCount || scope === "preserved-recent-window")
|
||||
) {
|
||||
return `Mostrando las últimas ${visibleCount} bajas detectadas.`;
|
||||
}
|
||||
if (scope === "open-admin-log-match-window") {
|
||||
return "Bajas detectadas en la partida actual.";
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
<p class="eyebrow eyebrow--section">Combate</p>
|
||||
<h2 id="current-match-feed-title">Feed de combate</h2>
|
||||
<p class="historical-panel__note">
|
||||
Leyendo eventos recientes para esta partida.
|
||||
Leyendo los enfrentamientos recientes de la partida.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user