Files
comunidadhll/docs/stats-section-functional-plan.md
2026-06-08 18:38:08 +02:00

9.7 KiB

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

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:

{
  "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

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:

{
  "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

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:

{
  "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
  • 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