Module:CCI stats
Appearance
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