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 }, )