#!/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()