From 29e107ecb1d1e86bb62fdac8b5a463b1a1c41fd6 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Fri, 12 Jun 2026 17:59:01 +0200 Subject: [PATCH] perf: project imported matches to slim structs in standings import Co-Authored-By: Claude Fable 5 --- lua/wikis/commons/Standings/Parse/Lpdb.lua | 39 ++++++--- lua/wikis/commons/Standings/Table.lua | 86 +++++++++++++++++-- .../Standings/Tiebreaker/Interface.lua | 2 +- 3 files changed, 108 insertions(+), 19 deletions(-) diff --git a/lua/wikis/commons/Standings/Parse/Lpdb.lua b/lua/wikis/commons/Standings/Parse/Lpdb.lua index 9d81614006a..330c71a8897 100644 --- a/lua/wikis/commons/Standings/Parse/Lpdb.lua +++ b/lua/wikis/commons/Standings/Parse/Lpdb.lua @@ -23,6 +23,15 @@ local ColumnName = Condition.ColumnName local StandingsParseLpdb = {} +---Slim match struct carrying only the fields consumed by standings tiebreakers and import. +---Dropped: bracketData, extradata, full game payloads. +---@class StandingsImportMatch +---@field matchId string +---@field finished boolean +---@field winner integer? +---@field opponents standardOpponent[] -- by reference from matchFromRecord +---@field games {winner: integer?, status: string?, scores: table?}[] + ---@param rounds {roundNumber: integer, matches: string[]}[] ---@param scoreMapper fun(opponent: match2opponent): number|nil ---@return StandingTableOpponentData[] @@ -59,10 +68,21 @@ function StandingsParseLpdb.importFromMatches(rounds, scoreMapper) { conditions = tostring(conditions), }, - function(match2) - local roundNumbers = matchIdToRound[match2.match2id] + function(record) + local match2 = MatchGroupUtil.matchFromRecord(record) + ---@type StandingsImportMatch + local slimMatch = { + matchId = match2.matchId, + finished = match2.finished, + winner = match2.winner, + opponents = match2.opponents, + games = Array.map(match2.games, function(game) + return {winner = game.winner, status = game.status, scores = game.scores} + end), + } + local roundNumbers = matchIdToRound[record.match2id] Array.forEach(roundNumbers, function(roundNumber) - StandingsParseLpdb.parseMatch(roundNumber, match2, opponents, scoreMapper, #rounds) + StandingsParseLpdb.parseMatch(roundNumber, slimMatch, opponents, scoreMapper, #rounds) end) end ) @@ -114,13 +134,12 @@ function StandingsParseLpdb.newOpponent(opponentData, maxRounds) end ---@param roundNumber integer ----@param match match2 +---@param slimMatch StandingsImportMatch ---@param opponents StandingTableOpponentData[] ---@param scoreMapper fun(opponent: standardOpponent): number? ---@param maxRounds integer -function StandingsParseLpdb.parseMatch(roundNumber, match, opponents, scoreMapper, maxRounds) - local match2 = MatchGroupUtil.matchFromRecord(match) - Array.forEach(match2.opponents, function(opponent) +function StandingsParseLpdb.parseMatch(roundNumber, slimMatch, opponents, scoreMapper, maxRounds) + Array.forEach(slimMatch.opponents, function(opponent) ---Find matching opponent local standingsOpponentData = Array.find(opponents, function(opponentData) return Opponent.same(opponentData.opponent, opponent) @@ -137,11 +156,11 @@ function StandingsParseLpdb.parseMatch(roundNumber, match, opponents, scoreMappe opponentRoundData.scoreboard.points = (opponentRoundData.scoreboard.points or 0) + points end opponentRoundData.specialstatus = '' - opponentRoundData.match = match2 - if not match2.finished then + opponentRoundData.match = slimMatch + if not slimMatch.finished then return end - local matchResult = match2.winner == 0 and 'd' or opponent.placement == 1 and 'w' or 'l' + local matchResult = slimMatch.winner == 0 and 'd' or opponent.placement == 1 and 'w' or 'l' opponentRoundData.scoreboard.match[matchResult] = (opponentRoundData.scoreboard.match[matchResult] or 0) + 1 end) end diff --git a/lua/wikis/commons/Standings/Table.lua b/lua/wikis/commons/Standings/Table.lua index d23c6f11285..068b0ade285 100644 --- a/lua/wikis/commons/Standings/Table.lua +++ b/lua/wikis/commons/Standings/Table.lua @@ -11,7 +11,6 @@ local Arguments = Lua.import('Module:Arguments') local Array = Lua.import('Module:Array') local FnUtil = Lua.import('Module:FnUtil') local Logic = Lua.import('Module:Logic') -local Table = Lua.import('Module:Table') local StandingsParseWiki = Lua.import('Module:Standings/Parse/Wiki') local StandingsParseLpdb = Lua.import('Module:Standings/Parse/Lpdb') @@ -32,7 +31,7 @@ local StandingsTable = {} ---@class StandingTableOpponentData ---@field rounds {tiebreakerPoints: number?, specialstatus: string, scoreboard: Scoreboard?, ----match: MatchGroupUtilMatch?, matches: MatchGroupUtilMatch[], matchId: string}[]? +---match: StandingsImportMatch?, matches: StandingsImportMatch[], matchId: string}[]? ---@field opponent standardOpponent ---@field startingPoints number? @@ -73,6 +72,27 @@ function StandingsTable.fromTemplate(frame) return StandingsDisplay{pageName = mw.title.getCurrentTitle().text, standingsIndex = standingsTable.standingsindex} end +---Merge a single imported opponent round into a manual opponent round. +---Manual values take priority; imported-only fields (matches, matchId, scoreboard.match) survive. +---@param importedRound table +---@param manualRound table +---@return table +local function mergeRound(importedRound, manualRound) + local importedScoreboard = importedRound.scoreboard or {} + local manualScoreboard = manualRound.scoreboard or {} + return { + scoreboard = { + points = manualScoreboard.points ~= nil and manualScoreboard.points or importedScoreboard.points, + match = importedScoreboard.match, + }, + specialstatus = manualRound.specialstatus ~= nil and manualRound.specialstatus or importedRound.specialstatus, + tiebreakerPoints = manualRound.tiebreakerPoints ~= nil + and manualRound.tiebreakerPoints or importedRound.tiebreakerPoints, + matches = importedRound.matches, + matchId = importedRound.matchId, + } +end + ---@param manualOpponents StandingTableOpponentData[] ---@param importedOpponents StandingTableOpponentData[] ---@param addNewOpponents boolean @@ -81,21 +101,71 @@ function StandingsTable.mergeOpponentsData(manualOpponents, importedOpponents, a --- Add all manual opponents to the new opponents list local newOpponents = Array.map(manualOpponents, FnUtil.identity) + --- Build a name-keyed index of newOpponents for O(1) lookup. + --- For team opponents a renamed-team fallback (Opponent.same) is used on miss. + local opponentIndex = {} + Array.forEach(newOpponents, function(opponentData, idx) + local name = Opponent.toName(opponentData.opponent) + if name then + opponentIndex[name] = idx + end + end) + --- Find all imported opponents Array.forEach(importedOpponents, function(importedOpponent) - --- Find the matching manual opponent - local manualOpponentId = Array.indexOf(newOpponents, function(manualOpponent) - return Opponent.same(manualOpponent.opponent, importedOpponent.opponent) - end) + local importedName = Opponent.toName(importedOpponent.opponent) + local manualOpponentId = importedName and opponentIndex[importedName] or 0 + + --- On miss, do one fallback linear scan (handles renamed teams via historicaltemplate) + if manualOpponentId == 0 then + manualOpponentId = Array.indexOf(newOpponents, function(manualOpponent) + return Opponent.same(manualOpponent.opponent, importedOpponent.opponent) + end) + --- Alias the name so future lookups hit the fast path + if manualOpponentId ~= 0 and importedName then + opponentIndex[importedName] = manualOpponentId + end + end + --- If there isn't one, means this is a new opponent if manualOpponentId == 0 then if addNewOpponents then table.insert(newOpponents, importedOpponent) + local name = Opponent.toName(importedOpponent.opponent) + if name then + opponentIndex[name] = #newOpponents + end end return end - --- Manual data has priority over imported data - newOpponents[manualOpponentId] = Table.deepMerge(importedOpponent, newOpponents[manualOpponentId]) + + --- Manual data has priority over imported data; build a new merged opponent table + local manualOpponent = newOpponents[manualOpponentId] + local importedRounds = importedOpponent.rounds or {} + local manualRounds = manualOpponent.rounds or {} + + -- Determine the union of round indices present on either side + local maxRoundIndex = math.max(#importedRounds, #manualRounds) + local mergedRounds = {} + for i = 1, maxRoundIndex do + local importedRound = importedRounds[i] + local manualRound = manualRounds[i] + if importedRound and manualRound then + mergedRounds[i] = mergeRound(importedRound, manualRound) + elseif manualRound then + mergedRounds[i] = manualRound + else + mergedRounds[i] = importedRound + end + end + + newOpponents[manualOpponentId] = { + opponent = manualOpponent.opponent, + startingPoints = manualOpponent.startingPoints ~= nil + and manualOpponent.startingPoints + or importedOpponent.startingPoints, + rounds = mergedRounds, + } end) return newOpponents diff --git a/lua/wikis/commons/Standings/Tiebreaker/Interface.lua b/lua/wikis/commons/Standings/Tiebreaker/Interface.lua index ab2ceabb6e1..208f97f3f7a 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Interface.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Interface.lua @@ -9,7 +9,7 @@ local Lua = require('Module:Lua') local Class = Lua.import('Module:Class') ----@alias TiebreakerOpponent {opponent: standardOpponent, points: number, matches: MatchGroupUtilMatch[], +---@alias TiebreakerOpponent {opponent: standardOpponent, points: number, matches: StandingsImportMatch[], ---match: {w: integer, d: integer, l:integer}, extradata: table} ---@class StandingsTiebreaker