Module:Proposal check

From the Super Mario Wiki, the Mario encyclopedia
Jump to navigationJump to search

This module powers the {{proposal check}} template.


local p = {}

local function fmtVotes(votes)
    if votes == 1 then
        return "'''1''' vote"
    else
        return "'''" .. votes .. "''' votes"
    end
end

local function fmtProposalCheckOutput(args)
    local bq = mw.html.create("blockquote")
    if args.desc then
        bq
            :tag("span")
            :wikitext(args.desc .. " ")
    end
    if args.verdict then
        bq
            :tag("b")
            :wikitext(args.verdict)
            :css("color", args.verdictColor)
    end
    return bq
end

local function normalizeArgs(rawArgs)
    local args = {}
    for k, v in pairs(rawArgs) do
        v = mw.text.trim(v)
        if v ~= "" then
            args[k] = v
        end
    end
    return args
end

function p.proposalCheck(args)
    local voteTally = nil
    local totalUsers = nil
    local isEarly = args.early ~= nil
    local firstPlaceOptionVotes = -1
    local firstPlaceOptionIndex = nil
    local secondPlaceOptionVotes = -1
    local voteMargin = nil
    local approvalRate = nil

    if args[1] == nil then
        return fmtProposalCheckOutput {
            verdictColor = "red",
            verdict = "MISSING PARAMETERS; SEE [[Template:Proposal check|DOCUMENTATION]]",
        }
    elseif args[1]:match("^%d+$") then
        -- legacy call: 5 | 3 | 7 | early=y
        firstPlaceOptionVotes = tonumber(args[1])
        secondPlaceOptionVotes = tonumber(args[2])
        if (firstPlaceOptionVotes == nil or secondPlaceOptionVotes == nil) then
            return fmtProposalCheckOutput {
                verdictColor = "red",
                verdict = "INVALID PARAMETERS; SEE [[Template:Proposal check|DOCUMENTATION]]",
            }
        elseif secondPlaceOptionVotes > firstPlaceOptionVotes then
            return fmtProposalCheckOutput {
                verdictColor = "red",
                verdict = "SECOND PLACE OPTION CANNOT HAVE MORE VOTES THAN FIRST PLACE",
            }
        end
        if args[3] == nil then
            totalUsers = firstPlaceOptionVotes + secondPlaceOptionVotes
        else
            totalUsers = tonumber(args[3])
            if totalUsers == nil then
                return fmtProposalCheckOutput {
                    verdictColor = "red",
                    verdict = '"' .. args[3] .. '" IS NOT A VALID NUMBER',
                }
            end
        end
    else
        -- 5-3-2 | 7 | early=y
        voteTally = mw.text.split(args[1], "-", true)
        totalUsers = tonumber(args[2])
        local totalVoteCount = 0

        for index, votesStr in ipairs(voteTally) do
            local votes = tonumber(votesStr)
            if votes == nil then
                return fmtProposalCheckOutput {
                    verdictColor = "red",
                    verdict = '"' .. votesStr .. '" IS NOT A VALID NUMBER',
                }
            end
            totalVoteCount = totalVoteCount + votes
            if votes >= firstPlaceOptionVotes then
                secondPlaceOptionVotes = firstPlaceOptionVotes
                firstPlaceOptionVotes = votes
                firstPlaceOptionIndex = index
            elseif votes >= secondPlaceOptionVotes then
                secondPlaceOptionVotes = votes
            end
        end

        if totalUsers == nil and #voteTally == 2 then
            totalUsers = firstPlaceOptionVotes + secondPlaceOptionVotes
        elseif totalUsers == nil then
            return fmtProposalCheckOutput {
                verdictColor = "red",
                verdict = "MISSING UNIQUE USER COUNT FOR MULTI-OPTION PROPOSAL",
            }
        elseif totalUsers > totalVoteCount then
            return fmtProposalCheckOutput {
                verdictColor = "red",
                verdict = "PROPOSAL CANNOT HAVE MORE UNIQUE USERS THAN VOTES",
            }
        end
    end

    if firstPlaceOptionVotes > totalUsers then
        return fmtProposalCheckOutput {
            verdictColor = "red",
            verdict = "FIRST PLACE OPTION CANNOT HAVE MORE VOTES THAN UNIQUE USERS",
        }
    end

    voteMargin = firstPlaceOptionVotes - secondPlaceOptionVotes
    approvalRate = firstPlaceOptionVotes / totalUsers

    if firstPlaceOptionVotes < 4 then
        return fmtProposalCheckOutput {
            desc = "The first place option must have at least 4 votes, "
                .. "but it has only " .. fmtVotes(firstPlaceOptionVotes) .. ".",
            verdictColor = "gray",
            verdict = "NO QUORUM",
        }
    elseif voteMargin == 0 then
        return fmtProposalCheckOutput {
            desc = "The top two options have an equal number of votes.",
            verdictColor = "red",
            verdict = "CONSENSUS NOT REACHED",
        }
    end

    local prefix = "With a margin of " .. fmtVotes(voteMargin) .. ", "
    local suffix = "'''" .. (math.floor(1000 * approvalRate + 0.5) / 10) .. "%''' approve "
        .. (
            firstPlaceOptionIndex and "option #" .. firstPlaceOptionIndex .. "."
            or "the first place option."
        )

    if isEarly then
        if voteMargin < 8 then
            return fmtProposalCheckOutput {
                desc = prefix .. "the proposal is not eligible to close one week early.",
                verdictColor = "red",
                verdict = "DO NOT CLOSE EARLY",
            }
        end
        local canCloseEarly = approvalRate >= 0.8
        return fmtProposalCheckOutput {
            desc = prefix .. "at least 80% approval is required to close one week early. "
                .. suffix,
            verdictColor = canCloseEarly and "green" or "red",
            verdict = canCloseEarly and "CLOSE EARLY" or "DO NOT CLOSE EARLY",
        }
    elseif voteMargin >= 3 then
        local hasConsensus = approvalRate > 0.5
        return fmtProposalCheckOutput {
            desc = prefix .. "more than 50% approval is required. " .. suffix,
            verdictColor = hasConsensus and "green" or "red",
            verdict = hasConsensus and "CONSENSUS REACHED" or "CONSENSUS NOT REACHED",
        }
    else
        local hasConsensus = approvalRate >= 0.6
        return fmtProposalCheckOutput {
            desc = prefix .. "at least 60% approval is required. " .. suffix,
            verdictColor = hasConsensus and "green" or "red",
            verdict = hasConsensus and "CONSENSUS REACHED" or "CONSENSUS NOT REACHED",
        }
    end
end

function p.main(frame)
    local parentArgs = frame:getParent() and frame:getParent().args or {}
    local args = normalizeArgs(parentArgs)
    if next(args) == nil then
        args = normalizeArgs(frame.args)
    end
    return p.proposalCheck(args)
end

return p