Module:ABVRS Points

From zunagi

Revision as of 21:31, 10 September 2026 by Lee G (talk | contribs)

Documentation for this module may be created at Module:ABVRS Points/doc

local p = {}

-- Base ranking points by finishing place (1st-16th), from the 2024/25 ABVRS Points Table
-- (https://zunagi.com/images/2024-25-ABVRS-Points-Table.pdf).
local base = {
	[1] = 30, [2] = 28, [3] = 25, [4] = 23, [5] = 21, [6] = 20,
	[7] = 19, [8] = 18, [9] = 17, [10] = 16.5, [11] = 16, [12] = 15.5,
	[13] = 15, [14] = 14.5, [15] = 14, [16] = 13.5,
}

-- Ladder of competition tiers, highest (-4) to lowest (6), with each tier's points
-- multiplier. -4 = FIVB World Championships, -3 = Beach Pro Tour Elite 16, -2 = Beach Pro
-- Tour Challenge/Asian Championships, -1 = Beach Pro Tour Futures/AVC Open/Australian
-- Championships (these four are all "No Star - International" in the PDF, differentiated
-- from each other by tournament name rather than star count), 0 = ABVT Elite (5 star),
-- 1 = ABVT Challenger/Pacific Games (4 star), 2 = Highest State (3 star), 3 = Competitive
-- State (2 star), 4 = Entry Level State (1 star), 5 = Local Competitive (no star),
-- 6 = Local Club Entry level (no star).
local multiplier = {
	[-4] = 80, [-3] = 70, [-2] = 50, [-1] = 30,
	[0] = 20, [1] = 12, [2] = 8, [3] = 5, [4] = 3, [5] = 2, [6] = 1,
}

-- Event rating -> ladder index that Division 1 starts at. Division 2 is one tier lower,
-- Division 3 two tiers lower, and so on.
--
-- "No Star - International" is only wired up to the Australian Championships tier (-1) for
-- now, since that's the only one of the four international tiers this calendar currently
-- needs (ABVC Australian Championships). If a future event needs one of the other three,
-- add it here the same way, e.g. ["no star international asian championships"] = -2.
local startIndex = {
	["4 star"] = 1, ["4"] = 1,
	["3 star"] = 2, ["3"] = 2,
	["2 star"] = 3, ["2"] = 3,
	["1 star"] = 4, ["1"] = 4,
	["no star"] = 5, ["none"] = 5, ["0"] = 5,
	["no star international"] = -1, ["international"] = -1,
}

-- Draw size -> number of "Place" rows to show.
local drawSizes = { ["12"] = 12, ["12 teams"] = 12, ["16"] = 16, ["16 teams"] = 16 }

-- Event rating -> the clean display label used in the subheading (e.g. "3-Star event with a
-- 12-team draw"). Keys must stay in sync with startIndex above.
local ratingLabel = {
	["4 star"] = "4-Star", ["4"] = "4-Star",
	["3 star"] = "3-Star", ["3"] = "3-Star",
	["2 star"] = "2-Star", ["2"] = "2-Star",
	["1 star"] = "1-Star", ["1"] = "1-Star",
	["no star"] = "No Star", ["none"] = "No Star", ["0"] = "No Star",
	["no star international"] = "No Star - International", ["international"] = "No Star - International",
}

local ordinal = {
	[1] = "1st", [2] = "2nd", [3] = "3rd", [4] = "4th", [5] = "5th", [6] = "6th",
	[7] = "7th", [8] = "8th", [9] = "9th", [10] = "10th", [11] = "11th", [12] = "12th",
	[13] = "13th", [14] = "14th", [15] = "15th", [16] = "16th",
}

local function trim(s)
	if not s then return nil end
	s = mw.text.trim(s)
	if s == "" then return nil end
	return s
end

-- Lowercase, and treat hyphens as spaces so "No Star - International" and "No Star
-- International" (and similar minor punctuation variants) match the same lookup key.
local function normalizeRating(s)
	s = string.lower(s)
	s = s:gsub("%-", " ")
	s = s:gsub("%s+", " ")
	return mw.text.trim(s)
end

-- Show "240" rather than "240.0", but keep the ".5" values (e.g. "82.5") as-is.
local function formatNumber(n)
	if n == math.floor(n) then
		return tostring(math.floor(n))
	end
	return tostring(n)
end

-- {{#invoke:ABVRS Points|table|rating=3 Star|draw_size=12|divisions=3}}
function p.table(frame)
	local args = frame.args

	local rating = trim(args.rating)
	local drawSizeRaw = trim(args.draw_size)
	local divisionsRaw = trim(args.divisions)

	-- All three hidden fields must be filled in, or nothing is shown.
	if not rating or not drawSizeRaw or not divisionsRaw then
		return ""
	end

	local start = startIndex[normalizeRating(rating)]
	local drawSize = drawSizes[string.lower(drawSizeRaw)]
	local divisions = tonumber(divisionsRaw)

	if not start or not drawSize or not divisions then
		return ""
	end

	divisions = math.floor(divisions)
	if divisions < 1 then
		return ""
	end

	local label = ratingLabel[normalizeRating(rating)] or rating

	local out = mw.html.create("")

	out:tag("div")
		:addClass("zunagi-points-heading")
		:css("font-size", "1.1em")
		:css("font-weight", "bold")
		:css("margin", "1em 0 0")
		:wikitext("Points on Offer")

	out:tag("div")
		:addClass("zunagi-points-subheading")
		:css("font-size", "0.8em")
		:css("font-weight", "bold")
		:css("color", "#666")
		:css("margin", "4px 0 12px 0")
		:wikitext(label .. " event with a " .. drawSize .. "-team draw")

	local tbl = out:tag("table"):addClass("wikitable zunagi-points-table")

	local headerRow = tbl:tag("tr")
	headerRow:tag("th"):css("padding", "6px 10px"):wikitext("Place")
	for d = 1, divisions do
		headerRow:tag("th"):css("padding", "6px 10px"):wikitext("Div " .. d)
	end

	for place = 1, drawSize do
		local row = tbl:tag("tr")
		row:tag("th"):css("padding", "6px 10px"):wikitext(ordinal[place] or (place .. "th"))
		for d = 1, divisions do
			local tier = start + d - 1
			local mult = multiplier[tier]
			local cell = row:tag("td")
				:css("text-align", "center")
				:css("padding", "6px 10px")
				:css("background-color", "#fff")
			if mult and base[place] then
				cell:wikitext(formatNumber(base[place] * mult))
			end
		end
	end

	return tostring(out)
end

return p