import type Database from 'better-sqlite3'; import type { LeaderboardPlayerCount, PersonalBestMetric, PersonalBestRecords, RecordEvidence } from '../../leaderboard.js'; import { MIN_RECORDED_ROUND } from '../../match-history.js'; interface RecordRequest extends Record { guid: string; game?: string; map?: string; players?: LeaderboardPlayerCount; } /** Fetch evidence only for requested players and scopes, never rank the whole fleet's history. */ export function getPersonalBestRecords( handle: Database.Database, requests: RecordRequest[] ): PersonalBestRecords[] { const result: PersonalBestRecords[] = []; // Page requests are at most 100 players; chunk unusually large profile map collections too. for (let offset = 0; offset < requests.length; offset += 100) { const batch = requests.slice(offset, offset + 100); const parameters = batch.flatMap((request, index) => [ index, request.guid, request.game ?? null, request.map ?? null, request.players ?? null, request.highestRound, request.bestKills, request.bestScore ]); const rows = handle .prepare( `WITH requested (requestId, guid, game, map, players, highestRound, bestKills, bestScore) AS ( VALUES ${batch.map(() => '(?, ?, ?, ?, ?, ?, ?, ?)').join(', ')} ), candidates AS ( SELECT r.requestId, m.id AS matchId, m.game, m.map_key AS mapKey, m.map_name AS mapName, m.server, m.started_at AS startedAt, m.ended_at AS endedAt, m.start_observed AS startObserved, mp.first_observed_round AS firstObservedRound, mp.present_at_start AS presentAtStart, COALESCE(m.ended_at, m.last_received_at) AS completionOrder, mp.highest_round AS highestRound, mp.kills AS bestKills, mp.best_score AS bestScore, r.highestRound AS targetRound, r.bestKills AS targetKills, r.bestScore AS targetScore FROM requested r JOIN leaderboard_match_players mp ON mp.guid = r.guid JOIN leaderboard_matches m ON m.id = mp.match_id WHERE m.game IN ('t5', 't6') AND m.status = 'completed' AND mp.has_stats = 1 AND m.highest_round >= ${MIN_RECORDED_ROUND} AND mp.highest_round >= ${MIN_RECORDED_ROUND} AND (r.game IS NULL OR m.game = r.game) AND (r.map IS NULL OR m.map_key = r.map) AND (r.players IS NULL OR m.start_players = r.players) AND (mp.highest_round = r.highestRound OR mp.kills = r.bestKills OR mp.best_score = r.bestScore) ), valuesByMetric AS ( SELECT *, 'highestRound' AS metric, highestRound AS value FROM candidates WHERE highestRound = targetRound UNION ALL SELECT *, 'bestKills', bestKills FROM candidates WHERE bestKills = targetKills UNION ALL SELECT *, 'bestScore', bestScore FROM candidates WHERE bestScore = targetScore ), ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY requestId, metric ORDER BY completionOrder, matchId) AS choice FROM valuesByMetric ) SELECT requestId, metric, value, matchId, game, mapKey, mapName, server, startedAt, endedAt, startObserved, firstObservedRound, presentAtStart FROM ranked WHERE choice = 1` ) .all(...parameters) as Array< Omit & { requestId: number; metric: PersonalBestMetric; startObserved: number; presentAtStart: number | null; } >; const records = batch.map(() => ({}) as PersonalBestRecords); for (const { requestId, metric, startObserved, presentAtStart, ...record } of rows) { records[requestId][metric] = { ...record, startObserved: Boolean(startObserved), presentAtStart: presentAtStart === null ? null : Boolean(presentAtStart) }; } result.push(...records); } return result; }