Source code for openassembly.pirate_consensus.tasks

from celery.task import task
import pytz
import datetime
from pyvotecore.schulze_method import SchulzeMethod
from pyvotecore.schulze_stv import SchulzeSTV
from pirate_consensus.models import ConfirmRankedVote, RankedVote, RankedDecision, Consensus, UpDownVote
from collections import defaultdict
from pirate_topics.models import GroupSettings, MyGroup


"""
Scheduled phasechange task, moves consensus object from nomination
to voting phase and voting to decision phase. Events should be based
on a timedelta generated at the time of the proposal generation.
"""


[docs]def local_tz_to_utc(tz, phase_change_dt): dt1 = datetime.datetime.strptime(phase_change_dt, '%Y-%m-%d %H:%M') dt1 = tz.localize(dt1) return pytz.utc.normalize(dt1)
@task(ignore_result=True) def add(c): c.phase.curphase = c.phase.curphase.nextphase c.phasname = c.phase.curphase.phasename if c.phase.curphase.nextphase == None: c.phase.complete = True c.phase.active = False c.phase.save() c.save()
[docs]def get_consensus(consensus): consent = UpDownVote.objects.filter(parent=consensus, vote__gt=6).count() dissent = UpDownVote.objects.filter(parent=consensus, vote__lt=6).count() try: cons_perc = float(consent) / float(consent + dissent) except: print 'consent: ' + str(consent) print 'dissent: ' + str(dissent) cons_perc = 0.0 return cons_perc
""" Run this task on Conesnsu objects attached to questions. Shifts timed decisions. Also takes care of determining the winners, so this possibly computationally expensive task is offloaded to the worker """ @task(ignore_result=True) def initiate_nextphase(consensus): logger = initiate_nextphase.get_logger() logger.info('Initiating Next Phase for: {0}'.format(consensus.content_object.summary)) consensus.phase.curphase = consensus.phase.curphase.nextphase consensus.phasename = consensus.phase.curphase.phasename mygroups = MyGroup.objects.filter(topic=consensus.content_object.parent) num_members = mygroups.count() if consensus.phase.curphase.nextphase == None: #get the group settings settings, is_new = GroupSettings.objects.get_or_create(topic=consensus.content_object.parent) #iterate decisions made consensus.content_object.parent.decisions += 1 consensus.content_object.parent.save() #if this question does not pass consensus we do not accept, however ignore reporting #this gives people an opportunity to not agree with the need for the question itself consensus.consensus_percent = get_consensus(consensus) consensus.reporting_percent = float(UpDownVote.objects.filter(parent=consensus).count()) / float(num_members) cons_passed = test_if_passes(consensus.consensus_percent, consensus.reporting_percent, settings, ignore_reporting=False) winner = [] passes = False #if we accept all winners no need for ranking if consensus.winners is not None: #get nominations and ranked votes nominations = Consensus.objects.filter(parent_pk=consensus.content_object.pk) #currently supports single winner, in the future we check here for multiple winner or single winner confirmed = ConfirmRankedVote.objects.filter(parent=consensus, confirm=True) ballot_dict = defaultdict(int) user_has_ranked = [] for conf in confirmed: user_has_ranked.append(conf.user) rv = tuple([i.nom_cons.pk for i in RankedVote.objects.filter(user=conf.user, parent=consensus).order_by('ranked_vote')]) ballot_dict[rv] += 1 #for those that didnt rank vote, we can sample from their updownvotes for user in [i.user for i in UpDownVote.objects.filter(parent=consensus)]: if user not in user_has_ranked: user_ranks = [] print nominations for nom in nominations: try: vote = UpDownVote.objects.get(parent=nom, user=user) user_ranks.append(vote) except: print 'novote: ' + str(nom) user_ranks = sorted(user_ranks, key=lambda x: x.vote) user_ranks.reverse() rv = tuple([i.parent.pk for i in user_ranks]) ballot_dict[rv] += 1 #load up the vote dict for python-vote-core blist = [] for k, v in ballot_dict.items(): ballot = [[i] for i in k] if ballot != []: blist.append({'count': v, 'ballot': ballot}) print 'calc schulze' #right now there is only single winner schulze, add mechanism in for multi later on noms_passed = False if blist != []: #scz = SchulzeMethod(blist, ballot_notation="grouping").as_dict() scz = SchulzeSTV(blist, required_winners=consensus.winners, ballot_notation="grouping").as_dict() print scz schulze_winners = scz['winners'] #make sure it passes consensus also for nom in nominations: nom.consensus_percent = get_consensus(nom) nom.reporting_percent = float(UpDownVote.objects.filter(parent=nom).count()) / float(num_members) nom.save() noms_passed = test_if_passes(nom.consensus_percent, nom.reporting_percent, settings, ignore_reporting=True) if noms_passed == True and nom.pk in schulze_winners: passes = True winner.append(nom.pk) print 'set winner via schulze and passing' #there was no ranked winner or the ranked winner failed to consense (weird side case), cycle through all and choose if passes == False: #if this is None, accept all winners if consensus.winners == None: num_winners = len(nominations) else: num_winners = consensus.winners consensii = [] for nom in nominations: val = (get_consensus(nom), nom) consensii.append(val) nom.consensus_percent = val[0] nom.reporting_percent = float(UpDownVote.objects.filter(parent=nom).count()) / float(num_members) nom.save() consensii = sorted(consensii, key=lambda x: x[0]) consensii.reverse() for nom_cons, nom in consensii[0:num_winners]: ##calculate reporting percentage, the best is the winner noms_passed = test_if_passes(nom_cons, nom.reporting_percent, settings, ignore_reporting=True) if noms_passed: winner.append(nom.pk) passes = True print 'noms passed ' + str(noms_passed) #if we still haven't if passes == True and cons_passed == True: consensus.phasename = 'pass' else: consensus.phasename = 'fail' #what to do if there is no winner? #decision failed, no one voted in time rd = RankedDecision(passed=passes and cons_passed, winner=winner, parent=consensus, consensus_percent=consensus.consensus_percent, reporting_percent=consensus.reporting_percent, submit_date=datetime.datetime.now(), algorithm='Schulze Method Single Winner') rd.save() consensus.phase.complete = True consensus.phase.active = False else: initiate_nextphase.apply_async(args=[consensus], eta=consensus.phase.decision_dt) consensus.phase.save() consensus.save() logger.info('Next Phase Transition {0} completed'.format(consensus.content_object.summary)) #Tests to see if these objects pass the consensus/reporting settings of the groups
[docs]def test_if_passes(cons_perc, report_perc, settings, ignore_reporting=False): if cons_perc >= settings.consensus_percentage: if report_perc >= settings.percent_reporting or ignore_reporting: return True return False