Module:Mayor

local loader = require('Module:Loader') local getArgs = require('Module:Arguments').getArgs

local string, table, yesno, _ = loader.require('String', 'Table', 'Yesno', 'LibraryUtil') local mayorData, eDT = loader.loadData('Mayor/Data', 'Mayor/Elections')

local electionsData = eDT.elections

local p = {}

local curTitle = mw.title.getCurrentTitle local crown = 'https://static.wikia.nocookie.net/mcchampionship/images/3/39/WinnerCrown.png/revision/latest/scale-to-width-down/18?cb=20210922151946' local arrowSymbol = '➤' local infosymbol = 'ⓘ'

local function getClosestYear for i = table.length(electionsData), 1, -1 do		local c = electionsData[i].control or '' if not (c:match('in%-progress') or c:match('collapsedown')) then return i		end end end

function p.getMayorLink(mayor) return (''):format(mayorData.mayors[mayor] and mayorData.mayors[mayor].link or mayor, mayor) end

-- For Module:Infobox/Mechanics function p._mayorPerksTable(mayor) local dt = table.deepCopy(mayorData.mayors[mayor], true) dt = dt.perks_listed or dt.perks for _, v in ipairs(dt) do		v[1] = mayorData.linkedPerkNames[v[1]] or v[1] end return dt end

-- For Template:Current Mayor function p.getCurrentMayor(frame) local args = getArgs(frame) local year = getClosestYear local ret if args[1] == 'e' then return ' ' .. arrowSymbol .. ' View Current Mayor/Election ' else local stats = p._getElectionStats(year) local m = stats[1].name if args[1] == 'r' then ret = m		elseif args[1] == 'l' then ret = mayorData.mayors[m] and mayorData.mayors[m].link or m		elseif args[1] == 'p' then ret = ('\'\'\'Perks: \'\'\' %s'):format(p.generatePerks(nil, year)) else local d = mayorData.mayors[m] ret = ('%s'):format(d and d.link or m, m)		end end return mw.getCurrentFrame:preprocess(ret) end

function p.specialNextParticipate( frame ) local args = getArgs(frame) return p._specialNextParticipate(args[1]) end

function p._specialNextParticipate(name) local mayordt = mayorData.mayors[name] local nSpecial = table.length(table.filterNamed(mayorData.mayors, function (k, v)		return v.type == 'special' end)) local currYear = 1 + math.floor((os.time(os.date("!*t")) - 1560275700) / 446400) local k = math.floor(currYear / 8) local increment = ((mayordt.special_order + nSpecial) - (k % nSpecial)) % nSpecial return (k + ((increment == 0 and currYear % 8 > 0) and 3 or increment)) * 8 end

-- For Template:MayorPerks function p.mayorPerks(frame) local args = getArgs(frame) local name = args[1] or curTitle.text local mayordt = mayorData.mayors[name] local headtext = args.headText or args.headtext headtext = headtext == 'none' and '' or headtext if mayordt.type == 'special' then headtext = headtext or ('Candidate %s is a special candidate, and will run the election according to the Special Candidates Election Cycle. This candidate is \'\'speculated to\'\' run for mayor next time in the election of with all of the  listed below. '):format(name, p._specialNextParticipate(name), table.length(mayordt.perks)) else headtext = headtext or ('Candidate %s may run for election with any of the listed below. This candidate can offer  up to . When this candidate is elected, the next time they join an election, they will run with . If they lost, the next time they join an election, unless they already offer all possible perks, they will have  to gain . '):format(name, table.length(mayordt.perks)) end local txt = table.concat { headtext, args[2] or '', }	return mw.getCurrentFrame:preprocess(p._mayorPerks(name, txt)) end

function p._mayorPerks(mayor, prependText) local function insertperks(t, perksTb, frontdent) for _, info in ipairs(perksTb) do			table.push(t, ('%s %s : %s'):format( frontdent, mayorData.linkedPerkNames[info[1]] or info[1], type(info[2]) == 'table' and '' or info[2] ))			if type(info[2]) == 'table' then for _, item in ipairs(info[2]) do					table.push(t, ('%s* %s'):format(frontdent, item)) end end end end local dt = mayorData.mayors[mayor] local lines_ = {} local frontdent = dt.perks_listed and '**#' or '*#' if dt.perks_listed then table.push(lines_, '**\'\'\'Listed Perks\'\'\':') insertperks(lines_, dt.perks_listed, frontdent) table.push(lines_, ('**\'\'\'Actual Perks\'\'\': \'\'(hidden until %s is elected)\'\''):format(mayor)) end insertperks(lines_, dt.perks, frontdent) return table.concat({		prependText or '',		('*\'\'\'%s\'\'\' %s'):format( p.getMayorLink(mayor), dt.aka or '' ),		unpack(lines_)	}, '\n') end

-- For Template:MayorList function p.mayorList(frame) local args = getArgs(frame) return mw.getCurrentFrame:preprocess(p._mayorList(args[1])) end

function p._mayorList(_type) local mayors = table.deepCopy(mayorData.mayors, true) if _type then mayors = table.filterNamed(mayors, function(k, v)			return v.type == string.lower(_type)		end) end local paragraphs = {} for k, v in table.sortedPairs(mayors) do		table.push(paragraphs, p._mayorPerks(k)) end return table.concat(paragraphs, '\n') end

-- For Template:ElectionTable function p.electionTable(frame) return mw.getCurrentFrame:preprocess(p._electionTable) end

function p.generatePerks(mayor, year) year = year or getClosestYear local function each(t, i, title, text) text = type(text) == 'table' and table.map(table.deepCopy(text, true), function(d)			return '— ' .. d		end) or { text } table.push(t, string.wrapHtml{			i,			' ', {				class = 'minetip gemstone-slot',				style = { ['color'] = 'white' },				['data-minetip-title'] = '&d' .. title,				['data-minetip-text'] = table.concat(table.map(text, function(tx)					return string.gsubAll(tx, '{', '&#123;', '}', '&#125;', '.*', '')				end), '/'),			}		}) end local stats, perks = p._getElectionStats(year), nil for _, v in ipairs(stats) do		if v.name == mayor then perks = v.perks end end mayor = mayor or stats[1].name perks = perks or stats[1].perks if not perks then error('No perks information for Mayor ' .. mayor .. ' in year ' .. year .. ' election') end local allperks, t = mayorData.mayors[mayor].perks, {} if perks:match('%*') then for i = 1, table.xlength(allperks, false) do			if not allperks[i] then error(('Perk %s of "%s" not found.'):format(i, mayor)) end if not allperks[i] then break end each(t, i, allperks[i][1], allperks[i][3] ~= '' and allperks[i][3] or allperks[i][2]) end else for _, v in ipairs(string.split(perks, '')) do			v = tonumber(v) each(t, v, allperks[v][1], allperks[v][3] ~= '' and allperks[v][3] or allperks[v][2]) end end return table.concat(t, ' ') end

-- Helper function -- Returns ordered election stats with processed information -- Returns empty table if no relevant information function p._getElectionStats(year) local election = electionsData[tonumber(year)] local ret = {} if election then local c = election.control or '' if election and not c:match('novote') and not c:match('collapsedown') then if election.result or election.data then local tot = 0 local result = election.result or table.mapNamed(election.data, function(k, v)					return v.votes				end) for _, v in pairs(result) do					tot = tot + v				end for k, v in table.sortedPairsByValue(result, function(a, b)					return a > b				end) do					table.push(ret, table.merge({}, (election.data and election.data[k] or {}), { name = k,						votes = v,						percent = v / tot * 100, }))				end ret.total_votes = tot end if election.result or election.mayor then ret[1] = ret[1] or {} ret[1].name = ret[1].name or election.mayor ret[1].votes = ret[1].votes or election.votes ret[1].perks = ret[1].perks or election.perks ret[1].percent = ret[1].percent or election.percent end end if c:match('novote') and election.mayor then ret[1] = { name = election.mayor, votes = election.votes, }		end end return ret end

function p._electionTable local dt, rows = electionsData, {} local _date, _year = {}, {} for i = table.length(dt), 1, -1 do		local item = dt[i] local c = item.control or '' if not c:match('in%-progress') then table.push(_date, item.date) table.push(_year, i)			if not c:match('collapsedown') then local y2, y1 = _year[1], _year[#_year] local y = y2 == y1 and y2 or ('%s-%s'):format(y1, y2) local button = item.ui and (' &#91;UI&#93; '):format(y1) or '' local stats = p._getElectionStats(i) local _mayor, _votes, _perks, _other if #stats > 0 then local mayorName = stats[1].name if not mayorName then error(i) end _mayor = p.getMayorLink(mayorName) _votes = stats[1].percent and ('%s%s'):format(						(''):format(stats[1].percent),						stats[1].votes and (' '):format(stats[1].votes)					) or nil _perks = stats[1].perks or item.perks if _perks then if _perks:match('%?') then _perks = '' elseif mayorName then _perks = p.generatePerks(mayorName, i)						else _perks = 'Perk ' .. table.concat(string.split(_perks, ''), ', ') end local caption = stats[1].note or item.note _perks = caption and ('%s %s '):format(_perks, caption) or _perks end _other = {} for j = 2, #stats do						table.push(_other, (' %s: %s %s '):format( p.getMayorLink(stats[j].name), (''):format(stats[j].percent), (''):format(stats[j].votes) ))					end _other = #_other > 0 and table.concat(_other, ' ') or nil end table.push(rows, {					date = table.concat(_date, ' '),					year = y .. button,					electedY = tonumber(y) and (y + 1),					mayor = _mayor,					votes = _votes,					perks = _perks,					other = _other,					control = c,				}) _date, _year = {}, {} end end end local wikitable = mw.html.create('table'):addClass('wikitable') wikitable:tag('tr'):tag('th'):wikitext('Election Year'):done :tag('th'):wikitext('Term Began on'):done :tag('th'):wikitext('Mayor'):done :tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Perks'):done :tag('th'):wikitext('Other Candidates'):done local function g(val, control) return val or (control:match('novote') and  or ) end for _, v in ipairs(rows) do		wikitable:tag('tr'):tag('th'):wikitext(v.year):done :tag('td'):wikitext((v.date or '??') .. '*' .. (v.electedY and ' Year ' .. v.electedY or '')):done :tag('td'):wikitext(g(v.mayor, v.control)):done :tag('td'):wikitext(g(v.votes, v.control)):done :tag('td'):wikitext(g(v.perks, v.control)):done :tag('td'):wikitext(g(v.other, v.control)):done end return tostring(wikitable) end

function p.mayorTooltip(name, mode, opt) opt = opt or {} local nums, col, ago, note = opt.nums, opt.col or 'd', opt.ago, opt.note local m = mayorData.mayors[name] mode = ('%s %s'):format(m.type, mode or '') local info = {} local perksref = table.deepCopy(mode:match('candidate') and m.perks_listed or m.perks, true) for _, perk in ipairs(nums and table.map(string.split(tostring(nums), ''), function(i) return perksref[tonumber(i)] end) or perksref) do		local desc = perk[3] ~= '' and perk[3] or perk[2] desc = type(desc) == 'table' and table.map(desc, function(d)			return '— ' .. d		end) or { desc } desc = string.gsubAll(table.concat(desc, '/'), '{', '&#123;', '}', '&#125;', '%|', '&#124;', '.*', '') -- Foxy event name replacement on tooltip if name == 'Foxy' and note then local c = note:match('[Ff]ishing') and 'b' or '6' local repl = string.gsub(string._ucfirst(note), '(.-) (.*)', ('&%s%%1/&%s%%2'):format(c, c)) desc = string.gsub(desc, '&6&#60;Event/&6name&#62;', repl) end table.push(info, ('&%s%s/&r%s'):format( col, perk[1], desc ))	end if mode:match('special') and mode:match('candidate') then table.push(info, '&r&' .. col .. 'This is a SPECIAL candidate!/&7It rarely appears!') end if mode:match('candidate') and ago then table.push(info, '&r&7Last elected: &' .. col .. ago .. 'y ago') end return { name, col, m.link or name, table.concat(info, '//'), title = perksref.title, text = perksref.text } end

local function templateTemplate(id, mode) local ret = {} for name, dt in pairs(mayorData.mayors) do		table.push(ret, p.mayorTooltip(name, mode)) end ret.id = id	return ret end

function p.getMayorsTemplate return templateTemplate('Mayor {o}', 'mayor') end

function p.getMayorCandidatesTemplate return templateTemplate(nil, 'candidate') end

-- For Template:MayorWins function p.getWins( frame ) local args = getArgs(frame) local mayor = args[1] if mayor then local stats = p._getWins if not mayorData.mayors[mayor] then return nil end return stats[mayor].wins end end

function p._getWins function newPerkChance(list, maxperks, validElections) local gainedNewPerk, participated, started = 0, 0, true local last for i = 1, table.length(electionsData) do if not (list[i] or validElections[i]) then -- we *don't know* the state of that mayor in that election (participated? perks?) last = nil -- break the count so that it does not include the first participation after unknown things have happened elseif list[i] then -- candidate participated local curr = list[i] -- counting if last and last.n < maxperks then local diff = curr.n - last.n					if diff == 1 then -- not diff > 0 because Barry's perks got increased by admins once (+2) gainedNewPerk = gainedNewPerk + 1 end participated = participated + 1 end if curr.elected then -- mayor is elected last = nil -- break the count so that it does not include the first participation after elected (100% of 1 perk is useless for statistics) else last = curr end else -- candidate not participated, do nothing end end return gainedNewPerk, participated end function nPerks(s, maxperks) return s:match('*') and maxperks or #s end local dt, storage, percentages, ret, validElections = electionsData, {}, {}, {}, {} for _, v in ipairs(table.keys(mayorData.mayors)) do		storage[v] = { tot_wins = 0, tot_perc = 0, perc_counted = 0, tot_votes = 0, perks_counted = 0, tot_perks = 0, votes_counted = 0, perk_numbers = {}, }	end for i = table.length(dt), 1, -1 do		local item, isValid = dt[i], false local stats = p._getElectionStats(i) if stats[1] then local m = stats[1].name if storage[m] then storage[m].tot_wins = storage[m].tot_wins + 1 if stats[1].percent then storage[m].tot_perc = storage[m].tot_perc + (stats[1].percent or 0) storage[m].perc_counted = storage[m].perc_counted + 1 if stats[1].percent > (storage[m].top_perc or 0) then storage[m].top_perc = stats[1].percent storage[m].top_perc_year = i					end end if stats[1].votes then storage[m].tot_votes = storage[m].tot_votes + (stats[1].votes or 0) storage[m].votes_counted = storage[m].votes_counted + 1 if stats[1].votes > (storage[m].top_votes or 0) then storage[m].top_votes = stats[1].votes storage[m].top_votes_year = i					end end isValid = true if stats[1].perks then storage[m].tot_perks = storage[m].tot_perks + #((stats[1].perks):gsub('%D', '')) storage[m].perks_counted = storage[m].perks_counted + 1 storage[m].perk_numbers[i] = { n = nPerks(stats[1].perks), elected = true } else isValid = false end for k = 2, 5 do					if stats[k] and stats[k].name and stats[k].perks then storage[stats[k].name].perk_numbers[i] = { n = nPerks(stats[k].perks), elected = false } else isValid = false end end end end validElections[i] = isValid end for mayor, stored in pairs(storage) do		local perk_gains, possible_perk_gains = newPerkChance(stored.perk_numbers, table.length(mayorData.mayors[mayor].perks_listed or mayorData.mayors[mayor].perks), validElections) ret[mayor] = { wins = stored.tot_wins, avg_perc = stored.perc_counted > 0 and (stored.tot_perc / stored.perc_counted) or 0, top_perc = stored.top_perc, top_perc_year = stored.top_perc_year, avg_votes = stored.votes_counted > 0 and (stored.tot_votes / stored.votes_counted) or 0, top_votes = stored.top_votes, top_votes_year = stored.top_votes_year, avg_perks = stored.perks_counted > 0 and (stored.tot_perks / stored.perks_counted) or 0, perk_gains = perk_gains, possible_perk_gains = possible_perk_gains, recorded_appearances = table.length(stored.perk_numbers), new_perk_chance = possible_perk_gains > 0 and (perk_gains / possible_perk_gains) or nil, -- perk_numbers = stored.perk_numbers, -- for debugging }	end return ret end

function p.statistics( frame ) return mw.getCurrentFrame:preprocess(table.concat(table.map(p._statistics, function (v)		return tostring(v)	end))) end

function p._statistics local function regCheck (v) return mayorData.mayors[v.name].type == 'regular' or mayorData.mayor[v.name].type == 'retired_regular' end local alltables = {} local stats, analysis = p._getWins, p._analyse local tvote, tperc, tdvote, tdperc = analysis.topvotes, analysis.toppercent, analysis.diffvotes, analysis.diffpercent local tvotereg, tpercreg = table.filter(analysis.topvotes, regCheck), table.filter(analysis.toppercent, regCheck) local lvote, lperc, ldiff = table.length(tvote), table.length(tperc), table.length(tdvote) local eYear = 'ElecYear ' -- Table 1 local wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 10):wikitext(crown .. ' Mayor Election Stats') wikitable:tag('tr'):tag('th'):attr('rowspan', 2):wikitext('Candidate'):done :tag('th'):addClass('lefttxt'):attr('rowspan', 2):wikitext('Info TE: Times Elected NP%: New Perk Chance '):done :tag('th'):attr('colspan', 3):wikitext('Statistics On Elected'):done :tag('tr'):tag('th'):wikitext(string.makeTitle('Avg. Perks', 'Average amount of perks that a mayor has when they are elected.')):done :tag('th'):wikitext(string.makeTitle('Vote Percentage', 'Vote percentage statistics for a mayor when they are elected.')):done :tag('th'):wikitext(string.makeTitle('Vote Count', 'Vote count statistics for a mayor when they are elected.')):done for k, v in table.sortedPairsByValue(stats, function(a, b)		return a.wins > b.wins	end) do		wikitable:tag('tr'):tag('td'):wikitext(p.getMayorLink(k)):done :tag('td'):wikitext(('TE: %s NP%%: %s'):format( v.wins, v.new_perk_chance and ('%.2f%% '):format(v.perk_gains, v.possible_perk_gains, v.new_perk_chance * 100) or 'N/A' )):done :tag('td'):wikitext(v.avg_perks > 0 and ('%.2f'):format(v.avg_perks) or 'N/A'):done :tag('td'):wikitext((' Average: Top \'\'(%s %s)\'\': ')				:format(v.avg_perc, eYear, v.top_perc_year, v.top_perc)):done :tag('td'):wikitext((' Average: Top \'\'(%s %s)\'\': ')				:format(v.avg_votes, eYear, v.top_votes_year, v.top_votes)):done end table.push(alltables, wikitable) -- Table 2 wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 6):wikitext(string.makeTitle('Most Ever Votes' .. infosymbol, 'Highest vote counts of an elected mayor')):done :tag('th'):attr('rowspan', 2):attr('colspan', 3):wikitext(string.makeTitle('Least Ever Votes' .. infosymbol, 'Lowest vote counts of an elected mayor')):done :tag('tr'):tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(All Candidates)'):done :tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(No Special Mayor)'):done wikitable:tag('tr') :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext(eYear):done:tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext(eYear):done:tag('th'):wikitext('Votes'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext(eYear):done:tag('th'):wikitext('Votes'):done for i = 1, 5 do		local j = lvote - i		wikitable:tag('tr'):tag('td'):wikitext(p.getMayorLink(tvote[i].name)):done :tag('td'):wikitext(tvote[i].year):done :tag('td'):wikitext((''):format(tvote[i].votes)):done :tag('td'):wikitext(p.getMayorLink(tvotereg[i].name)):done :tag('td'):wikitext(tvotereg[i].year):done :tag('td'):wikitext((''):format(tvotereg[i].votes)):done :tag('td'):wikitext(''):done :tag('td'):wikitext(tvote[j].year):done :tag('td'):wikitext((''):format(tvote[j].votes)):done end table.push(alltables, wikitable) -- Table 3 wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 6):wikitext(string.makeTitle('Most Ever Percentage Support' .. infosymbol, 'Highest vote percentages of an elected mayor')):done :tag('th'):attr('rowspan', 2):attr('colspan', 3):wikitext(string.makeTitle('Least Ever Percentage Support' .. infosymbol, 'Lowest vote percentages of an elected mayor')):done :tag('tr'):tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(All Candidates)'):done :tag('th'):attr('colspan', 3):addClass('size-small'):wikitext('(No Special Mayor)'):done wikitable:tag('tr') :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext(eYear):done:tag('th'):wikitext('Percent'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext(eYear):done:tag('th'):wikitext('Percent'):done :tag('th'):wikitext('Candidate'):done:tag('th'):wikitext(eYear):done:tag('th'):wikitext('Percent'):done for i = 1, 5 do		local j = lperc - i		wikitable:tag('tr'):tag('td'):wikitext(p.getMayorLink(tperc[i].name)):done :tag('td'):wikitext(tperc[i].year):done :tag('td'):wikitext((''):format(tperc[i].percent)):done :tag('td'):wikitext(p.getMayorLink(tpercreg[i].name)):done :tag('td'):wikitext(tpercreg[i].year):done :tag('td'):wikitext((''):format(tpercreg[i].percent)):done :tag('td'):wikitext(''):done :tag('td'):wikitext(tperc[j].year):done :tag('td'):wikitext((''):format(tperc[j].percent)):done end table.push(alltables, wikitable) -- Table 4 local difflegn = string.makeTitle('Diff', 'Difference between top two.') wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('tr'):tag('th'):attr('colspan', 3):wikitext('Tightest Race by Votes'):done :tag('th'):attr('colspan', 3):wikitext('Tightest Race by Percentage'):done wikitable:tag('tr') :tag('th'):wikitext(eYear):done:tag('th'):wikitext(difflegn):done:tag('th'):wikitext('Top Two'):done :tag('th'):wikitext(eYear):done:tag('th'):wikitext(difflegn):done:tag('th'):wikitext('Top Two'):done for i = 1, 5 do		local j = ldiff - i		local resultstr = ('%s %s'):format(			p.getMayorLink(tdvote[j].name[1]),			tdvote[j].votes[1],			p.getMayorLink(tdvote[j].name[2]),			tdvote[j].votes[2]		) local resultstr2 = ('%s %s'):format(			p.getMayorLink(tdperc[j].name[1]),			tdperc[j].percent[1],			p.getMayorLink(tdperc[j].name[2]),			tdperc[j].percent[2]		) wikitable:tag('tr'):tag('td'):wikitext(tdvote[j].year):done :tag('td'):wikitext((''):format(tdvote[j].votes_diff)):done :tag('td'):wikitext(resultstr):done :tag('td'):wikitext(tpercreg[j].year):done :tag('td'):wikitext((''):format(tdperc[j].percent_diff)):done :tag('td'):wikitext(resultstr2):done end table.push(alltables, wikitable) return alltables end

function p._analyse -- This function returns all necessary tables/values after analysing all voting data local dt, topvotes, toppercent, diffvotes, diffpercent = electionsData, {}, {}, {}, {} for i = table.length(dt), 1, -1 do		local item = dt[i] local stats = p._getElectionStats(i) if stats[1] and stats[1].votes then table.push(topvotes, {				year = i,				name = stats[1].name,				votes = stats[1].votes,			}) end if stats[1] and stats[1].percent then table.push(toppercent, {				year = i,				name = stats[1].name,				percent = stats[1].percent,			}) end if stats[1] and stats[2] then table.push(diffvotes, {				year = i,				name = { stats[1].name, stats[2].name },				votes = { stats[1].votes, stats[2].votes },				percent = { stats[1].percent, stats[2].percent },				votes_diff = stats[1].votes - stats[2].votes,			}) table.push(diffpercent, {				year = i,				name = { stats[1].name, stats[2].name },				votes = { stats[1].votes, stats[2].votes },				percent = { stats[1].percent, stats[2].percent },				percent_diff = stats[1].percent - stats[2].percent,			}) end end table.sort(topvotes, function (a, b)		return a.votes > b.votes	end) table.sort(toppercent, function (a, b)		return a.percent > b.percent	end) table.sort(diffvotes, function (a, b)		return a.votes_diff > b.votes_diff	end) table.sort(diffpercent, function (a, b)		return a.percent_diff > b.percent_diff	end) return { topvotes = topvotes, toppercent = toppercent, diffvotes = diffvotes, diffpercent = diffpercent, } end

function p.isValidMayor(mayor) return mayorData.mayors[string.ucfirst(mayor:lower)] and true or false end

return p