Module:Election box US auto
Appearance
This module is rated as beta, and is ready for widespread use. It is still new and should be used with some caution to ensure the results are as expected. |
This is used by {{Election box US auto}}
.
Usage
[edit]{{#invoke:Election box US auto|function_name}}
local p = {}
local mYesno = require('Module:Yesno')
-- a global per #invoke
local linked_write_in = false
function formatnum( num )
-- simple wrapper
local lang = mw.getContentLanguage()
return lang:formatNum( num )
end
function percent( part, total )
if total >= 1000000 then
-- if > 1 million votes, then round to 2 decimals
round_to = 2
else
round_to = 1
end
local ret = mw.ext.ParserFunctions.expr( "" .. 100 * part / total .. " round " .. round_to )
if not string.find( ret, ".", 1, true ) then
-- add the decimals that expr doesn't
ret = ret .. "." .. string.rep("0", round_to)
end
return ret
end
function p.make( invoke )
frame = invoke:getParent()
local state, year, contest, type = parse_args( frame.args )
local ret = ""
local no_headings = mYesno(frame.args["no headings"])
if string.find(year, ",", 1, true ) then
-- multi mode
for i,v in pairs(mw.text.split( year, ",", true )) do
if not no_headings then
ret = ret .. "\n=== " .. v .. " ==="
end
ret = ret .. make( state, v, contest, type, year_args(v, frame.args) )
end
else
ret = ret .. make( state, year, contest, type, frame.args )
end
return invoke:preprocess( ret )
end
function fmt_candidate(v, winner, total_votes,args, usestateparties)
local temp = "{{"
if v[2] == winner[2] then
temp = temp .. "Election box winning candidate"
else
temp = temp .. "Election box candidate"
end
local n_party = normalize_parties( v[3], usestateparties )
if n_party then
temp = temp .. " with party link no change|party=" .. n_party
else
temp = temp .. " no party link no change"
end
link = args[v[2] .. " link"]
if link then
link = mw.title.new(link)
else
-- bypass redirects, which is mostly important for display names
link = mw.title.new(v[2])
-- except if the redirect goes to an "elections" article (e.g. Kim Vann), then
-- we don't want a bypass
if link.isRedirect and not string.find(link.redirectTarget.prefixedText, "elections", 1, true ) then
link = link.redirectTarget
end
end
-- Strip disambiguators since we can't use the pipe trick
display_name, ignore = mw.ustring.gsub(link.prefixedText, "%b()", "")
if args.redlinks or (link.exists and not link.isRedirect) then
full_link = "[[" .. link.prefixedText .. "|" .. display_name .. "]]"
else
full_link = display_name
end
temp = temp .. "|candidate=" .. full_link
if v[4] or args.incumbent == v[2] then
-- incumbent
temp = temp .. " (Incumbent)"
end
if v[6] then
-- write in
if linked_write_in then
temp = temp .. " (write-in)"
else
temp = temp .. " ([[Write-in candidate|write-in]])"
linked_write_in = true -- only link it once
end
end
temp = temp .. "|votes=" .. formatnum(v[5])
temp = temp .. "|percentage=" .. percent(v[5], total_votes) .. "%"
temp = temp .. "}}"
return temp
end
function parse_args( args )
local state = args[1]
if not state then
error("State is missing")
end
local year = args[2]
if not year then
error("Year is missing")
end
local contest = args[3]
if not contest then
error("Contest is missing")
end
local type = "General"
if args.type then
if args.type == "Primary" then
type = "Primary"
else
error("Invalid value for |type=")
end
end
return state, year, contest, type
end
function year_args( year, args )
-- we want to turn year args like "|2018 foo=" into just foo
-- drop any other year args like "|2016 foo="
-- and have year args override general args
-- finally have general args
local new = {}
for k,v in pairs(args) do
local k_year = mw.ustring.match(k, "^%d%d%d%d ")
if k_year then
k_year = mw.text.trim(k_year)
end
if k_year and k_year == year then
new[mw.ustring.sub(k, 6)] = v
elseif k_year and k_year ~= year then
-- do nothing
else
-- if k isn't set yet, set it.
if not new[k] then
new[k] = v
end
end
end
return new
end
function make( state, year, contest, type, args )
function load_tabular( state, year, type )
local tab_name = state .. " Elections/" .. year .. "/" .. type .. "/Candidates.tab"
local tabular = mw.ext.data.get(tab_name)
if tabular then
return tabular
else
return {error="Unable to find tabular data: " .. tab_name}
end
end
local tabular = load_tabular(state, year, type)
if tabular.error then
error(tabular.error)
end
function find_candidates(data, contest)
local candidates = {}
for k,v in pairs(data) do
if v[1] == contest then
table.insert(candidates, v)
end
end
return candidates
end
local candidates = find_candidates(tabular.data, contest)
function sum_totals(candidates)
local total_votes = 0
local incumb_party = false
local winner = {}
winner[5] = 0
for k,v in pairs(candidates) do
total_votes = total_votes + v[5]
if v[5] > winner[5] then
winner = v
end
if v[4] or args.incumbent == v[2] then
incumb_party = v[3]
end
end
return total_votes, winner, incumb_party
end
local total_votes, winner, incumb_party = sum_totals(candidates)
local usestateparties = nil
if mw.ustring.find(contest, "United States Representative", 1, true) then
title = "[[United States House of Representatives elections, " .. year .. "]]"
elseif mw.ustring.find(contest, "State Assembly Member", 1, true) then
title = "[[" .. state .. " State Assembly election, " .. year .. "]]"
usestateparties = state
elseif contest == "President" then
title = "U.S. presidential election in " .. state .. ", " .. year
else
title = "...????"
end
local primary = mYesno(args.primary)
ptabular = load_tabular(state, year, "Primary")
if ptabular.error then
-- todo log an error here?
primary = false
end
if primary then
open = "Election box open primary begin no change"
else
open = "Election box begin no change"
end
function make_ref(tabular)
return '<ref name="' .. tabular.description .. '">' .. tabular.sources .. "</ref>"
end
local ref = make_ref(tabular)
if primary then
-- primary ref goes first
ref = make_ref(ptabular) .. ref
end
local ret = "{{" .. open .. "| title=" .. title .. ref .. "}}"
function total_box(total_votes)
return "{{Election box total no change|votes=" .. formatnum(total_votes) .. "|percentage = " .. percent(total_votes, total_votes) .. "%}}"
end
function sort_candidates(a,b)
return a[5] > b[5]
end
table.sort(candidates, sort_candidates)
if primary then
local pcandidates = find_candidates(ptabular.data, contest)
table.sort(pcandidates, sort_candidates)
local ptotal_votes, pwinner, pincumb_party = sum_totals(pcandidates)
local fake_winner = {}
-- we don't want a winner in primaries, so use a fake one that no
-- candidate will match
fake_winner[2] = ""
for k,v in pairs(pcandidates) do
ret = ret .. fmt_candidate(v, fake_winner,ptotal_votes,args,usestateparties)
end
ret = ret .. total_box(ptotal_votes) .. "{{Election box open primary general election no change}}"
end
for k,v in pairs(candidates) do
ret = ret .. fmt_candidate(v, winner,total_votes,args,usestateparties)
end
ret = ret .. total_box(total_votes)
local hold = args.hold
local gain = false
if hold == "held" or winner[4] or args.incumbent == winner[2] then
ret = ret .. "{{Election box hold with party link without swing|winner=" .. normalize_parties(winner[3],usestateparties) .. "}}"
elseif hold == "flip" then
-- shorthand for D->R/R->D
win_party = winner[3]
if win_party == "Democratic" then
lose_party = normalize_parties("Republican",usestateparties)
else
lose_party = normalize_parties("Democratic",usestateparties)
end
win_party = normalize_parties(win_party)
gain = true
elseif args.gain then
win_party = normalize_parties(args.gain,usestateparties)
lose_party = normalize_parties(args.loser,usestateparties)
gain = true
elseif incumb_party and incumb_party ~= winner[3] then
win_party = normalize_parties(winner[3],usestateparties)
lose_party = normalize_parties(incumb_party,usestateparties)
gain = true
end
if gain then
ret = ret .. "{{Election box gain with party link without swing|winner=" .. win_party .. "|loser=" .. lose_party .. "}}"
end
ret = ret .. "{{Election box end}}"
return ret
end
function normalize_parties( party, state )
-- Drop all parties after the first one?
party = mw.text.split( party, ",", true )[1]
local specials = {
Blank = "Independent (politician)",
Independent = "Independent (politician)",
}
specials["No Party Preference"] = "No party preference"
if specials[party] then
return specials[party]
end
if state then
-- ex "California Democratic Party"
return state .. " " .. party .. " Party"
end
return party .. " Party (US)"
end
return p