Jump to content

Module:Sandbox/Julio974fr

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Julio974fr (talk | contribs) at 13:14, 19 September 2023 (Incomplete additions to ch_majoritarian). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
local p = {};
local political_party = require('Module:Political party')
local lang = 'en'

--[[ 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

--[[ 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

function p.getAllPartiesData(electionQid, previousQid, partyNameOverrides, doGroupByParty)
	local partiesData = p.getElectionParties(electionQid, previousQid, doGroupByParty)
	
	for partyIndex, partyData in ipairs(partiesData) do
		local fetchedPartyData = p.fetchPartyData(partyData.qid)
		
		partyData.name = fetchedPartyData.name
		partyData.color = fetchedPartyData.color
		partyData.articleTitle = fetchedPartyData.articleTitle
		
		if partyNameOverrides['name_override_'..(partyData.qid)] then -- If name override specified
			partyData.name = partyNameOverrides['name_override_'..(fetchedPartyData.qid)]
		end
		
		partiesData[partyIndex] = partyData
	end
	
	return partiesData
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.votes or 0) > (partyB.votes or 0) then
			return true
		elseif (partyA.votes or 0) < (partyB.votes or 0) then
			return false
		else
			return ((partyA.seats or 0) > (partyB.seats))
		end
	end
	table.sort(listParties, comparePartyByVotes)
end

local function findPartyIndex(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

local function initPartyIndex(listParties, partyQid)
	
	local partyIndex = findPartyIndex(listParties, partyQid)
	
	if not partyIndex then
		table.insert(listParties, {qid=partyQid, votes = 0, seats = 0})
		partyIndex = #listParties
	end
	
	return partyIndex
end

local function addPartyDataFromStatement(partyData, statement)
	
	if statement.qualifiers.P1111 then
		if not partyData.votes then partyData.votes = 0 end
		partyData.votes = partyData.votes + getAmount(statement.qualifiers.P1111[1].datavalue.value)
	end
	
	if statement.qualifiers.P1410 then
		if not partyData.seats then partyData.seats = 0 end
		partyData.seats = partyData.seats + getAmount(statement.qualifiers.P1410[1].datavalue.value)
	end
	
	if statement.qualifiers.P155 then
		if not partyData.predecessors then partyData.predecessors = {} end
		for _, predecessorStatement in ipairs(statement.qualifiers.P155) do
			table.insert(partyData.predecessors, getItem(predecessorStatement.datavalue.value))
		end
	end
	
	return partyData
end

local function addPrevPartyDataFromStatement(partyData, statement)
	
	if not partyData.prevVotes then partyData.prevVotes = 0 end
	if statement.qualifiers.P1111 then
		partyData.prevVotes = partyData.prevVotes + getAmount(statement.qualifiers.P1111[1].datavalue.value)
	end
	
	if not partyData.prevSeats then partyData.prevSeats = 0 end
	if statement.qualifiers.P1410 then
		partyData.prevSeats = partyData.prevSeats + getAmount(statement.qualifiers.P1410[1].datavalue.value)
	end
	
	return partyData
end

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 addPartyPredecessorsByComponents(listParties)
	for partyIndex, partyData in ipairs(listParties) do
		local allParts = mw.wikibase.getAllStatements(partyData.qid, 'P527')
		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

-- Returns the party Qid from a candidacy statement
local function getPartyQidFromStatement(candidateStatement, doGroupByParty)
	if not doGroupByParty or not candidateStatement.qualifiers.P102 then
		return candidateStatement.mainsnak.datavalue.value.id
	end
	
	return candidateStatement.qualifiers.P102[1].datavalue.value.id
end

-- Insert a candidate of the current election in the list of candidates
local function insertCurrentCandidate(listParties, candidateStatement, doGroupByParty)
	partyQid = getPartyQidFromStatement(candidateStatement, doGroupByParty)
	
	local partyIndex = initPartyIndex(listParties, partyQid)
	
	listParties[partyIndex] = addPartyDataFromStatement(listParties[partyIndex], candidateStatement)
	
	return listParties
end

-- Inserts a candidate of the previous election in the list of candidates
local function insertPreviousCandidate(listParties, candidateStatement, doGroupByParty)
	local partyQid = getPartyQidFromStatement(candidateStatement, doGroupByParty)
	
	local partyIndex = findPartyIndex(listParties, partyQid)
	
	partyIndex = findPartyPredecessorOverride(listParties, partyQid, partyIndex)
	
	if not partyIndex then
		partyIndex = findPartyIndex(listParties, getStatementValue(partyQid, 'P1366', getItem))
	end
	
	if partyIndex then
		listParties[partyIndex] = addPrevPartyDataFromStatement(listParties[partyIndex], candidateStatement)
	end
	
	return listParties
end

-- Retrieves the list of parties and sorts it by votes/seats
function p.getElectionParties(electionQid, previousQid, doGroupByParty)
	
	local listParties = {}
	
	-- For each 'candidate' in the election
	local allStatementsInElection = mw.wikibase.getAllStatements(electionQid, 'P726')
	for _, candidateStatement in ipairs(allStatementsInElection) do
		insertCurrentCandidate(listParties, candidateStatement, doGroupByParty)
	end
	
	listParties = addPartyPredecessorsByComponents(listParties)
	
	-- For each 'candidate' in the previous election
	if previousQid then
		local allStatementsPrevElection = mw.wikibase.getAllStatements(previousQid, 'P726')
		for _, candidateStatement in ipairs(allStatementsPrevElection) do
			insertPreviousCandidate(listParties, candidateStatement, doGroupByParty)
		end
	end
	
	sortParties(listParties)
	
	return listParties
end

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

-- For a given party, gets its name, article, and color
function p.fetchPartyData(qid)
	
	-- Get party article name
	local articleTitle, partyLabel
	articleTitle = mw.wikibase.getSitelink(qid)
	if not articleTitle then
		partyLabel = mw.wikibase.getLabel(qid)
	end
	
	-- Get party shortname
	local shortnameStatement = mw.wikibase.getBestStatements(qid, 'P2561')
	local shortname
	for i, v in ipairs(shortnameStatement) do
		if v.mainsnak.datavalue.value.language == lang then
			shortname = v.mainsnak.datavalue.value.text
		end
	end
	
	-- Make party name/link wikitext
	local nameWikitext
	if articleTitle then
		nameWikitext = '[['..articleTitle..'|'..(shortname or partyLabel or articleTitle)..']]'
	elseif partyLabel then
		nameWikitext = partyLabel
	else
		nameWikitext = '[[wikidata:'..qid..']]'
	end
	
	-- Get party color
	local color = getPartyColor(qid, articleTitle)
	
	return {
		qid=qid,
		name=nameWikitext,
		color=color,
		articleTitle=articleTitle
	}
end

local function getElectionSitelink(qid)
	
	local articleTitle
	local supersetElectionQid = getStatementValue(qid, 'P361', getItem)
	
	while (not articleTitle) and supersetElectionQid do
		qid = supersetElectionQid
		supersetElectionQid = getStatementValue(qid, 'P361', 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

function p.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)
		refIndex = getReferenceIndex(allReferences, reference)
		-- TODO: Add other reference attributes
		-- allReferences[refIndex].url = uwu
	end

	for _, property in ipairs({'P726', 'P1867', 'P1868', 'P5044', 'P1697', 'P5045'}) 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 getRounds(electionQid)
	local rounds = {}
	
	for _,partStatement in ipairs(mw.wikibase.getAllStatements('Q113636224', 'P527')) do
		roundQid = partStatement.mainsnak.datavalue.value.id
		roundNumber = partStatement.qualifiers.P1545[1].datavalue.value
		
		rounds[tonumber(roundNumber)] = roundQid
	end
	
	return rounds
end





-- Formatting functions (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 '&ndash;'
end

local function diff(n, prevN)
	if not n then
		return '&ndash;'
	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 then
		return '&ndash;'
	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





--[[ Table-generating functions ]]

local function beginTable(classes, electionQid)
	
	local root = mw.html.create('span')
	root:attr('id', electionQid..'_resultsTable')
	
	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, 'P155', getItem)
	local nextElectionQid = getStatementValue(electionQid, 'P156', getItem)
	
	if previousElectionQid then
		caption = '[['..getElectionSitelink(previousElectionQid)..'#'..previousElectionQid..'_resultsTable|←]] ' .. caption
	end
	if nextElectionQid then
		caption = caption .. ' [['.. getElectionSitelink(nextElectionQid)..'#'..nextElectionQid..'_resultsTable|→]]'
	end
	
	tableCaption:wikitext(caption)
	tableCaption:done()
	
	tab:done()
	return root, tab
end

local function addHeaderCell(row, wikitext, colspan, rowspan)
	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
	cell:done()
end

local function addCell(row, wikitext, align, colspan)
	local cell = row:tag('td')
	if align then cell:css('text-align', align) end
	if colspan then cell:attr('colspan', tostring(colspan)) end
	cell:wikitext(wikitext)
	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 fillTopCell(topCell, wikitext, colspan)
	topCell:wikitext(wikitext)
	topCell:css('text-align', 'center')
	topCell:css('background', '#F8F9FA')
	topCell:attr('colspan', colspan)
	topCell:done()
end

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





--[[ Main functions ]]

function p._ch_proportional(args)
	
	local electionQid = args.qid or args.election or args[1]
	local previousElectionQid = getStatementValue(electionQid, 'P155', getItem)
	
	local doGroupByParty = args.groupByParty or false
	
	local cols = 0
	local year = getYear(getStatementValue(electionQid, 'P585', getTimestamp))
	
	local totalVotes = getStatementValue(electionQid, 'P1697', getAmount)
	
	local prevTotalVotes
	if previousElectionQid then prevTotalVotes = getStatementValue(previousElectionQid, 'P1697', getAmount) end
	
	local references = p.getReferences(electionQid)
	
	
	
	local rootSpan, root = beginTable('wikitable sortable', electionQid)
	
	-- topCell (for the parliament diagram)
	local topCell = nil
	topCell = root:tag('th')

	-- Table header
	local headerRow = root:tag('tr')
	
	addHeaderCell(headerRow, (args.partytitle or 'Party'), 2)
	addHeaderCell(headerRow, 'Votes')
	addHeaderCell(headerRow, '%')
	if previousElectionQid then addHeaderCell(headerRow, '+/−') end
	addHeaderCell(headerRow, 'Seats')
	if previousElectionQid then addHeaderCell(headerRow, '+/−') end
	
	cols = cols + 5
	if previousElectionQid then cols = cols + 2 end
	
	-- Fetch parties data
	partyNameOverrides = filterArgs(args, 'name_override_Q%d+')
	local partiesData = p.getAllPartiesData(electionQid, previousElectionQid, partyNameOverrides, doGroupByParty)
	
	if topCell then
		fillTopCell(
			topCell,
			(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)
		else
			addColorCell(row, party.color)
			addCell(row, party.name)
		end
		
		addCell(row, fmt(party.votes), 'right')
		addCell(row, pct(party.votes, totalVotes), 'right')
		if previousElectionQid then addCell(row, diffPct(party.votes, totalVotes, party.prevVotes, prevTotalVotes), 'right') end
		addCell(row, party.seats, 'right')
		if previousElectionQid then addCell(row, diff(party.seats, party.prevSeats), 'right') end
	end
	
	-- Footer separator
	local row = root:tag('tr')
	addHeaderCell(row, '', cols)
	
	-- References row
	if references then
		local row = root:tag('tr')
		
		local refsCell = row:tag('td')
		refsCell:wikitext('Sources')
		refsCell:attr('colspan', cols)
		
		for _, ref in ipairs(references) do
			addReference(refsCell, ref)
		end
	end
	
	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, 'P585', getTimestamp))
	
	local totalVotes = getStatementValue(electionQid, 'P1697', getAmount)
	
	local references = p.getReferences(electionQid)
	
	local roundsQid = getRounds(electionQid)
	local firstRoundQid = roundsQid[1]
	local secondRoundQid = roundsQid[2]
	local roundsCount = secondRoundQid and 2 or 1
	
	local rootSpan, root = beginTable('wikitable sortable', electionQid)

	-- Table header
	local headerRow = root:tag('tr')
	
	if roundsCount == 1 then
		addHeaderCell(headerRow, 'Candidate', 2)
		addHeaderCell(headerRow, (args.partytitle or 'Party'))
		addHeaderCell(headerRow, 'Votes')
		addHeaderCell(headerRow, '%')
		cols = cols+5
	elseif roundsCount == 2 then
		local secondHeaderRow = root:tag('tr')
		
		addHeaderCell(headerRow, 'Candidate', 2, 2)
		addHeaderCell(headerRow, (args.partytitle or 'Party'), 1, 2)
		addHeaderCell(headerRow, 'First round', 2)
		addHeaderCell(headerRow, 'Second round', 2)
		
		addHeaderCell(secondHeaderRow, 'Votes')
		addHeaderCell(secondHeaderRow, '%')
		addHeaderCell(secondHeaderRow, 'Votes')
		addHeaderCell(secondHeaderRow, '%')
		cols = cols+7
	end
	
	-- Fetch parties data
	partyNameOverrides = filterArgs(args, 'name_override_Q%d+')
	local partiesData = p.getAllPartiesData(firstRoundQid, nil, partyNameOverrides)
	mw.logObject(partiesData)
	
	-- 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)
		else
			addColorCell(row, party.color)
			addCell(row, party.name)
		end
		
		addCell(row, fmt(party.votes), 'right')
		addCell(row, pct(party.votes, totalVotes), 'right')
		addCell(row, party.seats, 'right')
	end
	
	-- Footer separator
	local row = root:tag('tr')
	addHeaderCell(row, '', cols)
	
	-- References row
	if references then
		local row = root:tag('tr')
		
		local refsCell = row:tag('td')
		refsCell:wikitext('Sources')
		refsCell:attr('colspan', cols)
		
		for _, ref in ipairs(references) do
			addReference(refsCell, ref)
		end
	end
	
	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