blob: fb0da5fef3e79ebfb177f6a412241c390905ed09 [file] [log] [blame]
require 'set'
require 'whimsy/asf/board'
# Creates a summary hash of information from an Agenda
class ASF::Board::Agenda
# Strings or symbols returned from ASF::Board::Agenda.parse
ATTACH_KEY = :attach
INDEX_KEY = :index
APPROVED_KEY = 'approved'
TITLE_KEY = 'title'
# Hash keys returned by summarize()
ERRORS_KEY = 'errors'
PEOPLE_KEY = 'people'
OFFICERS_KEY = 'officers'
PMCS_KEY = 'pmcs'
ACTIONS_KEY = 'actions'
STATS_KEY = 'stats'
COMMENT_LEN = 'cl'
REPORT_LEN = 'rl'
APPROVALS_KEY = 'ap'
SKIP_AGENDAS = {
'board_agenda_2009_11_01' => 'F2F meeting: ApacheCon, St. Helena, CA',
'board_agenda_2010_09_11' => 'F2F meeting: Boston, MA',
'board_agenda_2010_11_02' => 'F2F meeting: ApacheCon, Atlanta, GA',
'board_agenda_2011_11_07' => 'F2F meeting: ApacheCon, Vancouver, BC',
'board_agenda_2012_08_28' => 'F2F meeting: Adobe, McLean, VA',
'board_agenda_2017_06_15' => 'F2F meeting: Capital One, Mclean, VA'
}
# Summarize data from these meeting minutes
# @param fname of agenda file to summarize
# @return hash of summary statistics from this meeting
# @note if error, includes details in [ERRORS_KEY] = ['SKIP(meeting): foo', 'ERROR(meeting): bar',...]
def self.summarize(fname)
summary = {}
meeting = File.basename(fname, '.*')
if SKIP_AGENDAS.has_key?(meeting)
summary[ERRORS_KEY] = "SKIP(#{meeting}) was: #{SKIP_AGENDAS[meeting]}"
return summary
end
begin
agenda = ASF::Board::Agenda.parse(File.read(fname.untaint))
rescue StandardError => e
summary[ERRORS_KEY] = "ERROR(#{meeting}) Agenda parse error: #{e.message} #{e.backtrace[0]}"
return summary
end
begin
summary[PEOPLE_KEY] = Hash[agenda[1][PEOPLE_KEY]]
summary[PEOPLE_KEY].each do |id, data|
# Note: this adds initials to everyone who was *ever* a director, who was at this meeting
data['initials'] = ASF::Board.directorInitials(id) if ASF::Board.directorHasId?(id)
end
rescue StandardError => e
summary[ERRORS_KEY] = "ERROR(#{meeting}) no attendance error: #{e.message} #{e.backtrace[0]}"
return summary
end
begin
# Gather statistics about reports with preapprovals
approvals = agenda.select{ |v| v.has_key?(APPROVED_KEY) }
# PMC report :attach starts with letter; rest are officer or misc reports
preports, oreports = approvals.partition{ |v| /\A[[:alpha:]]/ =~ v[ATTACH_KEY] }
summary[OFFICERS_KEY] = Hash.new{|h,k| h[k] = {} }
oreports.each do |r|
summary[OFFICERS_KEY][r[TITLE_KEY]]['owner'] = r['owner'] if r.has_key?('owner')
summary[OFFICERS_KEY][r[TITLE_KEY]][APPROVALS_KEY] = Array.new(r['approved'])
summary[OFFICERS_KEY][r[TITLE_KEY]][COMMENT_LEN] = r['comments'].length
summary[OFFICERS_KEY][r[TITLE_KEY]][REPORT_LEN] = r['report'].length if r['report']
end
summary[PMCS_KEY] = Hash.new{|h,k| h[k] = {} }
preports.each do |r|
summary[PMCS_KEY][r[TITLE_KEY]]['owner'] = r['owner']
if r.has_key?('missing')
summary[PMCS_KEY][r[TITLE_KEY]]['missing'] = true
else
summary[PMCS_KEY][r[TITLE_KEY]][APPROVALS_KEY] = Array.new(r['approved'])
summary[PMCS_KEY][r[TITLE_KEY]][COMMENT_LEN] = r['comments'].length
summary[PMCS_KEY][r[TITLE_KEY]][REPORT_LEN] = r['report'].length if r['report']
end
end
actions = agenda.select{ |v| v.has_key?(INDEX_KEY) && v[INDEX_KEY] == "Action Items" }[0][ACTIONS_KEY]
if actions
summary[ACTIONS_KEY] = Hash.new{|h,k| h[k] = [] }
actions.each do |r|
summary[ACTIONS_KEY][r[:owner]] << r[:pmc]
end
end
# Summarize across this report
summary[STATS_KEY] = {}
summary[STATS_KEY]['specialorders'] = agenda.select{ |v| /\A7/ =~ v[ATTACH_KEY] }.length
summary[STATS_KEY]['discusstextlen'] = agenda.select{ |v|
v[INDEX_KEY] == "Discussion Items" || /\A8[A-Z]/ =~ v[ATTACH_KEY]
}.map {|v| v['text'].length}.sum
totapprovals = 0
totcommentlen = 0
totreportlen = 0
totreports = (summary[OFFICERS_KEY].length + summary[PMCS_KEY].length).to_f
# TODO figure out the ruby way to average these
summary[OFFICERS_KEY].each do |x, data|
totapprovals += data[APPROVALS_KEY].length if data[APPROVALS_KEY]
totcommentlen += data[COMMENT_LEN] if data[COMMENT_LEN]
totreportlen += data[REPORT_LEN] if data[REPORT_LEN]
end
summary[PMCS_KEY].each do |x, data|
totapprovals += data[APPROVALS_KEY].length if data[APPROVALS_KEY]
totcommentlen += data[COMMENT_LEN] if data[COMMENT_LEN]
totreportlen += data[REPORT_LEN] if data[REPORT_LEN]
end
if totreports != 0 # Avoid NaN in minutes that aren't parsed fully
summary[STATS_KEY]['avgapprovals'] = (totapprovals / totreports).round(2)
summary[STATS_KEY]['avgcommentlen'] = (totcommentlen / totreports).round(0)
summary[STATS_KEY]['avgreportlen'] = (totreportlen / totreports).round(0)
end
rescue StandardError => e
summary[ERRORS_KEY] ||= "ERROR(#{meeting}) process error: #{e.message} #{e.backtrace[0]}"
end
return summary
end
end