262 lines
8.3 KiB
Markdown
262 lines
8.3 KiB
Markdown
# Global Ranking Page Plan
|
|
|
|
## Objective
|
|
|
|
Define the V1 functional contract for a dedicated `Ranking` page that exposes public top lists across HLL Vietnam community servers without merging this flow into `Stats`.
|
|
|
|
## Why Ranking Is Separate From Stats
|
|
|
|
`Stats` and `Ranking` solve different user jobs:
|
|
|
|
- `Stats` is player-centric: search one player, open one profile, inspect personal performance.
|
|
- `Ranking` is leaderboard-centric: open a public table, switch filters, compare top players.
|
|
|
|
They can safely share:
|
|
|
|
- the same sober tactical visual system already used by `stats.html` and `historico.html`
|
|
- the same backend health/offline messaging pattern
|
|
- the same table/card rendering conventions for loading, empty and error states
|
|
|
|
They must remain separate in navigation and contracts:
|
|
|
|
- `Stats` keeps player search and personal summary endpoints.
|
|
- `Ranking` gets its own page and dedicated ranking endpoint.
|
|
- `Ranking` may link to `Stats` for player lookup, but it does not embed the player search workflow as a primary interaction.
|
|
|
|
## Current Foundation
|
|
|
|
- Weekly and monthly historical leaderboard reads already exist in `backend/app/rcon_historical_leaderboards.py`.
|
|
- Annual ranking reads already exist through snapshot storage in `backend/app/rcon_annual_rankings.py`.
|
|
- Historical ranking remains RCON-first.
|
|
- Public scoreboard remains fallback or enrichment only and must not become the primary read path while RCON coverage exists.
|
|
|
|
## V1 Scope
|
|
|
|
The first Ranking page supports:
|
|
|
|
- public top-player lists
|
|
- timeframe filter: `weekly`, `monthly`, `annual`
|
|
- server scope filter: `all`, `comunidad-hispana-01`, `comunidad-hispana-02`
|
|
- metric filter: V1 committed to `kills`
|
|
- limit filter: default `20`, with smaller values allowed
|
|
- filter changes without manual page reload where feasible
|
|
- clear differentiation between `ready`, `missing`, `empty`, `offline` and request-error states
|
|
|
|
## V1 Non-goals
|
|
|
|
- Elo/MMR
|
|
- authentication
|
|
- private or expanded player profiles
|
|
- advanced charts
|
|
- weapon or map breakdowns
|
|
- large schema changes
|
|
- annual recalculation on public request
|
|
- reintroducing Comunidad Hispana #03
|
|
|
|
## User Flow
|
|
|
|
1. User opens `Ranking`.
|
|
2. Page checks backend availability.
|
|
3. User chooses timeframe.
|
|
4. User chooses server scope.
|
|
5. User chooses metric.
|
|
6. User chooses limit if needed.
|
|
7. Frontend requests ranking data and updates the list in place.
|
|
8. User can switch filters again without manual reload.
|
|
9. If the user wants one-player analysis, the page offers a small link to `Stats`.
|
|
|
|
## Data Source Policy
|
|
|
|
### Weekly and monthly
|
|
|
|
- Source: materialized RCON/AdminLog leaderboard read model.
|
|
- Reader: existing weekly/monthly selection logic in `select_leaderboard_window(...)`.
|
|
- Ordering: metric desc, `matches_considered` desc, `player_name` asc.
|
|
- V1 metric commitment: `kills`.
|
|
|
|
### Annual
|
|
|
|
- Source: persisted annual ranking snapshots.
|
|
- Reader: existing annual snapshot loader.
|
|
- No annual recomputation during public requests.
|
|
- Missing annual data must surface as a controlled `snapshot_status="missing"` state.
|
|
|
|
## API Contract Direction
|
|
|
|
### Dedicated Ranking endpoint
|
|
|
|
```http
|
|
GET /api/ranking?timeframe=weekly|monthly|annual&server_id=<server-or-all>&metric=kills&limit=20&year=<year-when-annual>
|
|
```
|
|
|
|
Purpose:
|
|
|
|
- give Ranking its own public contract without changing Stats endpoints or mixing player-profile concerns into ranking reads
|
|
|
|
Query rules:
|
|
|
|
- `timeframe` is optional and defaults to `weekly`
|
|
- `server_id` is optional and defaults to `all`
|
|
- `metric` is optional and defaults to `kills`
|
|
- `limit` is optional and defaults to `20`
|
|
- `year` is required only when `timeframe=annual`
|
|
|
|
Validation rules:
|
|
|
|
- allowed `timeframe`: `weekly`, `monthly`, `annual`
|
|
- V1 allowed `metric`: `kills`
|
|
- allowed `limit`: `1..100`
|
|
- `year` must be a positive integer when annual is requested
|
|
- annual requests ignore weekly/monthly runtime window policy and read snapshots only
|
|
|
|
## Response Contract
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"data": {
|
|
"page_kind": "global-ranking",
|
|
"timeframe": "monthly",
|
|
"server_id": "all",
|
|
"metric": "kills",
|
|
"limit": 20,
|
|
"requested_limit": 20,
|
|
"window_start": "2026-06-01T00:00:00Z",
|
|
"window_end": "2026-06-08T18:00:00Z",
|
|
"window_kind": "current-month",
|
|
"window_label": "Mes actual",
|
|
"source": {
|
|
"primary_source": "rcon",
|
|
"read_model": "rcon-materialized-admin-log-leaderboard",
|
|
"generated_at": "2026-06-08T18:00:00Z",
|
|
"freshness": "runtime"
|
|
},
|
|
"snapshot_status": "ready",
|
|
"items": [
|
|
{
|
|
"ranking_position": 1,
|
|
"player_id": "76561198000000000",
|
|
"player_name": "Rambo",
|
|
"metric_value": 421,
|
|
"matches_considered": 14,
|
|
"kills": 421,
|
|
"deaths": 280,
|
|
"teamkills": 3,
|
|
"kd_ratio": 1.5
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Field Rules
|
|
|
|
Common top-level fields:
|
|
|
|
- `timeframe`: `weekly`, `monthly`, `annual`
|
|
- `server_id`: `all` or one supported active server id
|
|
- `metric`: `kills` in V1
|
|
- `limit`: effective served limit
|
|
- `requested_limit`: requested client limit
|
|
- `snapshot_status`: always present for frontend state handling
|
|
- `items`: always an array
|
|
|
|
Required item fields:
|
|
|
|
- `ranking_position`
|
|
- `player_id`
|
|
- `player_name`
|
|
- `metric_value`
|
|
- `matches_considered`
|
|
- `kills`
|
|
- `deaths`
|
|
- `teamkills`
|
|
- `kd_ratio`
|
|
|
|
Weekly/monthly-only metadata:
|
|
|
|
- `window_start`
|
|
- `window_end`
|
|
- `window_kind`
|
|
- `window_label`
|
|
- `selection_reason`
|
|
|
|
Annual-only metadata:
|
|
|
|
- `year`
|
|
- `generated_at`
|
|
- `snapshot_limit`
|
|
- `effective_limit`
|
|
- `item_count`
|
|
|
|
## Timeframe-specific expectations
|
|
|
|
### Weekly
|
|
|
|
- Use the same weekly fallback rule already defined by the leaderboard read model.
|
|
- If current-week sample is insufficient, backend may serve previous-week data.
|
|
- Frontend must not reinterpret the window; it should display returned window metadata.
|
|
|
|
### Monthly
|
|
|
|
- Use the same current-month vs previous-month rule already defined by the leaderboard read model.
|
|
- Frontend must display the returned monthly window metadata exactly as served.
|
|
|
|
### Annual
|
|
|
|
- Read only from annual snapshots.
|
|
- `snapshot_status="ready"` with `items=[]` is a valid empty-ready state.
|
|
- `snapshot_status="missing"` is not a backend crash and must be rendered distinctly.
|
|
|
|
## UI State Contract
|
|
|
|
The Ranking page must support these states explicitly:
|
|
|
|
- `loading`: request in flight
|
|
- `backend offline`: `/health` unavailable or ranking request unreachable
|
|
- `no data`: successful response with `items=[]` in weekly/monthly
|
|
- `annual snapshot missing`: annual response with `snapshot_status="missing"`
|
|
- `unsupported metric`: client requested unsupported metric or backend returns 400 metric validation error
|
|
- `controlled error`: valid HTTP response with request validation error or unexpected backend failure
|
|
|
|
Expected rendering behavior:
|
|
|
|
- loading keeps previous table hidden or visually muted
|
|
- backend offline shows a clear retry-safe message
|
|
- no data keeps filters visible and explains that there are no rows for the active scope
|
|
- annual snapshot missing explains that the annual snapshot has not been generated yet
|
|
- unsupported metric does not silently downgrade to another metric
|
|
- controlled error does not break page navigation or filter controls
|
|
|
|
## Backend Behavior Requirements
|
|
|
|
- Preserve existing Stats endpoint compatibility.
|
|
- Do not expose public-scoreboard as the normal primary source for Ranking.
|
|
- Do not expand annual generation logic inside request handling.
|
|
- Keep server scope limited to active supported scopes: `all`, `comunidad-hispana-01`, `comunidad-hispana-02`.
|
|
- Do not surface Comunidad Hispana #03 in defaults, options or backend read scope.
|
|
|
|
## Frontend Integration Guidance
|
|
|
|
Recommended page split:
|
|
|
|
- `stats.html`: personal lookup and personal ranking context
|
|
- `ranking.html`: public top lists
|
|
|
|
Recommended safe reuse:
|
|
|
|
- backend availability chip pattern from `stats.js`
|
|
- existing tactical panel and card styling from `styles.css`
|
|
- simple select/button driven filtering similar to `historico.html`
|
|
|
|
Recommended minimal cross-links:
|
|
|
|
- `Ranking` page links to `Stats` with copy like "Buscar jugador"
|
|
- `Stats` page may link back to `Ranking` with copy like "Ver ranking global"
|
|
|
|
## Follow-up Task Suggestions
|
|
|
|
- Implement `GET /api/ranking` by adapting existing weekly/monthly leaderboard reads and annual snapshot reads.
|
|
- Build `frontend/ranking.html` and `frontend/assets/js/ranking.js` against this contract.
|
|
- Add a small regression script for Ranking endpoint and frontend state validation.
|