Module:Sandbox/Julio974fr

require('strict') local p = {}; local political_party = require('Module:Political party')

local LANG = 'en' local FALLBACK_LANGS = {'de', 'fr', 'it', 'rm'}

-- Properties shorthands local P_COUNTRY             = 'P17' local P_MEMBER_OF_PARTY     = 'P102' local P_FOLLOWS             = 'P155' local P_FOLLOWED_BY         = 'P156' local P_HAS_PARTS           = 'P527' local P_PART_OF             = 'P361' local P_COLOR               = 'P465' local P_APPLIES_TO_PART     = 'P518' local P_POINT_IN_TIME       = 'P585' local P_CANDIDATE           = 'P726' local P_SUCCESSFUL_CANDIDATE = 'P991' local P_VOTES_RECEIVED      = 'P1111' local P_REPLACED_BY         = 'P1366' local P_SEATS               = 'P1410' local P_TOTAL_VALID_VOTES   = 'P1697' local P_SHORT_NAME          = 'P1813' local P_ELIGIBLE_VOTERS     = 'P1867' local P_BALLOTS_CAST        = 'P1868' local P_NAME                = 'P2561' local P_OBJECT_HAS_ROLE     = 'P3831' local P_CANDIDATE_NUMBER    = '4243' local P_SPOILT_VOTES        = 'P5044' local P_BLANK_VOTES         = 'P5045'

-- Get a best ranking statement value from "item" with the property "property".	 The value is returned through the function "typefunc". -- Function copied from elsewhere local function getStatementValue(item, property, typefunc) local statements = mw.wikibase.getBestStatements(item, property) if statements[1] and statements[1].mainsnak.snaktype == 'value' then return typefunc(statements[1].mainsnak.datavalue.value) end end

local function getStatementQualifier(item, property, qualifier, typefunc) local statements = mw.wikibase.getBestStatements(item, property) if statements[1] and statements[1].qualifiers and statements[1].qualifiers[qualifier] and statements[1].qualifiers[qualifier][1] then return typefunc(statements[1].qualifiers[qualifier][1].datavalue.value) end end

-- The "typefunc"s - A series of functions that extract the wanted information    from a statement value depending on the value type. local function getAmount(value)		return tonumber(value.amount) end local function getString(value)		return value end local function getItem(value)		return value.id end local function getTimestamp(value)	return value.time end

-- Finds the text value of a given property in the given language local function getValueFromLanguage(qid, property, lang) local statement = mw.wikibase.getBestStatements(qid, property) for i, v in ipairs(statement) do		if v.mainsnak.datavalue.value.language == lang then return v.mainsnak.datavalue.value.text end end for i, v in ipairs(statement) do		if v.mainsnak.datavalue.value.language == 'mul' then return v.mainsnak.datavalue.value.text end end end

local function getPartyColor(qid, articleTitle) local color = getStatementValue(qid, P_COLOR, getString) if color then return '#'..color end if articleTitle == nil then return '#ffffff' end color = political_party._fetch({articleTitle, 'color', error='#ffffff'}) color = mw.ustring.gsub(color, '&(#)35;', '%1') return color end

local function sortParties(listParties) local function comparePartyByVotes(partyA, partyB) if partyB.qid == 'Q86630688' then -- Q86630688 is 'Others' return true elseif partyA.qid == 'Q86630688' then return false elseif (partyA.votes1 or 0) > (partyB.votes1 or 0) then -- compare votes return true elseif (partyA.votes1 or 0) < (partyB.votes1 or 0) then return false elseif (partyA.seats or 0) > (partyB.seats or 0) then -- compare seats return true elseif (partyA.seats or 0) < (partyB.seats or 0) then return false elseif (partyA.number or 0) < (partyB.number or 0) then -- compare electoral numbers (comparisons reversed, smaller numbers first) return true elseif (partyA.number or 0) > (partyB.number or 0) then return false elseif (partyA.candidateName or partyA.partyArticle or partyA.partyName or ) < (partyB.candidateName or partyB.partyArticle or partyB.partyName or ) then -- compare candidate names (comparisons reversed, first letters first) return true elseif (partyA.candidateName or partyA.partyArticle or partyA.partyName or ) > (partyB.candidateName or partyB.partyArticle or partyB.partyName or ) then return false else return ((partyA.seats or 0) > (partyB.seats or 0)) end end table.sort(listParties, comparePartyByVotes) end

local function formatWikilink_simple(link, displaytext) return ..displaytext.. end

local function getAndFormatInterlanguageLinks(qid) local fallbackLinks = {} for _, language in ipairs(FALLBACK_LANGS) do		local sitelink = mw.wikibase.getSitelink(qid, language..'wiki') if sitelink then fallbackLinks[#fallbackLinks+1] = formatWikilink_simple(':'..language..':'..sitelink, language) end end if #fallbackLinks == 0 then return '' end return ' [&ZeroWidthSpace;'..table.concat(fallbackLinks, '; ')..'] ' end

local function formatWikilink(link, displaytext, qid) if link then if displaytext then return ..displaytext.. else return ..link.. end else if displaytext then return displaytext..getAndFormatInterlanguageLinks(qid) else return ..qid....getAndFormatInterlanguageLinks(qid) end end end

local function fetchCandidacyData_party(partyQid) local data = {} data.candidacyType = 'party' -- Get party qid data.partyQid = partyQid -- Get party article local partyArticle, label partyArticle = mw.wikibase.getSitelink(partyQid) if not partyArticle then label = mw.wikibase.getLabel(partyQid) end data.partyArticle = partyArticle -- Get party name data.partyName = getValueFromLanguage(partyQid, P_NAME, LANG) or label return data end

local function fetchCandidacyData_partyShort(partyQid) local data = {} -- Get party data.partyQid = partyQid if not data.partyQid then return data end data.partyArticle = mw.wikibase.getSitelink(data.partyQid) data.partyName = getValueFromLanguage(data.partyQid, P_SHORT_NAME, LANG) return data end

local function fetchCandidacyData_candidate(candidateQid) local data = {} data.candidacyType = 'candidate' data.candidateQid = candidateQid -- Get article name local candidateArticle, label data.candidateArticle = mw.wikibase.getSitelink(candidateQid) data.candidateName = mw.wikibase.getLabel(candidateQid) return data end

-- For a given candidacy: gets their name, article link, party shortlink, and color local function fetchCandidacyData(qid, candidacyType) if candidacyType == 'party' then return fetchCandidacyData_party(qid) elseif candidacyType == 'candidate' then return fetchCandidacyData_candidate(qid) else error('candidacyType not specified') end end

-- Returns the candidacy qid from a candidacy statement local function getCandidacyQidFromStatement(candidacyStatement, candidacyType, doGroupByParty) if doGroupByParty then if candidacyStatement.qualifiers[P_MEMBER_OF_PARTY] then return candidacyStatement.qualifiers[P_MEMBER_OF_PARTY][1].datavalue.value.id		end end return candidacyStatement.mainsnak.datavalue.value.id end

-- function internal to findCandidacyIndex local function findCandidacyIndex_finder(listParties, partyQid) local finalPartyIndex = nil for curPartyindex, curParty in ipairs(listParties) do		if curParty.qid == partyQid then finalPartyIndex = curPartyindex break end end return finalPartyIndex end

-- Returns the index of a candidacy in a list given its qid. If it is not present and doCreate is true, creates an index for it local function findCandidacyIndex(listCandidacies, candidacyQid, candidacyType, doCreate) local candidacyIndex = findCandidacyIndex_finder(listCandidacies, candidacyQid) if not doCreate then return candidacyIndex end if not candidacyIndex then if candidacyType == 'party' then table.insert(listCandidacies, {qid=candidacyQid, votes1 = 0, seats = 0}) elseif candidacyType == 'candidate' then table.insert(listCandidacies, {qid=candidacyQid, votes1 = 0}) else error('candidacyType not specified') end candidacyIndex = #listCandidacies end return candidacyIndex end

-- Returns a table with data from the candidacy's statement local function addCandidacyDataFromStatement(candidacyData, statement, candidacyType, round, doPrevious, isWinning) local roundKey if round then roundKey = tostring(round) else roundKey = '1' end if doPrevious then if not candidacyData.prevVotes then candidacyData.prevVotes = 0 end if statement.qualifiers[P_VOTES_RECEIVED] then candidacyData.prevVotes = candidacyData.prevVotes + getAmount(statement.qualifiers[P_VOTES_RECEIVED][1].datavalue.value) end if not candidacyData.prevSeats then candidacyData.prevSeats = 0 end if statement.qualifiers[P_SEATS] then candidacyData.prevSeats = candidacyData.prevSeats + getAmount(statement.qualifiers[P_SEATS][1].datavalue.value) end return candidacyData end if isWinning then candidacyData.winning = round end if not statement.qualifiers then return candidacyData end if statement.qualifiers[P_VOTES_RECEIVED] then if not candidacyData['votes'..roundKey] then candidacyData['votes'..roundKey] = 0 end candidacyData['votes'..roundKey] = candidacyData['votes'..roundKey] + getAmount(statement.qualifiers[P_VOTES_RECEIVED][1].datavalue.value) end if statement.qualifiers[P_SEATS] then if not candidacyData.seats then candidacyData.seats = 0 end candidacyData.seats = candidacyData.seats + getAmount(statement.qualifiers[P_SEATS][1].datavalue.value) end if statement.qualifiers[P_MEMBER_OF_PARTY] then candidacyData.partyQid = getItem(statement.qualifiers[P_MEMBER_OF_PARTY][1].datavalue.value) end if statement.qualifiers[P_OBJECT_HAS_ROLE] and getItem(statement.qualifiers[P_OBJECT_HAS_ROLE][1].datavalue.value) == 'Q42841' then -- Q42841 means incumbent candidacyData.isIncumbent = true end if statement.qualifiers[P_FOLLOWS] then if not candidacyData.predecessors then candidacyData.predecessors = {} end for _, predecessorStatement in ipairs(statement.qualifiers[P_FOLLOWS]) do			table.insert(candidacyData.predecessors, getItem(predecessorStatement.datavalue.value)) end end return candidacyData end

local function findPartyPredecessorOverride(listParties, partyQid, partyIndex) for tempIndex, tempParty in ipairs(listParties) do		if tempParty.predecessors then for _, tempPredecessor in ipairs(tempParty.predecessors) do				if tempPredecessor == partyQid then return tempIndex end end end end return partyIndex end

-- function internal to insertCandidacyInList local function insertCandidacyInList_previous(listCandidacies, candidacyStatement, candidacyType, candidacyQid) local candidacyIndex = findCandidacyIndex(listCandidacies, candidacyQid, candidacyType, false) candidacyIndex = findPartyPredecessorOverride(listCandidacies, candidacyQid, candidacyIndex) if not candidacyIndex then candidacyIndex = findCandidacyIndex(listCandidacies, getStatementValue(candidacyQid, P_REPLACED_BY, getItem), candidacyType, false) end if candidacyIndex then listCandidacies[candidacyIndex] = addCandidacyDataFromStatement(listCandidacies[candidacyIndex], candidacyStatement, candidacyType, 1, true) end return listCandidacies end

-- Inserts a candidacy in the list of candidacies local function insertCandidacyInList(listCandidacies, candidacyStatement, candidacyType, round, doPrevious, doGroupByParty, isWinning) local candidacyQid = getCandidacyQidFromStatement(candidacyStatement, candidacyType, doGroupByParty) if doPrevious then return insertCandidacyInList_previous(listCandidacies, candidacyStatement, candidacyType, candidacyQid) end local candidacyIndex = findCandidacyIndex(listCandidacies, candidacyQid, candidacyType, true) listCandidacies[candidacyIndex] = addCandidacyDataFromStatement(listCandidacies[candidacyIndex], candidacyStatement, candidacyType, round, false, isWinning) return listCandidacies end

local function addPartyPredecessorsByComponents(listParties) for partyIndex, partyData in ipairs(listParties) do		local allParts = mw.wikibase.getAllStatements(partyData.qid, P_HAS_PARTS) for _, predecessorStatement in ipairs(allParts) do			if not partyData.predecessors then partyData.predecessors = {} end table.insert(partyData.predecessors, getItem(predecessorStatement.mainsnak.datavalue.value)) end listParties[partyIndex] = partyData end return listParties end

-- Retrieves the list of parties and sorts it by votes/seats local function getCandidaciesDataFromElection(roundsQidsList, candidacyType, previousQid, doGroupByParty) local listCandidacies = {} -- For each round for round, roundQid in ipairs(roundsQidsList) do		-- For each candidate local allStatementsInElection = mw.wikibase.getAllStatements(roundQid, P_CANDIDATE) for _, candidacyStatement in ipairs(allStatementsInElection) do			insertCandidacyInList(listCandidacies, candidacyStatement, candidacyType, round, false, doGroupByParty) end local allWinningStatementsInElection = mw.wikibase.getAllStatements(roundQid, P_SUCCESSFUL_CANDIDATE) for _, winningStatement in ipairs(allWinningStatementsInElection) do			insertCandidacyInList(listCandidacies, winningStatement, candidacyType, round, false, false, true) end end -- For each party in the previous election if previousQid then listCandidacies = addPartyPredecessorsByComponents(listCandidacies) local allStatementsPrevElection = mw.wikibase.getAllStatements(previousQid, P_CANDIDATE) for _, partyStatement in ipairs(allStatementsPrevElection) do			insertCandidacyInList(listCandidacies, partyStatement, 'party', 1, true, doGroupByParty) end end return listCandidacies end

local function getAllCandidaciesData(roundsQidsList, candidacyType, previousQid, partyNameOverrides, doGroupByParty) local candidaciesData = getCandidaciesDataFromElection(roundsQidsList, candidacyType, previousQid, doGroupByParty) for candidacyIndex, candidacyData in ipairs(candidaciesData) do		local fetchedCandidacyData = fetchCandidacyData(candidacyData.qid, candidacyType) if candidacyType == 'candidate' then local fetchedPartyData = fetchCandidacyData_partyShort(candidacyData.partyQid) candidacyData.partyName = candidacyData.partyName or fetchedPartyData.partyName candidacyData.partyArticle = candidacyData.partyArticle or fetchedPartyData.partyArticle end candidacyData.partyQid = candidacyData.partyQid or fetchedCandidacyData.partyQid candidacyData.partyName = candidacyData.partyName or fetchedCandidacyData.partyName candidacyData.partyArticle = candidacyData.partyArticle or fetchedCandidacyData.partyArticle candidacyData.candidateQid = candidacyData.candidateQid or fetchedCandidacyData.candidateQid candidacyData.candidateName = candidacyData.candidateName or fetchedCandidacyData.candidateName candidacyData.candidateArticle = candidacyData.candidateArticle or fetchedCandidacyData.candidateArticle if candidacyData.partyQid then candidacyData.partyWikilink = formatWikilink(candidacyData.partyArticle, candidacyData.partyName, candidacyData.partyQid) end if candidacyData.candidateQid then candidacyData.candidateWikilink = formatWikilink(candidacyData.candidateArticle, candidacyData.candidateName, candidacyData.candidateQid) end if candidacyData.partyQid then candidacyData.color = getPartyColor(candidacyData.partyQid, candidacyData.partyArticle) end if candidacyData.partyQid and partyNameOverrides['name_override_'..(candidacyData.partyQid)] then -- If name override specified candidacyData.name = partyNameOverrides['name_override_'..(fetchedCandidacyData.partyQid)] end candidaciesData[candidacyIndex] = candidacyData end sortParties(candidaciesData) return candidaciesData end

local function getElectionSitelink(qid) local articleTitle = mw.wikibase.getSitelink(qid) local supersetElectionQid = getStatementValue(qid, P_PART_OF, getItem) while (not articleTitle) or supersetElectionQid do		qid = supersetElectionQid supersetElectionQid = getStatementValue(qid, P_PART_OF, getItem) articleTitle = mw.wikibase.getSitelink(qid) end return articleTitle end

local function filterArgs(args, filter) local matched = {} for k, v in pairs(args) do		if string.match(k, filter) then matched[k] = v		end end return matched end

local function getYear(timestamp) return tonumber(string.match(timestamp, '%+(%d%d%d%d)%-%d%d%-%d%dT%d%d:%d%d:%d%d')) end

local function getReferences(electionQid) local allReferences = {} local function getReferenceIndex(allReferences, reference) local url = reference.snaks.P854[1].datavalue.value for i, curRef in ipairs(allReferences) do			if curRef.url == url then return i			end end table.insert(allReferences, {url=url}) return #allReferences end local function addReference(allReferences, reference) local refIndex = getReferenceIndex(allReferences, reference) -- TODO: Add other reference attributes -- allReferences[refIndex].url = uwu end

for _, property in ipairs({P_CANDIDATE, P_ELIGIBLE_VOTERS, P_BALLOTS_CAST, P_SPOILT_VOTES, P_TOTAL_VALID_VOTES, P_BLANK_VOTES}) do		for _, statement in ipairs(mw.wikibase.getBestStatements(electionQid, property)) do			if statement.references then for _, reference in ipairs(statement.references) do					addReference(allReferences, reference) end end end end return allReferences end

local function round(n) local quotient, remainder = math.modf(n) if remainder >= 0.5 then return quotient+1 else return quotient end end

local function getElectionStatistics(qid, fallbackQid) local statistics if fallbackQid then -- fallbackQid is used to handle the fact data can be in the main item or in a separate "first round" item statistics = getElectionStatistics(fallbackQid) else statistics = {} end if not qid then return {} end statistics.eligibleVoters = getStatementValue(qid, P_ELIGIBLE_VOTERS, getAmount) or statistics.eligibleVoters statistics.totalBallots = getStatementValue(qid, P_BALLOTS_CAST, getAmount) or statistics.totalBallots statistics.invalidBallots = getStatementQualifier(qid, P_BALLOTS_CAST, P_SPOILT_VOTES, getAmount) or statistics.invalidBallots statistics.blankBallots = getStatementQualifier(qid, P_BALLOTS_CAST, P_BLANK_VOTES, getAmount) or statistics.blankBallots statistics.invalidVotes = getStatementValue(qid, P_SPOILT_VOTES, getAmount) or statistics.invalidVotes statistics.blankVotes = getStatementValue(qid, P_BLANK_VOTES, getAmount) or statistics.blankVotes statistics.validVotes = getStatementValue(qid, P_TOTAL_VALID_VOTES, getAmount) or statistics.validVotes statistics.validBallots = (statistics.totalBallots or 0) - (statistics.invalidBallots or 0) - (statistics.blankBallots or 0) statistics.totalVotes = (statistics.validVotes or 0) + (statistics.invalidVotes or 0) + (statistics.blankVotes or 0) statistics.magnitude = round(statistics.totalVotes / statistics.validBallots) return statistics end

local function getRounds(electionQid) local rounds = {} for _,partStatement in ipairs(mw.wikibase.getAllStatements(electionQid, P_HAS_PARTS)) do		local roundQid = partStatement.mainsnak.datavalue.value.id		local roundNumber = partStatement.qualifiers.P1545[1].datavalue.value rounds[tonumber(roundNumber)] = roundQid end if rounds == {} then return {electionQid} end return rounds end

local function getSeatsTotals(partiesData) local totalSeats = 0 local totalSeatsChange = 0 for _, partyData in ipairs(partiesData) do		totalSeats = totalSeats + (partyData.seats or 0) totalSeatsChange = totalSeatsChange + (partyData.seats or 0) - (partyData.prevSeats or 0) end return totalSeats, totalSeatsChange end

-- Formatting functions (some copied from Module:Election_results) local lang = mw.getContentLanguage local function fmt(n) return n and tonumber(n) and lang:formatNum(tonumber(n)) or nil end

local function pct(n, d)	n, d = tonumber(n), tonumber(d) if n and d and d > 0 then return string.format('%.2f', n / d * 100) end return '–' end

local function formatMultiplier(n, d)	local n, d = tonumber(n), tonumber(d) if (not n) or (not d) or d == 0 then return '–' end local num = tostring(round(n/d)) return tostring(num)..'×' end

local function diff(n, prevN) if not n then return '–' elseif not prevN then return "New" end n, prevN = tonumber(n), tonumber(prevN) if n > prevN then return '+'..tostring(n-prevN) elseif prevN > n then return '−'..tostring(prevN-n) else return '±0' end end

local function diffPct(n, d, prevN, prevD) if not n or not d or not prevD then return '–' elseif not prevN then return "New" end n = tonumber(n) / tonumber(d) prevN = tonumber(prevN) / tonumber(prevD) if n > prevN then return '+'..string.format('%.2f', (n - prevN) * 100) elseif prevN > n then return '−'..string.format('%.2f', (prevN - n) * 100) else return '±0.00' end end

local function diffKey(n, prevN) if (not prevN) then return n end return n - prevN end

local function diffKeyPct(n, d, prevN, prevD) if not n or not d or not prevD then return nil end n = tonumber(n) / tonumber(d) if not prevN then return n end prevN = tonumber(prevN) / tonumber(prevD) return n - prevN end

-- Table-generating functions

local function beginTable(classes, electionQid) local root = mw.html.create('span') root:attr('id', electionQid..'_resultsTable') local tab = root:tag('table') tab:addClass(classes) local tableCaption = tab:tag('caption') local caption = 'Results of the ' .. mw.wikibase.getLabel(electionQid) local previousElectionQid = getStatementValue(electionQid, P_FOLLOWS, getItem) local nextElectionQid = getStatementValue(electionQid, P_FOLLOWED_BY, getItem) if previousElectionQid then caption = '← ' .. caption end if nextElectionQid then caption = caption .. ' →' end tableCaption:wikitext(caption) tableCaption:done tab:done return root, tab end

local function addHeaderCell(row, wikitext, colspan, rowspan, dataSortType) local cell = row:tag('th') cell:wikitext(wikitext) cell:attr('scope', 'col') if colspan then cell:attr('colspan', tostring(colspan)) end if rowspan then cell:attr('rowspan', tostring(rowspan)) end if dataSortType then cell:attr('data-sort-type', dataSortType) end cell:done end

local function addCell(row, wikitext, align, colspan, isBold, sortValue) local cell = row:tag('td') if align then cell:css('text-align', align) end if colspan then cell:attr('colspan', tostring(colspan)) end if isBold then cell:css('font-weight', 'bold') end if sortValue then cell:attr('data-sort-value', sortValue) end cell:wikitext(wikitext) cell:done end

local function addEmptyCell(row, colspan) local cell = row:tag('td') if colspan then cell:attr('colspan', tostring(colspan)) end cell:css('border', 'none') cell:done end

local function addColorCell(row, color) local cell = row:tag('td') cell:css('width', '0px') cell:css('background-color', color) cell:done end

local function fillDiagramCell(diagramCell, wikitext, colspan) diagramCell:wikitext(wikitext) diagramCell:css('text-align', 'center') diagramCell:css('background', '#F8F9FA') diagramCell:attr('colspan', colspan) diagramCell:done end

local function addStatRow(root, rowTitle, titleColspan, statisticsTables, statistic, fractionNumerator, fractionDenominator, doMakeBold, doUseMultiplier) local isStatisticPresent for _, roundStatistics in ipairs(statisticsTables) do -- Check if the statistic is even present if roundStatistics[statistic] then isStatisticPresent = true break end end local row if isStatisticPresent then row = root:tag('tr') row:addClass('sortbottom') if doMakeBold then row:css('font-weight', 'bold') end addCell(row, rowTitle, 'left', titleColspan) for _,roundStatistics in ipairs(statisticsTables) do			if roundStatistics[statistic] then addCell(row, fmt(roundStatistics[statistic]), 'right') if doUseMultiplier then addCell(row, formatMultiplier(roundStatistics[fractionNumerator], roundStatistics[fractionDenominator] or 1), 'right') else addCell(row, pct(roundStatistics[fractionNumerator], roundStatistics[fractionDenominator]), 'right') end end end end return row end

local function addReference(refsCell, ref) local r = refsCell:wikitext(		mw.getCurrentFrame:extensionTag({ name = 'ref', content = ref.url })	)	return r end

local function formatReferencesRow(root, references, electionQid, cols, hasIncumbent) -- References and wikidata link row local row = root:tag('tr') row:addClass('sortbottom') row:css('font-size', '90%') local refsCell = row:tag('td') refsCell:wikitext('See on Wikidata – See template') -- @ todo update link when publishing refsCell:attr('colspan', cols) if #references > 0 then refsCell:wikitext(' – Sources') for _, ref in ipairs(references) do			addReference(refsCell, ref) end end --if hasIncumbent then -- Removed to replace with a $inc.$, but not sure it will stay		refsCell:wikitext(' – * incumbent')	end end

local function checkElectionProperty(candidaciesData, property) for _,candidacy in ipairs(candidaciesData) do		if candidacy[property] and candidacy[property] ~= 0 then return true end end return false end

local function getElectionProperties(candidaciesData) local properties = {} if checkElectionProperty(candidaciesData, 'partyQid') then properties.party = true end if checkElectionProperty(candidaciesData, 'seats') then properties.seats = true end if checkElectionProperty(candidaciesData, 'votes1') then properties.round1 = true end if checkElectionProperty(candidaciesData, 'votes2') then properties.round2 = true end if checkElectionProperty(candidaciesData, 'prevVotes') or checkElectionProperty(candidaciesData, 'prevSeats') then properties.previousElection = true end return properties end

local function getSumOfVotes(candidaciesData, round) local total = 0 local hasVotes = false for _,candidacy in ipairs(candidaciesData) do		if candidacy['votes'..round] then hasVotes = true end total = total + (candidacy['votes'..round] or 0) end if hasVotes then return total end end

-- Main functions

function p._ch_proportional(args) local electionQid = args.qid or args.election or args[1] local previousElectionQid = getStatementValue(electionQid, P_FOLLOWS, getItem) local doGroupByParty = args.groupByParty or false local cols = 0 local year = getYear(getStatementValue(electionQid, P_POINT_IN_TIME, getTimestamp)) local totalVotes = getStatementValue(electionQid, P_TOTAL_VALID_VOTES, getAmount) local statistics = getElectionStatistics(electionQid) statistics.validVotes = statistics.validVotes or totalVotes mw.logObject({statistics}, 'statistics') local prevTotalVotes if previousElectionQid then prevTotalVotes = getStatementValue(previousElectionQid, P_TOTAL_VALID_VOTES, getAmount) end local references = getReferences(electionQid) local rootSpan, root = beginTable('wikitable sortable', electionQid) -- Fetch parties data local partyNameOverrides = filterArgs(args, 'name_override_Q%d+') local partiesData = getAllCandidaciesData({electionQid}, 'party', previousElectionQid, partyNameOverrides, doGroupByParty) local totalSeats, totalSeatsChange = getSeatsTotals(partiesData) mw.logObject(partiesData) if #partiesData == 0 then return nil end local properties = getElectionProperties(partiesData) mw.logObject(properties, 'properties') -- diagramCell (for the parliament diagram) local diagramCell = nil if (not getStatementQualifier(electionQid, P_COUNTRY, P_APPLIES_TO_PART, getItem)) and (totalSeats > 0) then diagramCell = root:tag('th') end

-- Table header local headerRow = root:tag('tr') addHeaderCell(headerRow, (args.partytitle or 'Party'), 2) if properties.round1 then addHeaderCell(headerRow, 'Votes') addHeaderCell(headerRow, '%') end if properties.previousElection and properties.round1 then addHeaderCell(headerRow, '+/−', nil, nil, 'number') end if properties.seats then addHeaderCell(headerRow, 'Seats') end if properties.previousElection and properties.seats then addHeaderCell(headerRow, '+/−', nil, nil, 'number') end cols = cols + 5 if properties.previousElection then cols = cols + 2 end if diagramCell then fillDiagramCell(			diagramCell,			(args['image'] or require('Module:Sandbox/Julio974fr/parliament_diagram').makeParliamentDiagram(partiesData, year)),			cols		) end -- Get parties list and make the rows for _, party in ipairs(partiesData) do		local row = root:tag('tr') if party.qid == 'Q86630688' then --Others addCell(row, 'Others', 'left', 2) row:addClass('sortbottom') else addColorCell(row, party.color) addCell(row, party.partyWikilink) end if properties.round1 then addCell(row, fmt(party.votes1), 'right') addCell(row, pct(party.votes1, statistics.totalVotes), 'right') end if properties.previousElection and properties.round1 then addCell(row, diffPct(party.votes1, totalVotes, party.prevVotes, prevTotalVotes), 'right', nil, nil, diffKeyPct(party.votes1, totalVotes, party.prevVotes, prevTotalVotes)) end if properties.seats then addCell(row, party.seats, 'right') end if properties.previousElection and properties.seats then addCell(row, diff(party.seats, party.prevSeats), 'right', nil, nil, diffKey(party.seats, party.prevSeats)) end end -- Footer separator and totals row if statistics.validVotes then local totalRow = addStatRow(root, 'Total', 2, {statistics}, 'validVotes', 'validVotes', 'validVotes', true) if properties.previousElection and properties.round1 then addCell(totalRow, '–', 'right') end if properties.seats then addCell(totalRow, fmt(totalSeats), 'right') end if properties.previousElection and properties.seats then addCell(totalRow, diff(totalSeatsChange, 0), 'right') end end local row = root:tag('tr') addHeaderCell(row, '', cols) row:addClass('sortbottom') -- Footer rows (statistics & references) if statistics.blankVotes or statistics.invalidVotes then addStatRow(root, 'Valid votes', 2, {statistics}, 'validVotes', 'validVotes', 'totalVotes') if statistics.blankVotes and not statistics.invalidVotes then addStatRow(root, 'Blank and invalid votes', 2, {statistics}, 'blankVotes', 'blankVotes', 'totalVotes') else addStatRow(root, 'Blank votes', 2, {statistics}, 'blankVotes', 'blankVotes', 'totalVotes') addStatRow(root, 'Invalid votes', 2, {statistics}, 'invalidVotes', 'invalidVotes', 'totalVotes') end addStatRow(root, 'Total votes', 2, {statistics}, 'totalVotes', 'totalVotes', 'validBallots', true) end if statistics.blankBallots or statistics.invalidBallots then if statistics.blankBallots and not statistics.invalidBallots then addStatRow(root, 'Blank and invalid ballots', 2, {statistics}, 'blankBallots', 'blankBallots', 'totalBallots') else addStatRow(root, 'Blank ballots', 2, {statistics}, 'blankBallots', 'blankBallots', 'totalBallots') addStatRow(root, 'Invalid ballots', 2, {statistics}, 'invalidBallots', 'invalidBallots', 'totalBallots') end addStatRow(root, 'Total ballots', 2, {statistics}, 'totalBallots', nil, nil, true) end addStatRow(root, 'Registered voters/Turnout', 2, {statistics}, 'eligibleVoters', 'totalBallots', 'eligibleVoters') formatReferencesRow(root, references, electionQid, cols) return rootSpan end

function p._ch_majoritarian(args) local electionQid = args.qid or args.election or args[1] local cols = 0 local year = getYear(getStatementValue(electionQid, P_POINT_IN_TIME, getTimestamp)) local hasIncumbent = false local references = getReferences(electionQid) local roundsQid = getRounds(electionQid) local firstRoundQid = roundsQid[1] or electionQid local secondRoundQid = roundsQid[2] local statistics = getElectionStatistics(electionQid, firstRoundQid) local statistics2 = getElectionStatistics(secondRoundQid) mw.logObject({statistics, statistics2}, 'statistics') local rootSpan, root = beginTable('wikitable sortable', electionQid) -- Fetch parties data local partyNameOverrides = filterArgs(args, 'name_override_Q%d+') local candidatesData = getAllCandidaciesData({firstRoundQid, secondRoundQid}, 'candidate', nil, partyNameOverrides, nil) mw.logObject(candidatesData) statistics.validVotes = statistics.validVotes or getSumOfVotes(candidatesData, 1) statistics2.validVotes = statistics2.validVotes or getSumOfVotes(candidatesData, 2) if #candidatesData == 0 then return nil end local properties = getElectionProperties(candidatesData) mw.logObject(properties, 'properties') local leftCols = properties.party and 3 or 2

-- Table header local headerRow = root:tag('tr') if secondRoundQid then -- not using properties.round2 cause it may be useful to show second-round qualifications later on (@todo Add candidate qualifications without votes) local secondHeaderRow = root:tag('tr') addHeaderCell(headerRow, 'Candidate', 2, 2) if properties.party then addHeaderCell(headerRow, (args.partytitle or 'Party'), 1, 2) end addHeaderCell(headerRow, 'First round', 2) addHeaderCell(headerRow, 'Second round', 2) addHeaderCell(secondHeaderRow, 'Votes') addHeaderCell(secondHeaderRow, '%') addHeaderCell(secondHeaderRow, 'Votes') addHeaderCell(secondHeaderRow, '%') cols = cols+7 else -- If no results yet addHeaderCell(headerRow, 'Candidate', 2) if properties.party then addHeaderCell(headerRow, (args.partytitle or 'Party')) end cols = cols+3 if properties.round1 then addHeaderCell(headerRow, 'Votes') addHeaderCell(headerRow, '%') cols = cols+2 end end -- Get parties list and make the rows for _, candidate in ipairs(candidatesData) do		local row = root:tag('tr') if candidate.isIncumbent then candidate.candidateWikilink = candidate.candidateWikilink .. ' inc.' hasIncumbent = true end if candidate.qid == 'Q86630688' then --Others addCell(row, 'Others', 'left', leftCols) row:addClass('sortbottom') elseif properties.party then addColorCell(row, candidate.color) addCell(row, candidate.candidateWikilink, nil, 1, (candidate.winning and true or false)) addCell(row, candidate.partyWikilink, 'left') else addCell(row, candidate.candidateWikilink, nil, 2, (candidate.winning and true or false)) end if properties.round1 and candidate.votes1 then addCell(row, fmt(candidate.votes1), 'right', 1, (candidate.winning==1)) addCell(row, pct(candidate.votes1, (statistics.validVotes/statistics.magnitude)), 'right', 1, (candidate.winning==1)) end if properties.round2 and candidate.votes2 then addCell(row, fmt(candidate.votes2), 'right', 1, (candidate.winning==2)) addCell(row, pct(candidate.votes2, (statistics2.validVotes/statistics2.magnitude)), 'right', 1, (candidate.winning==2)) elseif properties.round2 then addEmptyCell(row, 2) end end -- Footer separator and totals row addStatRow(root, 'Total', leftCols, {statistics, statistics2}, 'validVotes', 'validVotes', 'validVotes', true) local row = root:tag('tr') addHeaderCell(row, '', cols) row:addClass('sortbottom') -- Footer rows (statistics & references) if statistics.blankVotes or statistics.invalidVotes then addStatRow(root, 'Valid votes', leftCols, {statistics, statistics2}, 'validVotes', 'validVotes', 'totalVotes') if statistics.blankVotes and not statistics.invalidVotes then addStatRow(root, 'Blank and invalid votes', leftCols, {statistics, statistics2}, 'blankVotes', 'blankVotes', 'totalVotes') else addStatRow(root, 'Blank votes', leftCols, {statistics, statistics2}, 'blankVotes', 'blankVotes', 'totalVotes') addStatRow(root, 'Invalid votes', leftCols, {statistics, statistics2}, 'invalidVotes', 'invalidVotes', 'totalVotes') end addStatRow(root, 'Total votes', leftCols, {statistics, statistics2}, 'totalVotes', 'magnitude', nil, true, true) end if statistics.blankBallots or statistics.invalidBallots then if statistics.blankBallots and not statistics.invalidBallots then addStatRow(root, 'Blank and invalid ballots', leftCols, {statistics, statistics2}, 'blankBallots', 'blankBallots', 'totalBallots') else addStatRow(root, 'Blank ballots', leftCols, {statistics, statistics2}, 'blankBallots', 'blankBallots', 'totalBallots') addStatRow(root, 'Invalid ballots', leftCols, {statistics, statistics2}, 'invalidBallots', 'invalidBallots', 'totalBallots') end addStatRow(root, 'Total ballots', leftCols, {statistics, statistics2}, 'totalBallots', nil, nil, true) end addStatRow(root, 'Registered voters/Turnout', leftCols, {statistics, statistics2}, 'eligibleVoters', 'totalBallots', 'eligibleVoters') formatReferencesRow(root, references, electionQid, cols, hasIncumbent) return rootSpan end

-- Wrappers

function p.ch_proportional(frame) -- Initialise and populate variables local getArgs = require("Module:Arguments").getArgs local args = getArgs(frame) return p._ch_proportional(args) end

function p.ch_majoritarian(frame) -- Initialize and populate variables local getArgs = require("Module:Arguments").getArgs local args = getArgs(frame) return p._ch_majoritarian(args) end

return p