# Module:Medical cases chart/data

--- Example usage:  
---
--- =p._externalData({datapage="COVID-19 cases in Santa Clara County, California.tab",datarecoveries="hospitalized",datacases="totalConfirmedCases"})  

local p = {}  
local lang = mw.getContentLanguage  
local english = mw.getLanguage("en")  

local function round(x)  
return (math.modf(x + (x < 0 and -0.5 or 0.5)))  
end  

local function formatChange(previous, current)  
if not previous or previous == 0 then  
return  
end  
if previous == current then  
return "="  
end  

local change = current / previous * 100 - 100  
local sign = change < 0 and "−" or "+"  
return mw.ustring.format("%s%s%%", sign, lang:formatNum(round(math.abs(change))))  
end  

function p._externalData(args)  
local data = mw.ext.data.get(args.datapage)  

local dateIndex  
local deathsIndex  
local recoveriesIndex  
local casesIndex  
local class4Index  
local class5Index  
for i, field in ipairs(data.schema.fields) do  
if field.name == "date" or field.name == args.datadate then  
dateIndex = i  
elseif field.name == "deaths" or field.name == args.datadeaths then  
deathsIndex = i  
elseif field.name == "recoveries" or field.name == args.datarecoveries then  
recoveriesIndex = i  
elseif field.name == "cases" or field.name == args.datacases then  
casesIndex = i  
elseif field.name == "class4" or field.name == args.dataclass4 then  
class4Index = i  
elseif field.name == "class5" or field.name == args.dataclass5 then  
class5Index = i  
end  
end  
assert(dateIndex, "Date field not found.")  
assert(deathsIndex or not args.datadeaths, "Deaths field not found.")  
assert(recoveriesIndex or not args.datarecoveries, "Recoveries field not found.")  
assert(casesIndex or not args.datacases, "Cases field not found.")  
assert(class4Index or not args.dataclass4, "Class 4 field not found.")  
assert(class5Index or not args.dataclass5, "Class 5 field not found.")  

-- Restructure the data as tables with keys.  
local records = {}  
for i, row in ipairs(data.data) do  
local record = {  
date = row[dateIndex],  
deaths = deathsIndex and row[deathsIndex],  
recoveries = recoveriesIndex and row[recoveriesIndex],  
cases = casesIndex and row[casesIndex],  
class4 = class4Index and row[class4Index],  
class5 = class5Index and row[class5Index],  
options = {},  
streak = 1,  
}  
local prevRecord = records[#records] or {}  
if casesIndex and not prevRecord.cases and record.cases and record.cases > 0 then  
record.options.firstright1 = "y"  
end  
if deathsIndex and prevRecord.deaths == 0 and record.deaths and record.deaths > 0 then  
record.options.firstright2 = "y"  
end  
if deathsIndex and (prevRecord.deaths or prevRecord.assumedDeaths) and not record.deaths then  
record.assumedDeaths = prevRecord.deaths or prevRecord.assumedDeaths  
end  
if casesIndex and (prevRecord.cases or prevRecord.assumedCases) and not record.cases then  
record.assumedCases = prevRecord.cases or prevRecord.assumedCases  
end  
if record.deaths == prevRecord.deaths  
and record.recoveries == prevRecord.recoveries  
and record.cases == prevRecord.cases  
and record.class4 == prevRecord.class4  
and record.class5 == prevRecord.class5 then  
record.streak = prevRecord.streak + 1  
end  
table.insert(records, record)  
end  

-- Collapse streaks of identical data.  
for i = #records, 1, -1 do  
local record = records[i]  
if record.streak > 3 then  
for j = i, i - record.streak + 3, -1 do  
table.remove(records, j)  
end  
i = i - record.streak + 2  
record = records[i]  
record.options.collapse = "y"  
record.options.id = english:formatDate("M", record.date):lower  
record.date = nil  
end  
end  

-- Stringify the data.  
local rows = {}  
for i, record in ipairs(records) do  
local prevRecord = records[i - 1] or {}  
local row = {  
record.date or "",  
tostring(record.deaths or record.assumedDeaths or ""),  
tostring(record.recoveries or ""),  
tostring(record.cases or record.assumedCases or ""),  
tostring(record.class4 or ""),  
tostring(record.class5 or ""),  
record.cases and lang:formatNum(record.cases) or "",  
record.cases and formatChange(prevRecord.cases or prevRecord.assumedCases, record.cases) or "",  
record.deaths and lang:formatNum(record.deaths) or "",  
record.deaths and formatChange(prevRecord.deaths or prevRecord.assumedDeaths, record.deaths) or "",  
}  
for k, v in pairs(record.options) do  
table.insert(row, string.format("%s=%s", k, v))  
end  
table.insert(rows, table.concat(row, ";"))  
end  
return table.concat(rows, "\n")  
end  

function p.externalData  
local args = {}  
for k,v in pairs(.args) do  
if (v or *) ~= * then  
args['data'..k] = v  
end  
end  
return p._externalData(args)  
end  
return p