129 lines
3.8 KiB
Python
Executable File
129 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import csv
|
|
import requests
|
|
import os
|
|
|
|
from io import StringIO
|
|
|
|
from dotenv import load_dotenv
|
|
|
|
def get_votes(ids):
|
|
votes = {}
|
|
for id in ids:
|
|
res = requests.get(os.environ['API_VOTE'].format(question_id=id), headers={'Authorization': 'Bearer ' + os.environ['BEARER']})
|
|
if not res.ok:
|
|
print('Could not get votes for id', id)
|
|
continue
|
|
f = StringIO(res.text)
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
votes.setdefault(row['Delegation'], []).append(row['Option'])
|
|
return list(votes.values())
|
|
|
|
def get_candidates_from_votes(votes):
|
|
candidates = set()
|
|
for vote in votes:
|
|
for option in vote:
|
|
candidates.add(option)
|
|
return candidates
|
|
|
|
def determine_winners(people, votes):
|
|
"""Single Transferable Vote (https://en.wikipedia.org/wiki/Single_transferable_vote)"""
|
|
elected = []
|
|
elected_set = set()
|
|
candidates = get_candidates_from_votes(votes)
|
|
surplus_votes = {x: 0 for x in candidates}
|
|
quota = len(votes) / people + 1
|
|
|
|
print('Votes:', len(votes))
|
|
print('Quota:', quota)
|
|
print('Candidates:', len(candidates))
|
|
|
|
def one_round():
|
|
print('-----------')
|
|
print('Start Round')
|
|
print('-----------')
|
|
round_votes = {x: 0 for x in candidates}
|
|
for vote in votes:
|
|
vote_weight = 1
|
|
for option in vote:
|
|
if option in elected_set:
|
|
vote_weight *= surplus_votes[option]
|
|
if option in candidates and option not in elected_set:
|
|
round_votes[option] += vote_weight
|
|
break
|
|
vote_counts = []
|
|
for candidate in candidates:
|
|
if candidate in elected_set:
|
|
continue
|
|
vote_counts.append((round_votes[candidate], candidate))
|
|
vote_counts.sort()
|
|
print('Vote counts:')
|
|
for (count, candidate) in reversed(vote_counts):
|
|
print(' {}: {}'.format(candidate, count))
|
|
if vote_counts[-1][0] >= quota or len(candidates) == people:
|
|
elected.append((vote_counts[-1][1], vote_counts[-1][0]))
|
|
elected_set.add(vote_counts[-1][1])
|
|
|
|
surplus = vote_counts[-1][0] - quota
|
|
if surplus > 0:
|
|
surplus_votes[vote_counts[-1][1]] = surplus / vote_counts[-1][0]
|
|
|
|
print('Elect', vote_counts[-1][1])
|
|
elif len(candidates) > people:
|
|
candidates.remove(vote_counts[0][1])
|
|
print('Remove', vote_counts[0][1])
|
|
|
|
while len(elected_set) < people:
|
|
one_round()
|
|
|
|
return elected
|
|
|
|
def test_votes():
|
|
return [
|
|
['O'],
|
|
['O'],
|
|
['O'],
|
|
['O'],
|
|
['P', 'O'],
|
|
['P', 'O'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'S'],
|
|
['C', 'H'],
|
|
['C', 'H'],
|
|
['C', 'H'],
|
|
['C', 'H'],
|
|
['S'],
|
|
['H'],
|
|
]
|
|
|
|
def main():
|
|
load_dotenv('secrets.env')
|
|
parser = argparse.ArgumentParser(description='Determine voted persons from list of votes.')
|
|
parser.add_argument('--test', action='store_true', help='Use test values for the votes')
|
|
parser.add_argument('people', type=int, help='Number of people who need to be voted.')
|
|
parser.add_argument('questions', type=int, nargs='+', help='IDs of the questions.')
|
|
args = parser.parse_args()
|
|
if args.test:
|
|
votes = test_votes()
|
|
else:
|
|
votes = get_votes(args.questions)
|
|
winners = determine_winners(args.people, votes)
|
|
|
|
print('-------------')
|
|
print('Final results')
|
|
print('-------------')
|
|
for i, (winner, votes) in enumerate(winners):
|
|
print('{:2} with {:2} votes: {}'.format(i+1, int(votes), winner))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|