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 ' [​'..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 'wikidata:'..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 WikidataSee 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 {{sup|{{abbr|inc.|Incumbent}}}}, 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