Module:Daily proofreading stats table

--[=[
Module description
]=]
require('strict')

local p = {} --p stands for package

local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')

local lowerLimit = 100
local upperLimit = 200

local function get_days_in_month(month, year)
	local days_in_month = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }   
	local d = days_in_month[month]
   
	-- check for leap year
	if (month == 2) then
    	if (math.mod(year,4) == 0) then
    		if (math.mod(year,100) == 0) then
    			if (math.mod(year,400) == 0) then
        			d = 29
    			end
    		else
    			d = 29
    		end
    	end
	 end
  return d  
end

-- calculate "processed" pages for a given stats dump
-- validated is double because it includes a prior proofread step
local function get_processed(t)
	return t.q0 + t.q3 + (t.q4 * 2)
end

local function add_note_row(tab, colspan, content, classes)
	local row = tab:tag('tr')
		
	row:tag('td')
		:attr('colspan', colspan)
		:wikitext(content)
	
	if classes then
		row:addClass(classes)
	end
	
	row:addClass('wst-daily-stats-note-row')
end

local function add_col_header(tr, content, title)
	local th = tr:tag('th')
		:wikitext(content)
		:attr('scope', 'col')
		
	if title then
		th:attr('title', title)
	end
end

--[=[
Function docs
]=]
function p.table(frame)
	
	local args = getArgs(frame)
	
	local t = mw.html.create('table')
		:addClass('wst-daily-stats')
		
	local ok, data = pcall(mw.loadData, args.source)
	local year = tonumber(args.year)
	local month = tonumber(args.month)
	
	local num_cols = 5
	
	t:tag('caption')
		:wikitext("Daily statistics")
	
	add_note_row(t, num_cols,
		"Day under " .. lowerLimit,
		'wst-daily-stats-under-target')
	
	add_note_row(t, num_cols,
		"Day over " .. upperLimit,
		'wst-daily-stats-over-target')
	
	local tr = t:tag('tr')
	
	add_col_header(t, 'Day')
	add_col_header(t, 'P', 'Pages proofread on this day')
	add_col_header(t, 'V', 'Pages validated on this day')
	add_col_header(t, 'Pages', 'Pages proofread, validated or marked without text on this day')
	add_col_header(t, 'Total', 'Pages proofread, validated or marked without text since the start of the month')

	local now = os.date("!*t")

	local max_day = now.day;

	if yesno(args.include_today) then
		max_day = max_day + 1
	end

	local totals = {
		proofread = 0,
		validated = 0,
		processed = 0,
	}
	
	local avg_totals = {
		proofread = 0,
		validated = 0,
		processed = 0,
		num_days = 0,	
	}
	
	if (now.year > year) or (now.year == year and now.month > month) then
		-- the data is in the past, show all days
		max_day = 100
	elseif (now.year < year) or (now.month < month) then
		-- in the future
		max_day = 0;	
	end
	
	local last;
	if data and data.days then
		-- data is present
		last = data.days[0];
	else
		-- data is missing
		max_day = 0
	end

	local day_data
	for day = 1, get_days_in_month(month, year) do
		
		if data.days then
			day_data = data.days[day]
		end

		local processed = 0
		local diff = 0
		local proofread = 0
		local validated = 0
		local last_proc = 0
		if last then
			last_proc = get_processed(last)
		end
		if day_data then
			processed = get_processed(day_data)
			diff = processed - last_proc
			
			validated = day_data.q4 - (last.q4 or 0)
			-- every validated page also "steals" a proofread page
			proofread = day_data.q3 - (last.q3 or 0) + validated
			
			totals.proofread = totals.proofread + proofread
			totals.validated = totals.validated + validated
		end
		totals.processed = totals.processed + diff
		
		tr = t:tag('tr')
			:addClass('wst-daily-stats-daydata')
		
		tr:tag('td')
			:wikitext(day)
			:attr('data-day', day)

		if day < max_day then
			if args.include_today and day == max_day - 1 then
				tr:addClass('wst-daily-stats-in-progress')
					:attr('title', 'This day is not yet complete, these are the most recent statistics.')
			else
				-- include the day in the averages
				avg_totals.processed = totals.processed
				avg_totals.proofread = totals.proofread
				avg_totals.validated = totals.validated
				avg_totals.num_days = avg_totals.num_days + 1
			end
				
			tr:tag('td')
				:wikitext(proofread)
				
			tr:tag('td')
				:wikitext(validated)
				
			tr:tag('td')
				:wikitext(diff)
				
			-- month-to-date total
			tr:tag('td')
				:wikitext(totals.processed)
				
			if day_data and diff < lowerLimit then
				tr:addClass('wst-daily-stats-under-target')	
			elseif diff > upperLimit then
				tr:addClass('wst-daily-stats-over-target')	
			end
			if day_data == nil then
				tr:addClass('wst-daily-stats-missing-data')
			end
		else
			-- add dummy table cells
			tr:tag('td')
			tr:tag('td')
			
			tr:addClass('wst-daily-stats-empty')
		end

		last = day_data
	end

	if avg_totals.num_days > 0 then	
	-- add the averages
	tr = t:tag('tr')
		:addClass('wst-daily-stats-average')

		tr:tag( 'td' )
			:wikitext( 'Avg.' )
			:attr( 'title', 'The average page counts for whole days completed this month' )
		
		tr:tag( 'td' )
			:wikitext(string.format( '%d', ( avg_totals.proofread / avg_totals.num_days ) + 0.5 ) )
		tr:tag( 'td' )
			:wikitext(string.format( '%d', ( avg_totals.validated / avg_totals.num_days ) + 0.5  ) )
		tr:tag( 'td' )
		tr:tag( 'td' )
			:wikitext(string.format( '%d', ( avg_totals.processed / avg_totals.num_days ) + 0.5  ) )	
	
		-- add the totals
		tr = t:tag('tr')
			:addClass( 'wst-daily-stats-total' )
		
		tr:tag('td')
			:wikitext('Total')
			:attr( 'title', 'The total pages completed in each category this month' )
	
		tr:tag('td')
			:wikitext(totals.proofread)
		tr:tag('td')
			:wikitext(totals.validated)
		tr:tag('td')
		tr:tag('td')
			:wikitext(totals.processed)
	end
	
	return t
end

return p