Merge "Add script to tabulate scores"
This commit is contained in:
commit
8e22286c26
193
working_materials/tabulate_scores.py
Executable file
193
working_materials/tabulate_scores.py
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright 2015 VMware, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
# A custom class to preserve formatting in the help output
|
||||||
|
# description and also show default arguments.
|
||||||
|
class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter,
|
||||||
|
argparse.RawDescriptionHelpFormatter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Set up command line arguments.
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=textwrap.dedent("""\
|
||||||
|
Tabulate capability scores and write them to files.
|
||||||
|
|
||||||
|
This utility script tabulates scores from a DefCore scoring
|
||||||
|
worksheet based on the weights from a given Guideline JSON file.
|
||||||
|
It writes the scores in three formats:
|
||||||
|
|
||||||
|
1.) A text file that is identical to the source scoring
|
||||||
|
worksheet, but with an added column for the total score
|
||||||
|
for each capability.
|
||||||
|
2.) A CSV file with each capability's individual Criteria scores
|
||||||
|
as well as the total. The first line of the file will be
|
||||||
|
the plain-English Criteria names as parsed from the Guideline
|
||||||
|
json file.
|
||||||
|
3.) A simple "capability-name: total-score" output to stdout.
|
||||||
|
This is primarily useful for getting quick feedback on
|
||||||
|
the effect of changing scores.
|
||||||
|
"""),
|
||||||
|
add_help=True,
|
||||||
|
formatter_class=CustomFormatter)
|
||||||
|
parser.add_argument(
|
||||||
|
'-j', '--json-file',
|
||||||
|
default='../2015.next.json',
|
||||||
|
dest='json_file_name',
|
||||||
|
help='Path to the Guideline JSON file to read weights and names from.')
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--score-file',
|
||||||
|
default='scoring.txt',
|
||||||
|
dest='score_file_name',
|
||||||
|
help='File to read capabilities scores from.')
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--text-outfile',
|
||||||
|
dest='text_outfile_name',
|
||||||
|
help='File to write scores in text format to instead of the input file.')
|
||||||
|
parser.add_argument(
|
||||||
|
'-c', '--csv-outfile',
|
||||||
|
default='tabulated_scores.csv',
|
||||||
|
dest='csv_outfile_name',
|
||||||
|
help='File to write scores in CSV format to.')
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.text_outfile_name = args.text_outfile_name or args.score_file_name
|
||||||
|
|
||||||
|
# Folks have also requested a CSV output that can be imported to
|
||||||
|
# a spreadsheet program. Get that ready too.
|
||||||
|
csv_outfile = open(args.csv_outfile_name, 'w')
|
||||||
|
|
||||||
|
# We need to know what the weights assigned to each Criteria are
|
||||||
|
# in order to do scoring. Read them from a Guideline JSON file.
|
||||||
|
with open(args.json_file_name) as json_file:
|
||||||
|
json_data = json.loads(json_file.read())
|
||||||
|
criteria = json_data['criteria']
|
||||||
|
|
||||||
|
# Non-Admin doesn't appear in the scores because it's not
|
||||||
|
# an official criteria...rather it's something we use in scoring
|
||||||
|
# to remind ourselves when a non-admin API is being studied.
|
||||||
|
criteria['Non-Admin'] = {'name': 'Non-Admin'}
|
||||||
|
json_file.close()
|
||||||
|
|
||||||
|
# Now we're ready to parse scores from the scoring file.
|
||||||
|
# We'll buffer these in memory so we can write back to
|
||||||
|
# the same file we read them from if we're so inclined.
|
||||||
|
buffer = []
|
||||||
|
with open(args.score_file_name) as filehandle:
|
||||||
|
# The line format we're expecting here is:
|
||||||
|
#
|
||||||
|
# capability-name: [1,1,1] [1,1,1] [1,1,1] [1,1,1] [1] [100]
|
||||||
|
#
|
||||||
|
# Where the values inside the brackets can be zero, one, or a
|
||||||
|
# question mark. The final column is one that will be
|
||||||
|
# overwritten by this script and represents the total score
|
||||||
|
# for the capability. If present already, it's ignored.
|
||||||
|
pattern = re.compile('((\S+):\s+((\[\S,\S,\S\] ){4}\[\S\]))')
|
||||||
|
|
||||||
|
# The scores in the tuples have the following meanings, in
|
||||||
|
# the order they appear in the scoring files.
|
||||||
|
scorenames = ('deployed', 'tools', 'clients',
|
||||||
|
'future', 'complete', 'stable',
|
||||||
|
'discover', 'doc', 'sticky',
|
||||||
|
'foundation', 'atomic', 'proximity',
|
||||||
|
'Non-Admin')
|
||||||
|
|
||||||
|
# Write column headers to the CSV file using full names.
|
||||||
|
csv_outfile.write("Capability,")
|
||||||
|
for scorename in scorenames:
|
||||||
|
csv_outfile.write("%s," % (criteria[scorename]['name']))
|
||||||
|
csv_outfile.write("Total\n")
|
||||||
|
|
||||||
|
# Parse each line in the file and find scores.
|
||||||
|
for line in filehandle:
|
||||||
|
# Is this a scoring line? If so grab raw scores.
|
||||||
|
raw = pattern.match(line)
|
||||||
|
if raw is None:
|
||||||
|
# Not a line with a score, so just write it as-is.
|
||||||
|
buffer.append(line)
|
||||||
|
else:
|
||||||
|
# Grab the capability name
|
||||||
|
cap_name = raw.group(2)
|
||||||
|
|
||||||
|
# Write it to the CSV file
|
||||||
|
csv_outfile.write("%s," % cap_name)
|
||||||
|
|
||||||
|
# Grock the scores into a dict keyed by capability name.
|
||||||
|
scores = re.sub('[\[\]\, ]', '', raw.group(3))
|
||||||
|
score_hash = dict(zip(scorenames, list(scores)))
|
||||||
|
|
||||||
|
# Now tabluate scores for this capability. Scores will
|
||||||
|
# be negative if scoring isn't yet complete (e.g. it
|
||||||
|
# has '?' or another character that isn't 0 or 1 as
|
||||||
|
# it's score for any criteria.
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
# We also need to denote whether the scoring is complete.
|
||||||
|
# If we find capability scores that are not 0 or 1, we'll
|
||||||
|
# set this flag so we remember to negate the final score.
|
||||||
|
complete = 1
|
||||||
|
|
||||||
|
# If an API is non-admin, it's vetoed and set to 0.
|
||||||
|
# Only tabulate scores for non-admin API's.
|
||||||
|
if int(score_hash['Non-Admin']) == 1:
|
||||||
|
for scorename in scorenames:
|
||||||
|
csv_outfile.write("%s," % score_hash[scorename])
|
||||||
|
|
||||||
|
# If the scorename is non-admin, skip it as this
|
||||||
|
# doesn't affect the scoring total; it merely
|
||||||
|
# indicates whether the API in question is admin-only
|
||||||
|
# and therefore not scorable.
|
||||||
|
if scorename == 'Non-Admin':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If the score is a digit, add it in to the total.
|
||||||
|
if re.match('\d', score_hash[scorename]):
|
||||||
|
total += (int(score_hash[scorename]) *
|
||||||
|
int(criteria[scorename]['weight']))
|
||||||
|
|
||||||
|
# If the score isn't a digit, we're not done scoring
|
||||||
|
# this criteria yet. Denote that by making the
|
||||||
|
# final score negative.
|
||||||
|
else:
|
||||||
|
complete = -1
|
||||||
|
|
||||||
|
# The total now becomes negative if scoring
|
||||||
|
# wasn't complete.
|
||||||
|
total = total * complete
|
||||||
|
|
||||||
|
# Now write the total score to a couple of places.
|
||||||
|
# Put it in the tabulated file.
|
||||||
|
buffer.append("%s [%d]\n" % (raw.group(1), total))
|
||||||
|
|
||||||
|
# Put in in the CSV for easy spreadsheet import.
|
||||||
|
csv_outfile.write("%s\n" % (total))
|
||||||
|
|
||||||
|
# And stdout is useful for folks who are experimenting with
|
||||||
|
# the effect of changing a score.
|
||||||
|
print "%s: %d" % (cap_name, total)
|
||||||
|
|
||||||
|
# Now we can write the text output file.
|
||||||
|
with open(args.text_outfile_name, 'w') as outfile:
|
||||||
|
for line in buffer:
|
||||||
|
outfile.write(line)
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
print "\n\nText output has been written to %s" % args.text_outfile_name
|
||||||
|
print "CSV output has been written to %s" % args.csv_outfile_name
|
Loading…
Reference in New Issue
Block a user