Module:CCI stats/sandbox

From Wikipedia, the free encyclopedia
local TableTools = require("Module:TableTools");
local getArgs  = require("Module:Arguments").getArgs;

local p = {}

--- Converts the stats date (days since 1 Jan 1970) into a human-readable
-- date.
-- @param date The date to convert
function p._statsDateToDate(date)
	return mw.language.getContentLanguage()
		:formatDate(os.date("%Y-%m-%d", date * 86400))
end

--- Extracts case open and close dates from wikitext.
-- @param opens The table to append open dates to.
-- @param opens The table to append close dates to.
-- @param wikitext The wikitext to parse.
function p._extractCaseDates(opens, closes, wikitext)
    for openDate in mw.ustring.gmatch(wikitext, "data%-cci%-open=['\"](.-)['\"]") do
        table.insert(opens, tonumber(openDate))
    end
    for closeDate in mw.ustring.gmatch(wikitext, "data%-cci%-close=['\"](.-)['\"]") do
        table.insert(closes, tonumber(closeDate))
    end

    return opens, closes, wikitext
end

--- Returns a tuple containing case dates, the first being all the dates where a
-- case was opened, the second being all the dates where a case was closed. All
-- dates are in stats format (i.e. days since the first day of the UNIX epoch,
-- January 1, 1970).
-- @param noArchive `true` to exclude counting archives. `closes` will be empty.
function p._getCaseDates(noArchive)
    local frame = mw.getCurrentFrame()
    local opens = {}
    local closes = {}

    p._extractCaseDates(
        opens, closes, frame:expandTemplate{ title = "CCIlist" }
    )

    if not noArchive then
        p._extractCaseDates(
            opens, closes, frame:expandTemplate{ title = "Wikipedia:Contributor copyright investigations/Archive" }
        )
    end

    table.sort(opens)
    table.sort(closes)
    return opens, closes
end

--- Transforms a table of case opening and closing dates and returns a table
-- of overall case count change for each day. This function will does not return
-- any duplicate keys.
function p._caseDatesToCaseNet(opens, closes)
    local caseNet = {}

    for _, date in ipairs(opens) do
        if not caseNet[date] then
            caseNet[date] = 1
        else
            caseNet[date] = caseNet[date] + 1
        end
    end

    for _, date in ipairs(closes) do
        if not caseNet[date] then
            caseNet[date] = -1
        else
            caseNet[date] = caseNet[date] - 1
        end
    end

    return caseNet
end

-- Gets case net change and count as tabulated data (wikitext)
function p._caseCountToTabulatedRows(caseNet)
	local caseTable = mw.html.create("table")
		:addClass("wikitable")
    local caseCountRows = {}
	
	caseTable:node(
		mw.html.create("tr")
			:node(
				mw.html.create("th")
					:wikitext("Date")
			)
			:node(
				mw.html.create("th")
					:wikitext("Total")	
			)
			:node(
				mw.html.create("th")
					:wikitext("Net")
			)
	)

    local lastValue = 0
    for date, net in TableTools.sortedPairs(caseNet) do
    	local newValue = lastValue + net
    	local caseRow = mw.html.create("tr");
    	
    	local caseDate = mw.html.create("td")
    		:wikitext(p._statsDateToDate(date));
    	
    	local caseCount = mw.html.create("td")
    		:wikitext(newValue);
    		
		local caseNet = mw.html.create("td")
			:css("color", net > 0 and "#006400" or (net < 0 and "#8b0000" or "inherit"))
			:css("font-weight", net > 2 and "bold" or "normal")
			:wikitext(net);
    	
    	caseRow
    		:node(caseDate)
    		:node(caseCount)
    		:node(caseNet);
    	caseTable:node(caseRow);
        lastValue = newValue
    end

    return caseTable
end

--- Transforms a table of case net dates and returns a table with date:caseCount
-- pairs.
function p._caseNetToDateCaseCounts(caseNet)
    local dateCaseCounts = {}

    local lastValue = 0
    for date, net in TableTools.sortedPairs(caseNet) do
    	local newValue = lastValue + net
        dateCaseCounts[date] = newValue
        lastValue = newValue
    end

    return dateCaseCounts
end

--- Transforms date case counts to Vega data.
function p._dateCaseCountsToVega(dateCaseCounts)
    local values = {}

    for date, caseCount in TableTools.sortedPairs(dateCaseCounts) do
        table.insert(values, { 
            -- Multiply by seconds in a day to get UNIX timestamp.
            x = date * 86400000,
            y = caseCount
        })
    end

    return {
        data = {
            {
            	name = "cases",
            	values = values
            }
        },
        scales = {
            {
                name = "date",
                type = "time",
                range = "width",
                domain = { data = "cases", field = "x" }
            },
            {
                name = "open-cases",
                range = "height",
                nice = true,
                domain = { data = "cases", field = "y" }
            }
        },
        axes = {
            { type = "x", scale = "date", title = "Date" },
            { type = "y", scale = "open-cases", title = "Open cases" }
        },
        marks = {
            {
                type = "line",
                from = { data = "cases" },
                properties = {
                    enter = {
                        x = { scale = "date", field = "x" },
                        y = { scale = "open-cases", field = "y" },
                        stroke = { value = "#652c90" }
                    }
                }
            }
        }
    }
end

--- Build a graph with the relevant data.
local function buildGraph(frame, args, settings)
    local graphSettings = {
        version = "2",
        width = args.width or 1000,
        height = args.height or 300
    }
    local graphContent = TableTools.deepCopy(graphSettings)
    for k,v in TableTools.sortedPairs(settings) do graphContent[k] = v end

    return frame:extensionTag{
        name = "graph",
        content = mw.text.jsonEncode(
            graphContent,
            mw.text.JSON_PRETTY
        )
    }
end

function p._graphCases(args)
    local opens, closes = p._getCaseDates(args.noArchive)
    local caseNet = p._caseDatesToCaseNet(opens, closes)
    local dateCaseCounts = p._caseNetToDateCaseCounts(caseNet)
    local vegaData = p._dateCaseCountsToVega(dateCaseCounts)

    return buildGraph(args.frame, args.args, vegaData)
end

function p.graphCases(frame)
    local args = getArgs(frame, {
		trim = true,
		removeBlanks = false
	})

    return p._graphCases{
        frame = frame,
        args = args,
        from = args.from,
        upto = args.upto
    }
end

-- Get raw Vega data
function p.dumpCases(frame)
    local args = getArgs(frame, {
		trim = true,
		removeBlanks = false
	})

    local opens, closes = p._getCaseDates(args.noArchive)
    local caseNet = p._caseDatesToCaseNet(opens, closes)
    local dateCaseCounts = p._caseNetToDateCaseCounts(caseNet)
    
    return mw.text.jsonEncode(
        p._dateCaseCountsToVega(dateCaseCounts),
        mw.text.JSON_PRETTY
    )
end

function p._tabulatedData(args)
    local opens, closes = p._getCaseDates(args.noArchive)
    local caseNet = p._caseDatesToCaseNet(opens, closes)
    
    return tostring(p._caseCountToTabulatedRows(caseNet))
end

function p.tabulatedData(frame)
    local args = getArgs(frame, {
		trim = true,
		removeBlanks = false
	})

    return p._tabulatedData{
        frame = frame,
        args = args,
        from = args.from,
        upto = args.upto
    }
end

function p._caseCount(args)
    local opens = p._getCaseDates(true)
    return tostring(#opens)
end

function p.caseCount(frame)
    local args = getArgs(frame, {
		trim = true,
		removeBlanks = false
	})

    return p._caseCount{
        frame = frame,
        args = args
    }
end

return p