Module:PlayoffStats
From elfpedia.eu
Documentation for this module may be created at Module:PlayoffStats/doc
-- Module:PlayoffStats
local M = {}
-- Load the Standingsv2 module
local Standingsv2 = require('Module:Standingsv2')
-- Function to calculate SOV and SOS for all teams
-- Uses functions from Module:Standingsv2 to get game data and team records.
function M.calculateAdvancedPlayoffStats(season)
-- 1. Retrieve all games for the season (Cargo query via Standingsv2)
local allSeasonGames = Standingsv2.getGameData(season)
if not allSeasonGames or type(allSeasonGames) ~= 'table' or #allSeasonGames == 0 then
-- Standingsv2.getGameData can return empty tables if no games are found
-- Or we would have to catch the error from Standingsv2.getGameData here, if it throws one.
return nil, "Error: No or invalid game data received for season " .. season .. " from Module:Standingsv2."
end
-- 2. Calculate current statistics for ALL teams based on the loaded games
-- Standingsv2.calculateTeamStats should provide W/L/PF/PA as well as the 'GamesPlayed' history for each team.
local currentTeamStats = Standingsv2.calculateTeamStats(allSeasonGames)
if not currentTeamStats or type(currentTeamStats) ~= 'table' or next(currentTeamStats) == nil then
return nil, "Error: No or invalid team statistics received from Module:Standingsv2."
end
local teamStats = {} -- Result table: TeamName -> {wins, losses, sov, sos}
-- Initialize teamStats with basic W/L and copy for SOV/SOS calculation
for teamName, stats_from_standings in pairs(currentTeamStats) do
teamStats[teamName] = {
wins = stats_from_standings.W, -- Note: Standingsv2 uses 'W' and 'L'
losses = stats_from_standings.L,
-- Temporary storage for SOV/SOS calculations
defeatedOpponentWins = 0,
defeatedOpponentLosses = 0,
allScheduleOpponentWins = 0,
allScheduleOpponentLosses = 0,
-- Reference to the team's 'GamesPlayed' history from Standingsv2
gamesPlayedHistory = stats_from_standings.GamesPlayed or {}
}
end
-- Iteration: Collect data for SOV and SOS by iterating through each team's 'gamesPlayedHistory'
-- and fetching the W/L records of opponents from 'currentTeamStats'.
for teamName, stats in pairs(teamStats) do
for _, gameInfo in ipairs(stats.gamesPlayedHistory) do
local opponentName = gameInfo.opponent
local opponentStats = currentTeamStats[opponentName] -- Opponent statistics from the overall overview
if opponentStats then -- Ensure opponent stats exist
-- For SOS (all opponents on this team's schedule, since 'gamesPlayedHistory' includes all games)
teamStats[teamName].allScheduleOpponentWins = teamStats[teamName].allScheduleOpponentWins + opponentStats.W
teamStats[teamName].allScheduleOpponentLosses = teamStats[teamName].allScheduleOpponentLosses + opponentStats.L
-- For SOV (only defeated opponents)
if gameInfo.is_win then
teamStats[teamName].defeatedOpponentWins = teamStats[teamName].defeatedOpponentWins + opponentStats.W
teamStats[teamName].defeatedOpponentLosses = teamStats[teamName].defeatedOpponentLosses + opponentStats.L
end
end
end
end
-- Second iteration: Calculate final SOV and SOS values
for teamName, stats in pairs(teamStats) do
-- SOV calculation
local sov_denom = stats.defeatedOpponentWins + stats.defeatedOpponentLosses
stats.sov = (sov_denom > 0) and (stats.defeatedOpponentWins / sov_denom) or 0
-- SOS calculation
local sos_denom = stats.allScheduleOpponentWins + stats.allScheduleOpponentLosses
stats.sos = (sos_denom > 0) and (stats.allScheduleOpponentWins / sos_denom) or 0
end
return teamStats
end
-- Function to generate the Wikitable for SOV & SOS
function M.generateSOVSOSTable(frame)
local args = frame:getParent().args
local season = tonumber(args.season) or 2025
local advancedStats, errorMsg = M.calculateAdvancedPlayoffStats(season)
if not advancedStats then
return "<p>" .. errorMsg .. "</p>"
end
-- Sort teams for the table (e.g., by Win%, then SOV, then SOS)
local sortedTeams = {}
for teamName, stats in pairs(advancedStats) do
-- Only show teams with at least one game in the season
-- (Optional: Also display teams with 0-0 record, if desired)
if stats.wins + stats.losses > 0 then
table.insert(sortedTeams, {Name = teamName, Stats = stats})
end
end
table.sort(sortedTeams, function(a, b)
local win_a = (a.Stats.wins + a.Stats.losses > 0) and (a.Stats.wins / (a.Stats.wins + a.Stats.losses)) or 0
local win_b = (b.Stats.wins + b.Stats.losses > 0) and (b.Stats.wins / (b.Stats.wins + b.Stats.losses)) or 0
-- Primary sort: Win% descending
if win_a ~= win_b then return win_a > win_b end
-- Secondary sort: SOV descending
if a.Stats.sov ~= b.Stats.sov then return a.Stats.sov > b.Stats.sov end
-- Tertiary sort: SOS descending
return a.Stats.sos > b.Stats.sos
end)
local output = '{| class="wikitable sortable"\n'
output = output .. '|+ SOV & SOS Overview - Season ' .. season .. '\n'
output = output .. '! Team !! Wins !! Losses !! Win% !! SOV !! SOS\n'
output = output .. '|-\n'
for _, teamInfo in ipairs(sortedTeams) do
local teamName = teamInfo.Name
local stats = teamInfo.Stats
local win_percent = (stats.wins + stats.losses > 0) and string.format("%.3f", stats.wins / (stats.wins + stats.losses)) or "0.000"
local sov_formatted = string.format("%.3f", stats.sov)
local sos_formatted = string.format("%.3f", stats.sos)
output = output .. '| [[ ' .. teamName .. ' ]] || ' .. stats.wins .. ' || ' .. stats.losses .. ' || ' .. win_percent .. ' || ' .. sov_formatted .. ' || ' .. sos_formatted .. '\n'
output = output .. '|-\n'
end
output = output .. '|}'
return output
end
return M