312 lines
9.7 KiB
Markdown
312 lines
9.7 KiB
Markdown
# Stats Section Functional Plan
|
|
|
|
## Objective
|
|
|
|
Define the V1 functional contract for a future `Stats` section where a player can search their profile, review personal historical performance across HLL Vietnam community servers, and understand their weekly and monthly ranking position without implementing backend or frontend yet.
|
|
|
|
## Current Foundation
|
|
|
|
- HLL Vietnam historical read paths are RCON-first.
|
|
- `backend/app/rcon_historical_leaderboards.py` already defines weekly and monthly leaderboard windows over materialized RCON/AdminLog match stats.
|
|
- Current reliable materialized counters for public player-facing V1 are kills, deaths, teamkills and matches considered.
|
|
- Public scoreboard data remains fallback or enrichment only; it must not become the normal primary source for this section while RCON coverage exists.
|
|
|
|
## V1 Scope
|
|
|
|
The first `Stats` contract covers:
|
|
|
|
- player search by name or player id
|
|
- player detail view for one selected player
|
|
- personal totals for a selected timeframe and server scope
|
|
- weekly ranking position by kills
|
|
- monthly ranking position by kills
|
|
- data freshness and source metadata
|
|
- annual top 20 preparation through a dedicated snapshot model
|
|
|
|
V1 should support two server scopes:
|
|
|
|
- one specific server id
|
|
- `all` aggregated across active supported servers
|
|
|
|
## User Flow
|
|
|
|
1. User opens the future `Stats` section.
|
|
2. User searches by partial player name or exact player id.
|
|
3. Backend returns compact search matches ordered by best textual match and recent activity.
|
|
4. User selects one player result.
|
|
5. Frontend requests the player detail payload for a chosen server scope and timeframe.
|
|
6. UI shows core totals, calculated ratios, weekly rank position and monthly rank position.
|
|
7. UI may later request annual ranking snapshot data for the selected year without recalculating it on each public request.
|
|
|
|
## API Contract Direction
|
|
|
|
### 1. Player search
|
|
|
|
```http
|
|
GET /api/stats/players/search?q=<query>&server_id=<server-or-all>&limit=10
|
|
```
|
|
|
|
Purpose:
|
|
|
|
- resolve a user-entered name fragment or player id into selectable player records backed by materialized historical data
|
|
|
|
Query rules:
|
|
|
|
- `q` is required
|
|
- `server_id` is optional and defaults to `all`
|
|
- `limit` is optional and defaults to `10`, with a hard cap such as `20`
|
|
|
|
Response shape:
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"data": {
|
|
"query": "rambo",
|
|
"server_id": "all",
|
|
"items": [
|
|
{
|
|
"player_id": "76561198000000000",
|
|
"player_name": "Rambo",
|
|
"matches_considered": 42,
|
|
"last_seen_at": "2026-06-06T21:40:00Z",
|
|
"servers_seen": ["comunidad-hispana-01", "comunidad-hispana-02"]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
Search result notes:
|
|
|
|
- `player_id` is the canonical selection key for later requests
|
|
- `player_name` is the latest display name known from materialized stats
|
|
- `matches_considered` is the aggregated closed-match count in the selected scope
|
|
- `servers_seen` is optional and may be omitted when `server_id` is not `all`
|
|
|
|
### 2. Personal player stats
|
|
|
|
```http
|
|
GET /api/stats/players/{player_id}?server_id=<server-or-all>&timeframe=weekly|monthly
|
|
```
|
|
|
|
Purpose:
|
|
|
|
- return the selected player summary for the chosen scope and timeframe plus ranking position context
|
|
|
|
Response shape:
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"data": {
|
|
"player_id": "76561198000000000",
|
|
"player_name": "Rambo",
|
|
"server_id": "all",
|
|
"timeframe": "monthly",
|
|
"window_start": "2026-06-01T00:00:00Z",
|
|
"window_end": "2026-06-07T18:00:00Z",
|
|
"matches_considered": 12,
|
|
"kills": 356,
|
|
"deaths": 241,
|
|
"teamkills": 4,
|
|
"kd_ratio": 1.48,
|
|
"kills_per_match": 29.67,
|
|
"deaths_per_match": 20.08,
|
|
"weekly_ranking": {
|
|
"metric": "kills",
|
|
"ranking_position": 8,
|
|
"window_kind": "current-week",
|
|
"window_start": "2026-06-02T00:00:00Z",
|
|
"window_end": "2026-06-07T18:00:00Z"
|
|
},
|
|
"monthly_ranking": {
|
|
"metric": "kills",
|
|
"ranking_position": 5,
|
|
"window_kind": "current-month",
|
|
"window_start": "2026-06-01T00:00:00Z",
|
|
"window_end": "2026-06-07T18:00:00Z"
|
|
},
|
|
"source": {
|
|
"primary_source": "rcon",
|
|
"read_model": "rcon-materialized-admin-log-player-stats",
|
|
"generated_at": "2026-06-07T18:02:00Z",
|
|
"source_range_start": "2026-06-01T00:14:00Z",
|
|
"source_range_end": "2026-06-07T17:51:00Z",
|
|
"freshness": "runtime"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Field rules:
|
|
|
|
- `timeframe=weekly` returns the player totals within the same weekly window policy used by leaderboard reads
|
|
- `timeframe=monthly` returns the player totals within the same monthly window policy used by leaderboard reads
|
|
- `timeframe=all` is not implemented in the current contract and remains out of scope until a dedicated implementation is added.
|
|
- `kd_ratio`, `kills_per_match` and `deaths_per_match` should be rounded for display-safe payloads
|
|
|
|
### 3. Annual ranking snapshot
|
|
|
|
```http
|
|
GET /api/stats/rankings/annual?year=<year>&server_id=<server-or-all>&metric=kills
|
|
```
|
|
|
|
Purpose:
|
|
|
|
- return a precomputed annual top 20 leaderboard snapshot without recalculating the full year on each public request
|
|
|
|
Response shape:
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"data": {
|
|
"year": 2026,
|
|
"server_id": "all",
|
|
"metric": "kills",
|
|
"generated_at": "2027-01-01T01:30:00Z",
|
|
"snapshot_status": "ready",
|
|
"items": [
|
|
{
|
|
"ranking_position": 1,
|
|
"player_id": "76561198000000000",
|
|
"player_name": "Rambo",
|
|
"metric_value": 4210,
|
|
"matches_considered": 148,
|
|
"kills": 4210,
|
|
"deaths": 2950,
|
|
"teamkills": 18,
|
|
"kd_ratio": 1.43
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
V1 annual endpoint notes:
|
|
|
|
- only `metric=kills` needs to be committed in the first annual design pass
|
|
- annual read path should be snapshot-backed, not runtime full-range aggregation
|
|
- if a requested year is not generated yet, respond with a controlled empty or pending state, not a slow public recalculation
|
|
|
|
## Ranking Calculation Rules
|
|
|
|
### Weekly position
|
|
|
|
- Use the same weekly window selected by `select_leaderboard_window(... timeframe="weekly")`
|
|
- Respect the existing sufficient-sample policy:
|
|
- use current week when the closed-match threshold is met
|
|
- fall back to previous week when the current week sample is insufficient
|
|
- Rank by total kills descending
|
|
- Break ties by:
|
|
- `matches_considered` descending
|
|
- `player_name` ascending
|
|
- Player position should be derived from the same aggregate query family as the public leaderboard to avoid contradictory ordering
|
|
|
|
### Monthly position
|
|
|
|
- Use the same monthly window selected by `select_leaderboard_window(... timeframe="monthly")`
|
|
- Respect the current rule already present in the read model:
|
|
- from day 1 to day 7, use previous month
|
|
- from day 8 onward, use current month
|
|
- Rank by total kills descending with the same tie-break rules used in weekly ranking
|
|
|
|
### Personal totals
|
|
|
|
- Use the same RCON materialized match/player stats source as leaderboard calculations
|
|
- Only closed matches within the selected scope and selected timeframe window should count
|
|
- Do not add support, offense, defense or weapon breakdown fields until they are proven reliable in the RCON materialized model
|
|
|
|
## Annual Snapshot Persistence Direction
|
|
|
|
Future implementation should add a dedicated persistence model instead of recomputing annual top 20 on every request.
|
|
|
|
### Table: `rcon_annual_ranking_snapshots`
|
|
|
|
Expected columns:
|
|
|
|
- `id`
|
|
- `server_id`
|
|
- `year`
|
|
- `metric`
|
|
- `generated_at`
|
|
- `source_range_start`
|
|
- `source_range_end`
|
|
- `snapshot_status`
|
|
- `item_count`
|
|
- `generation_policy`
|
|
- `notes` nullable
|
|
|
|
Expected uniqueness:
|
|
|
|
- unique on `(server_id, year, metric)`
|
|
|
|
Expected behavior:
|
|
|
|
- one authoritative snapshot row per year, scope and metric
|
|
- regenerated only by an explicit batch job, scheduled maintenance command or year-close workflow
|
|
|
|
### Table: `rcon_annual_ranking_snapshot_items`
|
|
|
|
Expected columns:
|
|
|
|
- `id`
|
|
- `snapshot_id`
|
|
- `ranking_position`
|
|
- `player_id`
|
|
- `player_name`
|
|
- `metric_value`
|
|
- `matches_considered`
|
|
- `kills`
|
|
- `deaths`
|
|
- `teamkills`
|
|
- `kd_ratio`
|
|
|
|
Expected uniqueness:
|
|
|
|
- unique on `(snapshot_id, ranking_position)`
|
|
- unique on `(snapshot_id, player_id)`
|
|
|
|
Expected behavior:
|
|
|
|
- store only the top 20 rows for the selected annual snapshot
|
|
- preserve display-ready values used by the frontend
|
|
- keep enough totals to show a concise annual card without joining additional tables at read time
|
|
|
|
### Generation policy
|
|
|
|
- Run after year close or through an explicit maintenance workflow
|
|
- Read from materialized RCON historical data, not live RCON calls
|
|
- Default scope should support both `all` and individual active servers
|
|
- Prefer idempotent replace or upsert semantics for a given `(server_id, year, metric)`
|
|
- Store `generated_at` and source range metadata for traceability
|
|
|
|
## V1 Non-goals
|
|
|
|
- Elo or MMR reactivation
|
|
- Comunidad Hispana #03 reintroduction
|
|
- support, combat, offense or defense ranking when not already reliable in materialized RCON stats
|
|
- weapon-level breakdowns
|
|
- charts, heatmaps or advanced visualizations
|
|
- authenticated or private player profiles
|
|
- backend endpoint implementation
|
|
- database migrations
|
|
- frontend page, components or scripts
|
|
|
|
## Future Extensions After V1
|
|
|
|
- map-by-map player breakdowns
|
|
- server comparison cards per player
|
|
- annual ranking filters beyond kills
|
|
- profile history charts when a stable event or snapshot series exists
|
|
- external links to trusted scoreboard detail when correlation is already validated
|
|
|
|
## Recommended Follow-up Tasks
|
|
|
|
- add player search endpoint over materialized RCON player stats
|
|
- add personal player stats endpoint with weekly and monthly ranking context
|
|
- design and implement annual ranking snapshot schema and generation command
|
|
- extend frontend data consumption plan with the `Stats` section integration sequence
|
|
- implement the `Stats` frontend section with static-safe fallback behavior
|