Module:CCI stats

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

return p