pysteve/lib/plugins/cop.py (136 lines of code) (raw):
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
"""
Candidate or Party Voting Plugin
Currrently supports an arbitrary number of candidates from up to 26 parties
"""
import re
from lib import constants
def validateCOP(vote, issue):
"Tries to validate a vote, returns why if not valid, None otherwise"
parties = {}
if not 'candidates' in issue:
return "Invalid issue data detected"
for c in issue['candidates']:
if not 'pletter' in c:
return "Invalid issue data detected"
parties[c['pletter']] = True
letters = [chr(i) for i in range(ord('a'), ord('a') + len(parties))]
ivote = -1
try:
ivote = int(vote)
except:
pass # This is a fast way to determine vote type, passing here is FINE!
if not vote in letters and (ivote < 0 or ivote > len(issue['candidates'])):
return "Invalid characters in vote. Accepted are: %s" % ", ".join(letters,range(1,len(issue['candidates'])+1))
return None
def parseCandidatesCOP(data):
data = data if data else ""
candidates = []
pletter = ''
cletter = ''
pname = ''
s = 0
for line in data.split("\n"):
line = line.strip()
if len(line) > 0:
arr = line.split(":", 1)
letter = arr[0]
letter = letter.lower()
# Party delimiter?
if letter in [chr(i) for i in range(ord('a'), ord('a') + 26)] and len(arr) > 1 and len(arr[1]) > 0:
pname = arr[1]
pletter = letter
else:
candidates.append({
'name': line,
'letter': str(s),
'pletter': pletter,
'pname': pname
})
s += 1
return candidates
def tallyCOP(votes, issue):
m = re.match(r"cop(\d+)", issue['type'])
if not m:
raise Exception("Not a COP vote!")
numseats = int(m.group(1))
parties = {}
for c in issue['candidates']:
if not c['pletter'] in parties:
parties[c['pletter']] = {
'name': c['pname'],
'letter': c['pletter'],
'surplus': 0,
'candidates': []
}
parties[c['pletter']]['candidates'].append({
'letter': c['letter'],
'name': c['name'],
'votes': 0,
'elected': False
})
debug = []
winners = []
# Tally up all scores and surplus
for key in votes:
vote = votes[key]
for party in parties:
if parties[party]['letter'] == vote:
parties[party]['surplus'] += 1
else:
for candidate in parties[party]['candidates']:
if candidate['letter'] == vote:
candidate['votes'] += 1
numvotes = len(votes)
if numseats < len(issue['candidates']):
# Start by assigning all surplus (party votes) to the first listed candidate
iterations = 0
while numseats > len(winners) and iterations < 9999: # Catch forever-looping counts (in case of bug)
quota = (numvotes / numseats * 1.0) # Make it a float to prevent from rounding down for now
for party in parties:
surplus = 0
movedOn = False
for candidate in parties[party]['candidates']:
# If a candidate has not yet been elected, and has >= votes than the required quota, elect her/him
if not candidate['elected'] and numseats > len(winners):
if candidate['votes'] >= quota:
candidate['elected'] = True
winners.append("%s (%s) %u" % ( candidate['name'], parties[party]['name'], candidate['votes']))
# Did X receive more votes than needed? if so, add back to the party surplus
surplus += candidate['votes'] - quota
# If surplus of votes, add it to the next candidate in the same party
if surplus > 0:
for candidate in parties[party]['candidates']:
if not candidate['elected']:
candidate['votes'] += surplus
movedOn = True
break
# If surplus but no candidates left, decrease the number of votes required by the surplus
if not movedOn:
numvotes -= surplus
# Everyone's a winner!!
else:
for party in parties:
for candidate in parties[party]['candidates']:
winners.append("%s (%s) %u" % ( candidate['name'], parties[party]['name'], candidate['votes']))
# Return the data
return {
'votes': len(votes),
'winners': winners,
'winnernames': winners,
'debug': debug
}, """
Winners:
- %s
""" % "\n - ".join(winners)
constants.appendVote (
{
'key': "cop1",
'description': "Candidate or Party Vote with 1 seat",
'category': 'cop',
'validate_func': validateCOP,
'vote_func': None,
'parsers': {
'candidates': parseCandidatesCOP
},
'tally_func': tallyCOP
},
)
# Add ad nauseam
for i in range(2,constants.MAX_NUM+1):
constants.appendVote (
{
'key': "cop%02u" % i,
'description': "Candidate or Party Vote with %u seats" % i,
'category': 'cop',
'validate_func': validateCOP,
'vote_func': None,
'parsers': {
'candidates': parseCandidatesCOP
},
'tally_func': tallyCOP
},
)