Document teamkills ranking zero audit
This commit is contained in:
128
ai/tasks/done/TASK-251-investigate-teamkills-ranking-zeroes.md
Normal file
128
ai/tasks/done/TASK-251-investigate-teamkills-ranking-zeroes.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# TASK-251 - Investigate teamkills ranking zeroes
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This task audits why annual `teamkills` ranking data is missing or zeroed in practice.
|
||||||
|
|
||||||
|
No fix was implemented because the issue is upstream from the public ranking UI and needs a source decision first.
|
||||||
|
|
||||||
|
## Files Read First
|
||||||
|
|
||||||
|
- `backend/app/rcon_admin_log_parser.py`
|
||||||
|
- `backend/app/rcon_admin_log_storage.py`
|
||||||
|
- `backend/app/rcon_admin_log_materialization.py`
|
||||||
|
- `backend/app/rcon_historical_player_stats.py`
|
||||||
|
- `backend/app/rcon_annual_rankings.py`
|
||||||
|
- `backend/app/routes.py`
|
||||||
|
- `backend/tests/test_rcon_materialization_pipeline.py`
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### 1. Parser support exists
|
||||||
|
|
||||||
|
The parser already extracts:
|
||||||
|
|
||||||
|
- `killer_team`
|
||||||
|
- `victim_team`
|
||||||
|
|
||||||
|
The materialization code already increments:
|
||||||
|
|
||||||
|
- `teamkills`
|
||||||
|
- `deaths_by_teamkill`
|
||||||
|
|
||||||
|
when `killer_team == victim_team`.
|
||||||
|
|
||||||
|
Tests already cover this logic.
|
||||||
|
|
||||||
|
### 2. Local match facts contain no positive teamkills
|
||||||
|
|
||||||
|
Read-only local checks on `backend/data/hll_vietnam_dev.sqlite3` show:
|
||||||
|
|
||||||
|
- `rcon_admin_log_events` has many `kill` rows
|
||||||
|
- `rcon_match_player_stats where teamkills > 0` returns zero rows
|
||||||
|
- `rcon_annual_ranking_snapshot_items where teamkills > 0` returns zero rows
|
||||||
|
|
||||||
|
### 3. Local kill events contain no same-team kills
|
||||||
|
|
||||||
|
Read-only checks on parsed event payloads show:
|
||||||
|
|
||||||
|
- kill payloads include `killer_team` and `victim_team`
|
||||||
|
- but the local dataset contains zero `kill` rows where both teams are equal
|
||||||
|
|
||||||
|
That means the materialized player facts never receive a positive `teamkills` increment in this dataset.
|
||||||
|
|
||||||
|
### 4. Player profile snapshots do contain teamkill totals
|
||||||
|
|
||||||
|
`rcon_player_profile_snapshots` contains positive values for:
|
||||||
|
|
||||||
|
- `teamkills_done`
|
||||||
|
- `teamkills_received`
|
||||||
|
|
||||||
|
So the broader AdminLog/profile ecosystem does know about teamkills somewhere, but those totals are:
|
||||||
|
|
||||||
|
- lifetime-oriented
|
||||||
|
- not tied to one exact closed match window
|
||||||
|
- not directly usable for annual or per-match ranking backfills without a separate design
|
||||||
|
|
||||||
|
### 5. Annual public ranking payload currently returns empty for teamkills
|
||||||
|
|
||||||
|
Production and local checks both show:
|
||||||
|
|
||||||
|
- `/api/ranking?timeframe=annual&metric=teamkills&...` returns `items = []`
|
||||||
|
|
||||||
|
So the user-visible issue is not a frontend parse bug. It is a data-source gap for annual teamkill ranking inputs.
|
||||||
|
|
||||||
|
## Cause Probable
|
||||||
|
|
||||||
|
The annual ranking consumes `rcon_match_player_stats.teamkills`.
|
||||||
|
|
||||||
|
That field is derived from same-team `kill` events in the materialized AdminLog match stream.
|
||||||
|
|
||||||
|
In the audited dataset:
|
||||||
|
|
||||||
|
- same-team `kill` events are not present
|
||||||
|
- therefore `rcon_match_player_stats.teamkills` stays at zero
|
||||||
|
- therefore annual `teamkills` ranking has no rows
|
||||||
|
|
||||||
|
Meanwhile, profile snapshots expose lifetime teamkill totals, but they are not an annual closed-match ranking source.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Do not patch the public ranking UI.
|
||||||
|
|
||||||
|
Do not inject lifetime `teamkills_done` into annual ranking, because that would mix incompatible time models.
|
||||||
|
|
||||||
|
Do not fabricate teamkills from unrelated signals.
|
||||||
|
|
||||||
|
## Follow-up Direction
|
||||||
|
|
||||||
|
The next correct task should choose one of these paths explicitly:
|
||||||
|
|
||||||
|
1. Confirm whether the real RCON/AdminLog kill stream should include same-team kills and why they are absent.
|
||||||
|
2. If same-team kills are absent by product/API behavior, define a separate ranking source for teamkills.
|
||||||
|
3. If an alternate raw source exists, backfill `rcon_match_player_stats.teamkills` from that source with tests.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Read-only checks executed:
|
||||||
|
|
||||||
|
- local SQLite schema audit for:
|
||||||
|
- `rcon_admin_log_events`
|
||||||
|
- `rcon_match_player_stats`
|
||||||
|
- `rcon_annual_ranking_snapshots`
|
||||||
|
- `rcon_annual_ranking_snapshot_items`
|
||||||
|
- local count of `event_type='kill'`
|
||||||
|
- local count of `teamkills > 0` in materialized player stats
|
||||||
|
- local count of same-team parsed kill events
|
||||||
|
- local inspection of `parsed_payload_json` samples
|
||||||
|
- local inspection of `rcon_player_profile_snapshots.teamkills_done/teamkills_received`
|
||||||
|
- production endpoint checks for:
|
||||||
|
- annual all-servers teamkills ranking
|
||||||
|
- annual comunidad-hispana-01 teamkills ranking
|
||||||
|
- annual comunidad-hispana-02 teamkills ranking
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- No backend code changed.
|
||||||
|
- No frontend code changed for this audit.
|
||||||
|
- No migrations or data writes were performed.
|
||||||
94
docs/TEAMKILLS_RANKING_AUDIT.md
Normal file
94
docs/TEAMKILLS_RANKING_AUDIT.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Teamkills Ranking Audit
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This document explains why annual public `teamkills` ranking currently returns no usable rows in the audited dataset.
|
||||||
|
|
||||||
|
## What Was Checked
|
||||||
|
|
||||||
|
Sources reviewed:
|
||||||
|
|
||||||
|
- `rcon_admin_log_events`
|
||||||
|
- `rcon_match_player_stats`
|
||||||
|
- `rcon_annual_ranking_snapshots`
|
||||||
|
- `rcon_annual_ranking_snapshot_items`
|
||||||
|
- `rcon_player_profile_snapshots`
|
||||||
|
|
||||||
|
Code reviewed:
|
||||||
|
|
||||||
|
- `backend/app/rcon_admin_log_parser.py`
|
||||||
|
- `backend/app/rcon_admin_log_storage.py`
|
||||||
|
- `backend/app/rcon_admin_log_materialization.py`
|
||||||
|
- `backend/app/rcon_annual_rankings.py`
|
||||||
|
- `backend/app/routes.py`
|
||||||
|
- `backend/tests/test_rcon_materialization_pipeline.py`
|
||||||
|
|
||||||
|
## What Works Already
|
||||||
|
|
||||||
|
The parser and materialization pipeline already support teamkills when the source event is explicit enough.
|
||||||
|
|
||||||
|
The key condition is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
killer_team == victim_team
|
||||||
|
```
|
||||||
|
|
||||||
|
When that is true, the materializer increments:
|
||||||
|
|
||||||
|
- `teamkills` on the killer
|
||||||
|
- `deaths_by_teamkill` on the victim
|
||||||
|
|
||||||
|
This behavior is covered by regression tests.
|
||||||
|
|
||||||
|
## What The Local Dataset Shows
|
||||||
|
|
||||||
|
Read-only checks on `backend/data/hll_vietnam_dev.sqlite3` show:
|
||||||
|
|
||||||
|
- many `kill` events exist
|
||||||
|
- zero `rcon_match_player_stats` rows with `teamkills > 0`
|
||||||
|
- zero annual ranking snapshot items with `teamkills > 0`
|
||||||
|
- zero parsed `kill` events where `killer_team == victim_team`
|
||||||
|
|
||||||
|
That means the annual ranking source facts never receive positive teamkill values.
|
||||||
|
|
||||||
|
## What The Profile Snapshots Show
|
||||||
|
|
||||||
|
`rcon_player_profile_snapshots` does contain positive:
|
||||||
|
|
||||||
|
- `teamkills_done`
|
||||||
|
- `teamkills_received`
|
||||||
|
|
||||||
|
So the broader data ecosystem knows teamkills somewhere, but those values are:
|
||||||
|
|
||||||
|
- cumulative
|
||||||
|
- profile-oriented
|
||||||
|
- not scoped to exact closed match windows
|
||||||
|
|
||||||
|
Using them directly for annual ranking would be semantically wrong.
|
||||||
|
|
||||||
|
## Public Endpoint Check
|
||||||
|
|
||||||
|
The current public endpoint returns empty annual teamkill ranking items for:
|
||||||
|
|
||||||
|
- `all`
|
||||||
|
- `comunidad-hispana-01`
|
||||||
|
- `comunidad-hispana-02`
|
||||||
|
|
||||||
|
So the problem is not a frontend-only rendering issue.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The current annual teamkills ranking depends on:
|
||||||
|
|
||||||
|
- `rcon_match_player_stats.teamkills`
|
||||||
|
|
||||||
|
In the audited dataset, that field remains zero because the match-level kill stream does not contain same-team kill events that the materializer can classify as teamkills.
|
||||||
|
|
||||||
|
## Safe Next Step
|
||||||
|
|
||||||
|
Do not fake teamkills from unrelated counters.
|
||||||
|
|
||||||
|
The next implementation task should first decide whether:
|
||||||
|
|
||||||
|
1. the event stream is incomplete and needs parser/source correction, or
|
||||||
|
2. teamkills need a dedicated ranking source different from match-level kill events.
|
||||||
Reference in New Issue
Block a user