You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@whimsical.apache.org by cu...@apache.org on 2018/04/03 12:56:20 UTC

[whimsy] branch master updated: Add new summarize() method for use by officers/board-stats.cgi

This is an automated email from the ASF dual-hosted git repository.

curcuru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/whimsy.git


The following commit(s) were added to refs/heads/master by this push:
     new 94e899c  Add new summarize() method for use by officers/board-stats.cgi
94e899c is described below

commit 94e899cb466cc8e4ce808147e71ba2feefd48492
Author: Shane Curcuru <as...@shanecurcuru.org>
AuthorDate: Tue Apr 3 08:56:13 2018 -0400

    Add new summarize() method for use by officers/board-stats.cgi
---
 lib/whimsy/asf/agenda.rb         |   1 +
 lib/whimsy/asf/agenda/summary.rb | 149 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 150 insertions(+)

diff --git a/lib/whimsy/asf/agenda.rb b/lib/whimsy/asf/agenda.rb
index bbc96c7..63503ce 100644
--- a/lib/whimsy/asf/agenda.rb
+++ b/lib/whimsy/asf/agenda.rb
@@ -188,3 +188,4 @@ require_relative 'agenda/attachments'
 require_relative 'agenda/committee'
 require_relative 'agenda/special'
 require_relative 'agenda/back'
+require_relative 'agenda/summary'
diff --git a/lib/whimsy/asf/agenda/summary.rb b/lib/whimsy/asf/agenda/summary.rb
new file mode 100644
index 0000000..be2b010
--- /dev/null
+++ b/lib/whimsy/asf/agenda/summary.rb
@@ -0,0 +1,149 @@
+require 'set'
+
+# 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'
+
+  INITIALS_IDX = 0
+  # Map director ids->names and ids->initials
+  # Only filled in since 2007 or so, once the preapp data in meetings is parseable
+  DIRECTOR_MAP = {
+    'bayard' => ['hy', 'Henri', 'Henri Yandell'],
+    'bdelacretaz' => ['bd', 'Bertrand', 'Bertrand Delacretaz'],
+    'brett' => ['bp', 'Brett', 'Brett Porter'],
+    'brianm' => ['bmc', 'Brian', 'Brian McCallister'],
+    'cliffs' => ['cs', 'Cliff', 'Cliff Schmidt'],
+    'coar' => ['kc', 'Ken', 'Ken Coar'],
+    'curcuru' => ['sc', 'Shane', 'Shane Curcuru'],
+    'cutting' => ['dc', 'Doug', 'Doug Cutting'],
+    'dirkx' => ['dg', 'Dirk-Willem', 'Dirk-Willem van Gulik'],
+    'dkulp' => ['dk', 'Daniel', 'Daniel Kulp'],
+    'fielding' => ['rf', 'Roy', 'Roy T. Fielding'],
+    'geirm' => ['gmj', 'Geir', 'Geir Magnusson Jr'],
+    'gstein' => ['gs', 'Greg', 'Greg Stein'],
+    'isabel' => ['idf', 'Isabel', 'Isabel Drost-Fromm'],
+    'jerenkrantz' => ['je', 'Justin', 'Justin Erenkrantz'],
+    'jim' => ['jj', 'Jim', 'Jim Jagielski'],
+    'ke4qqq' => ['dn', 'David', 'David Nalley'],
+    'lrosen' => ['lr', 'Larry', 'Lawrence Rosen'],
+    'markt' => ['mt', 'Mark', 'Mark Thomas'],
+    'marvin' => ['mh', 'Marvin', 'Marvin Humphrey'],
+    'mattmann' => ['cm', 'Chris', 'Chris Mattmann'],
+    'noirin' => ['np', 'Noirin', 'Noirin Plunkett'],
+    'psteitz' => ['ps', 'Phil', 'Phil Steitz'],
+    'rbowen' => ['rb', 'Rich', 'Rich Bowen'],
+    'rgardler' => ['rg', 'Ross', 'Ross Gardler'],
+    'rubys' => ['sr', 'Sam', 'Sam Ruby'],
+    'rvs' => ['rs', 'Roman', 'Roman Shaposhnik'],
+    'striker' => ['ss', 'Sander', 'Sander Striker'],
+    'tdunning' => ['td', 'Ted', 'Ted Dunning']
+  }
+
+  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'] = DIRECTOR_MAP[id][INITIALS_IDX] if DIRECTOR_MAP[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" }[0]['text'].length
+      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
+      summary[STATS_KEY]['avgapprovals'] = (totapprovals / totreports).round(2)
+      summary[STATS_KEY]['avgcommentlen'] = (totcommentlen / totreports).round(0)
+      summary[STATS_KEY]['avgreportlen'] = (totreportlen / totreports).round(0)
+    rescue StandardError => e
+      summary[ERRORS_KEY] ||= "ERROR(#{meeting}) process error: #{e.message} #{e.backtrace[0]}"
+    end
+    return summary
+  end
+end

-- 
To stop receiving notification emails like this one, please contact
curcuru@apache.org.