From e27e9e88215a27de7d8f97ba75d0f0be2b6ef80f Mon Sep 17 00:00:00 2001 From: vikingowl Date: Sun, 7 Dec 2025 18:42:44 +0100 Subject: [PATCH] feat: Integrate player meta stats, match metadata, and sitemap proxies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add player meta stats API endpoint with teammates, weapons, and map data - Display avg_rank badge on match cards and match detail page - Add tick rate and demo download improvements to match layout - Create sitemap proxy routes to backend for SEO - Document backend data limitations in transformers (rounds/weapons) - Fix 400 error: backend limits meta stats to max 10 items 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/lib/api/players.ts | 14 +- src/lib/api/transformers.ts | 6 + src/lib/api/transformers/roundsTransformer.ts | 19 +- .../api/transformers/weaponsTransformer.ts | 13 +- src/lib/components/match/MatchCard.svelte | 6 + src/lib/types/Match.ts | 8 + src/lib/types/Player.ts | 76 ++++++ src/lib/types/index.ts | 11 +- src/routes/match/[id]/+layout.svelte | 33 ++- src/routes/player/[id]/+page.svelte | 217 +++++++++++++++++- src/routes/player/[id]/+page.ts | 13 +- src/routes/sitemap.xml/+server.ts | 76 +++--- src/routes/sitemap/[id]/+server.ts | 52 +++++ 13 files changed, 480 insertions(+), 64 deletions(-) create mode 100644 src/routes/sitemap/[id]/+server.ts diff --git a/src/lib/api/players.ts b/src/lib/api/players.ts index ab30af8..2100034 100644 --- a/src/lib/api/players.ts +++ b/src/lib/api/players.ts @@ -1,6 +1,6 @@ import { apiClient } from './client'; import { parsePlayer } from '$lib/schemas'; -import type { Player, PlayerMeta, TrackPlayerResponse } from '$lib/types'; +import type { Player, PlayerMeta, PlayerMetaStats, TrackPlayerResponse } from '$lib/types'; import { transformPlayerProfile, type LegacyPlayerProfile } from './transformers'; /** @@ -85,6 +85,18 @@ export const playersAPI = { return playerMeta; }, + /** + * Get player aggregated meta stats from backend + * Uses pre-computed stats (cached 30 days) including teammates, weapons, maps + * @param steamId - Steam ID (uint64 as string to preserve precision) + * @param limit - Number of items per category (max 10, default: 4) + * @returns Player meta stats with teammates, weapons, map performance + */ + async getPlayerMetaStats(steamId: string, limit = 4): Promise { + const url = `/player/${steamId}/meta/${limit}`; + return apiClient.get(url); + }, + /** * Add player to tracking system * @param steamId - Steam ID (uint64 as string to preserve precision) diff --git a/src/lib/api/transformers.ts b/src/lib/api/transformers.ts index dcfe7bd..296ac64 100644 --- a/src/lib/api/transformers.ts +++ b/src/lib/api/transformers.ts @@ -55,6 +55,9 @@ export interface LegacyMatchDetail { vac: boolean; // NOT vac_present game_ban: boolean; // NOT gameban_present stats?: LegacyPlayerStats[]; // Player stats array + tick_rate?: number; // Server tick rate (64 or 128) + avg_rank?: number; // Average Premier rating (backend computed) + replay_url?: string; // Demo download URL (if < 30 days old) } /** @@ -219,6 +222,9 @@ export function transformMatchDetail(legacy: LegacyMatchDetail): Match { demo_parsed: legacy.parsed, vac_present: legacy.vac, gameban_present: legacy.game_ban, + tick_rate: legacy.tick_rate, + avg_rank: legacy.avg_rank, + replay_url: legacy.replay_url, players: legacy.stats?.map(transformPlayerStats) }; } diff --git a/src/lib/api/transformers/roundsTransformer.ts b/src/lib/api/transformers/roundsTransformer.ts index fce813a..1002a39 100644 --- a/src/lib/api/transformers/roundsTransformer.ts +++ b/src/lib/api/transformers/roundsTransformer.ts @@ -3,10 +3,15 @@ import type { MatchRoundsResponse, RoundDetail, RoundStats, Match } from '$lib/t /** * Transform raw rounds API response into structured format - * @param rawData - Raw API response + * + * NOTE: The backend API only provides economy data (bank/equipment/spent) per round. + * Round winners and win reasons are not currently stored in the database. + * To add this data would require backend changes to the demo parser. + * + * @param rawData - Raw API response containing economy data per round * @param matchId - Match ID - * @param match - Match data with player information - * @returns Structured rounds data + * @param match - Match data with player information and final score + * @returns Structured rounds data with economy info (winner/win_reason unavailable) */ export function transformRoundsResponse( rawData: RoundsAPIResponse, @@ -15,7 +20,7 @@ export function transformRoundsResponse( ): MatchRoundsResponse { const rounds: RoundDetail[] = []; - // Create player ID to team mapping + // Create player ID to team mapping for potential future use const playerTeamMap = new Map(); if (match?.players) { for (const player of match.players) { @@ -47,8 +52,10 @@ export function transformRoundsResponse( rounds.push({ round: roundNum + 1, - winner: 0, // TODO: Determine winner from data if available - win_reason: '', // TODO: Determine win reason if available + // Round winner data not available from backend API + // Would require demo parser changes to store RoundEnd event winners + winner: 0, + win_reason: '', players }); } diff --git a/src/lib/api/transformers/weaponsTransformer.ts b/src/lib/api/transformers/weaponsTransformer.ts index 2070d6a..db25e7d 100644 --- a/src/lib/api/transformers/weaponsTransformer.ts +++ b/src/lib/api/transformers/weaponsTransformer.ts @@ -3,10 +3,15 @@ import type { MatchWeaponsResponse, PlayerWeaponStats, WeaponStats, Match } from /** * Transform raw weapons API response into structured format - * @param rawData - Raw API response + * + * NOTE: The backend API provides hit/damage data per weapon but not kill counts. + * Kill tracking would require the demo parser to correlate damage events with + * player death events. Currently only aggregated damage and hit group data is available. + * + * @param rawData - Raw API response containing damage/hit data per weapon * @param matchId - Match ID * @param match - Match data with player information - * @returns Structured weapons data + * @returns Structured weapons data with damage stats (kills unavailable) */ export function transformWeaponsResponse( rawData: WeaponsAPIResponse, @@ -77,7 +82,9 @@ export function transformWeaponsResponse( weapon_stats.push({ eq_type: eqType, weapon_name: rawData.equipment_map[String(eqType)] || `Weapon ${eqType}`, - kills: 0, // TODO: Calculate kills if needed + // Kill data not available - API only provides hit/damage events + // Would require backend changes to correlate damage with death events + kills: 0, damage: stats.damage, hits: stats.hits, hit_groups: hitGroupCounts, diff --git a/src/lib/components/match/MatchCard.svelte b/src/lib/components/match/MatchCard.svelte index cd64f29..ee1770a 100644 --- a/src/lib/components/match/MatchCard.svelte +++ b/src/lib/components/match/MatchCard.svelte @@ -3,6 +3,7 @@ import type { MatchListItem } from '$lib/types'; import { storeMatchesState, type FilterState } from '$lib/utils/navigation'; import { getMapBackground, formatMapName } from '$lib/utils/mapAssets'; + import PremierRatingBadge from '$lib/components/ui/PremierRatingBadge.svelte'; interface Props { match: MatchListItem; @@ -87,6 +88,11 @@
+ {#if match.avg_rank && match.avg_rank > 0} +
+ +
+ {/if} {#if match.player_count} ; + /** Weapon damage stats sorted by damage */ + weapon_dmg?: WeaponDamageStats[]; + /** Win rate per map (map name -> rate 0-1) */ + win_maps?: Record; + /** Tie rate per map (map name -> rate 0-1) */ + tie_maps?: Record; + /** Total matches per map (map name -> count) */ + total_maps?: Record; +} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index f0d22f3..23edee5 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -11,7 +11,16 @@ export type { ChatAPIResponse } from './api/ChatAPIResponse'; export type { Match, MatchListItem, MatchPlayer, MatchWithPlayers } from './Match'; // Player types -export type { Player, PlayerMatch, PlayerMeta, PlayerProfile } from './Player'; +export type { + Player, + PlayerMatch, + PlayerMeta, + PlayerProfile, + PlayerMetaStats, + TeammateStats, + WeaponDamageStats, + MapStats +} from './Player'; // Round statistics types export type { diff --git a/src/routes/match/[id]/+layout.svelte b/src/routes/match/[id]/+layout.svelte index 3793f65..e7d56d9 100644 --- a/src/routes/match/[id]/+layout.svelte +++ b/src/routes/match/[id]/+layout.svelte @@ -1,8 +1,9 @@ @@ -95,11 +105,13 @@ {mapName}
- {#if match.demo_parsed && match.share_code} + {#if canDownloadDemo}